嵌入式Linux字符驱动LED灯设计
嵌入式Linux字符驱动LED灯设计
嵌入式Linux字符设备驱动LED驱动编写
一.任务要求
完成一个字符IO口驱动,在开发板上该IO口对应LED灯。该驱动程序通过控制IO口的高低电平来控制亮灭。同时要写一个应用层的测试程序,用来测试驱动程序。我的测试程序为myled_test.c,要求在shell下能够通过该测试程序来控制LED灯的亮灭。如:
./myled_test on 表示灯全亮;
./myled_test off 表示灯全灭;
二.流程图设计
图1.应用层访问设备的流程图
三. 字符IO口驱动程序的设计流程
1)Linux内核的模块机制
在Linux下,驱动程序都是以模块存在的,模块是向内核动态的增加功能,每个模块都包括module_init和module_exit两个函数,分别在向系统插入模块和移除模块时被调用。框架如下:
#include <linux/module.h>
#include <linux/init.h>
static int hellomodule_init(void)
{
printk("hello word\n");
return 0;
}
static void hellomodule_exit(void)
{
printk("goodbye word\n");
return;
}
module_init(hellomodule_init);
module_exit(hellomodule_exit);
MODULE_LICENSE("GPL");
<hellomodule_init 和hellomodule_exit通过module_init和 module_exit两个宏注册到内核,这样在通过insmod和rmmod命令往内核增加移除时候就会调用。>
2)Linux字符IO驱动设计
步骤如下:
1. 定义描述字符IO设备的结构体
在Linux中,每个设备都有一个结构体来描述的,该结构体包含了该设备的所有信息。如下:
struct cdev
{
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
2. 定义设备结构体变量
用设备结构体来定义一个变量,在内核中该变量就代表对应的设备。如下:
struct cdev myled_cdev;
3. 定义设备的操作接口函数
设备都是有一些操作的,应用程序就通过这些接口操作函数来使用驱动程序对设备的控制。如下:
static struct file_operations led_ops={
.open = myled_open,
.ioctl = myled_ioctl,
.release = myled_close,
};
4. 设备的操作函数
根据设备的操作接口函数完成操作函数,如下:
int myled_open(struct inode *inode,struct file *file)
{
int value = 0;
value = (1<<10)|(1<<12)|(1<<16)|(1<<20);/*将GPB5,6,8,10设为输出功能*/
writel(value,S3C2410_GPBCON);
value = 0x0;/*引脚都为低电平,灯全亮*/
writel(value,S3C2410_GPBUP);
value = 0xfffffffe;/*引脚都为高电平,灯全灭蜂鸣器不响*/
writel(value,S3C2410_GPBDAT);
return 0;
}
<在应用程序执行open()系统调用时,myled_open函数将被调用。将LED的GPIO引脚设为输出功能。>
int myled_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case LED_ON:/*设置引脚为输出低电平0*/
writel(0x0,S3C2410_GPBDAT);
break;
case LED_OFF: /*设置引脚为输出高电平1*/
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
<当应用程序执行系统调用ioctl(fd,cmd)时,(fd为open()系统调用返回的文件句柄,)myled_ioctl函数将被调用。>
int myled_close(struct inode *inode,struct file *file)
{
printk("bye");
return 0;
}
5. 字符驱动模块的初始化和退出函数
这两个函数是模块的框架,定义如下:
static int __init LED_INIT(void)
{
int re = 0;
int ret = 0;
myled_no = MKDEV(MAINLEDNO,MINLEDNO);
ret = register_chrdev_region(myled_no,1,"myled");
if(ret<0)
{
printk("cant regist");
return ret;
}
printk("reg ok");
cdev_init(&myled_cdev,&led_ops);
myled_cdev.owner = THIS_MODULE;
re = cdev_add(&myled_cdev,myled_no,1);
if(re<0)
{
printk("add error");
return re;
}
printk("add ok");
return 0;
}
<LED_INIT主要完成3件事:调用register_chrdev_region询问内核该设备号是否有设备占用,如果没有就继续下面操做,初始化设备结构体变量cdev和向内核注册字符IO设备,分别调用cdev_init和 cdev_add。>
static void __exit LED_EXIT(void)
{
cdev_del(&myled_cdev);
unregister_chrdev_region(myled_no,1);
printk("exit ok");
}
<字符模块退出函数主要是删除内核中的字符设备并卸载驱动程序,分别调用cdev_del 和unregister_chrdev_region> 。
最后向内核申明myled_init和myled_exit函数以及LICENSE。如下:
module_init(LED_INIT);
module_exit(LED_EXIT);
MODULE_LICENSE("GPL");
6. 设备驱动的编译
Linux驱动模块的编译是通过Linux顶层下的Makefile文件来实现的,如下:
obj-m:=myled.o
KDIR=/home/neo/linux-2.6.30.9-EL-20101126
all:
make -C $(KDIR) SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o myled_test myled_test.c
<myled.o是我们需要编译的模块,KDIR是内核目录,SUNDIRS是驱动源码所在目录,arm-linux-gcc是交叉编译器。>
四. 字符IO驱动测试程序设计流程
为了测试IO驱动是否正常,在应用层编写一个LED灯的程序,主要完成打开,关闭功能。如下:
int main(int argc,char **argv)
{
int fd;
fd = open("/dev/ledS0",0);/*打开设备*/
if(!strcmp(argv[1],"on"))
{
ioctl(fd,LED_ON);/*点亮*/
}
else if(!strcmp(argv[1],"off"))
{
ioctl(fd,LED_OFF);/*熄灭*/
}
return 0;
}
<其中的open,ioctl最终会调用驱动程序中的myled_open,myled_ioctl函数。>
五. 编译及下载流程
1.动态加载
a) 将myled.c和myled_test.c用make进行编译,得到模块myled.ko和执行文件myled_test
b) 更新S3C2440的内核和文件系统。启动开发板的LINUX系统。
c) 往开发板内核中添加驱动模块
在shell下执行insmod myled.ko
d) 创建设备文件节点
在shell下执行mknod /dev/ledS0 c 108 0
<c:设备类型
108:主设备号
0:次设备号>
e) 将执行文件myled_test添加到开发板上,并执行:
./myled_test on
./myled_test off
2.静态加载
a) 打开内核源码文件包,将myled.c文件拷到driver文件夹下,打开Kconfig文件,在其中添加如下代码:
config LINETECH_LED
bool "config myled"
default y
help
myled driver test
b) 同时打开Makefile文件,添加如下代码:
obj-$(CONFIG_LINETECH_LED) +=myled.o
c) 在终端输入make menuconfig进行内核的配置,如图:
最后一个选项即为我要选得myled配置。
d) 将内核编译得到zImage,下载到开发板中,创建设备文件节点
在shell下执行mknod /dev/ledS0 c 108 0
<c:设备类型
108:主设备号
0:次设备号>\
e) 将执行文件myled_test添加到开发板上,并执行:
./myled_test on
./myled_test off
三. 总结
通过这次对LINUX字符驱动LED灯的设计让我获益匪浅,该实验让我对驱动程序的设计有了大概的了解。首先,要知道驱动程序必须要有框架,即初始化和退出。然后,每个设备都有与之对应的结构体,而应用层要使用驱动程序,其中必须要有接口操作函数,特别注意下我在myled_open中将LED的寄存器进行配置,而不是在初始化函数中设置,是因为:虽然加载了模块,但是这个模块却不一定会被用到,所以在使用时才去设置。最后在初始化函数中要对cdev结构体变量进行初始化,并把该结构体注册到内核中。
概括为:应用程序在内核的字符设备数组中能够找到主设备号,根据设备号找到该设备的结构体cdev,访问结构体中的变量*ops,而*ops指向file_operation结构体,该结构体中有被应用层调用的函数。
四. 代码
//myled.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#define MAINLEDNO 108
#define MINLEDNO 0
#define LED_ON 0x01
#define LED_OFF 0x02
dev_t myled_no = 0;
struct cdev myled_cdev;
````````int myled_open(struct inode *inode,struct file *file)
{
int value = 0;
value = (1<<10)|(1<<12)|(1<<16)|(1<<20);
writel(value,S3C2410_GPBCON);
value = 0x0;
writel(value,S3C2410_GPBUP);
value = 0xfffffffe;
writel(value,S3C2410_GPBDAT);
return 0;
}
int myled_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case LED_ON:
writel(0x0,S3C2410_GPBDAT);
break;
case LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
int myled_close(struct inode *inode,struct file *file)
{
printk("bye");
return 0;
}
static struct file_operations led_ops={
.open = myled_open,
.ioctl = myled_ioctl,
.release = myled_close,
};
static int __init LED_INIT(void)
{
int re = 0;
int ret = 0;
myled_no = MKDEV(MAINLEDNO,MINLEDNO);
ret = register_chrdev_region(myled_no,1,"myled");
if(ret<0)
{
printk("cant regist");
return ret;
}
printk("reg ok");
cdev_init(&myled_cdev,&led_ops);
myled_cdev.owner = THIS_MODULE;
re = cdev_add(&myled_cdev,myled_no,1);
if(re<0)
{
printk("add error");
return re;
}
printk("add ok");
return 0;
}
static void __exit LED_EXIT(void)
{
cdev_del(&myled_cdev);
unregister_chrdev_region(myled_no,1);
printk("exit ok");
}
module_init(LED_INIT);
module_exit(LED_EXIT);
MODULE_LICENSE("GPL");
//myled_test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define LED_ON 0x01
#define LED_OFF 0x02
int main(int argc,char **argv)
{
int fd;
fd = open("/dev/ledS0",0);
if(!strcmp(argv[1],"on"))
{
ioctl(fd,LED_ON);
}
else if(!strcmp(argv[1],"off"))
{
ioctl(fd,LED_OFF);
}
return 0;
}
Makefile
obj-m:=myled.o
KDIR=/home/neo/linux-2.6.30.9-EL-20101126
all:
make -C $(KDIR) SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o myled_test myled_test.c
南京 英贝得嵌入式学院 ---近几年 首次打入南京市场 最人性化的学习费用 我们教的 是你在书本上所学不到的知识。 以圆满成功的 寒假班迎接我们 “3月10日 的周末班'"4月10日的就业班” 报名既享受更多的优惠 。
顶一个
顶一个
支持一下
好长~
初学者,学习一下
顶一个!