嵌入式Linux字符设备驱动LED驱动编写嵌入式Linux字符设备驱动开发总结--LED驱动
作者:英贝得教育02就业班 杨广东
设备驱动程序是集成在内核中,处理硬件或操作硬件控制器的软件。字符设备还是块设备都为内核提供相同的调用接口,所以内核能以相同的方式处理不同的设备。字符设备提供给应用程序流控制接口有:open/read/write/ioctl/……,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码
模块的概念:linux内核模块是一种可以被内核动态的加载和卸载的可执行的二进制代码。通过内核模块可以扩展内核的功能,通常内核模块被用于设备驱动,文件系统等。如果没有内核模块,需要向内核添加功能就需要修改代码,重新编译内核,安装新内核等,不仅繁琐而且容易出错,不易于调试。Linux内核是一个整体结构,各种功能结合在一起,linux内核的开发者设计了内核模块机制,从代码的角度看,内核模块是一组可以完成某种功能的函数集合。从执行的角度看,内核模块是一个已经编译但没有连接的程序。内核模块类似应用程序,但是与普通应用程序有所不同,区别在与:
运行环境不同
功能定位不同
函数调用方式不同
Linux设备驱动程序与外界的接口可以分为如下3个部分:
1.驱动程序与内核操作系统内核的接口:通过数据结构:file_operation来完成的
2.驱动程序与系统引导内核的接口:利用驱动程序对设备进行初始化
3.驱动程序与设备的接口:描述了驱动程序如何与设备进行交互
一.字符驱动的具体流程:
所需要的头文件和宏定义:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LED_MAJOR 100 //主设备号
#define LED_SECOND 5 //次设备号
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
struct cdev led_cdev0; //定义cdev结构体,内核是通过这个结构体来访问驱 //动程序的
dev_t led_t = 0; //无整型32位
1.定义设备结构体变量:
因为内核是通过cdev结构体来访问驱动的,所以要定义结构体:
struct cdev my_cdve0;
定义好描述字符IO设备的结构体后,就用该结构体来定义一个变量,在内核中就用该变量来代表我们的字符IO设备,在这里就代表的是LED灯。
2.定义设备的操作接口和编写接口操作函数:
每个设备都对应一些操作,应用程序及是通过这些接口操作函数来使用驱动程序完成对设备的控制,设备的操作接口定义如下:
Struct file_operation led_ops = {
.ownr = THIS_MODULE; //一个宏
.open = myled_open,
ioctl = myled_ioctl,
.release = myled_close,
};
相应函数的实现:
static int leds_open(struct inode *inode, struct file *file)
{
int i;
i = (1 << 10) + (1 << 12) + (1 << 16) + (1 << 20);
writel(i,S3C2410_GPBCON); //S3C2410_GPBCON在头文件
//中已定义
i = 0x0; //灯全亮
writel(i,S3C2410_GPBUP); //在内核中读写函数用writel()和readl();
i = 0xfffffffe; //灯灭
writel(i,S3C2410_GPBDAT);
return 0;
}
tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ //内核中的参数 //应用程序中传过来的参数
switch(cmd)
{
case IOCTL_LED_ON :
writel(0xffffffde,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONa :
writel(0xffffffbe,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONb:
writel(0xfffffefe,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONc:
writel((~(0x01<<10)&0xfffffffe),S3C2410_GPBDAT);
break;
case IOCTL_LED_ONd:
writel(0x0,S3C2410_GPBDAT);
break;
case IOCTL_LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
一个LED灯对应一个字符设备驱动;
tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ //内核中的参数 //应用程序中传过来的参数
Dev_t cur_dev_no = inode->i_rdev; //主设备号
Dev_t minordev = MINOR(inode->i_rdev); //次设备号
If(minordev == 0) //表示是第一个灯的驱动,后面以此类推
{
int i = 0;
switch(cmd)
{
case IOCTL_LED_ON:
writel((~(0x01)<<5)&&0xfffffffe,S3C2410_GPBDAT);
Break;
case IOCTL_LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
}
If(minordev == 1) //表示是第二个灯的驱动,后面以此类推
{
……
}
return 0;
}
3.字符设备驱动模块的初始化和退出函数:
字符驱动模块是一个linux模块,所以要遵循linux模块的框架。首先查看自己定义的主设备号在内核中是否已被占用,初始化字符设备结构体变量cdev,向内核注册该字符IO设备驱动,分别通过调用cdev_init(),cdev_add()函数来实现,对于字符驱动模块初始化函数定义如下:
Static int__init myled_init(void)
{
led_t = MKDEV(LED_MAJOR,LED_SECOND);
re = register_chrdev_region(LED_MAJOR,1,"my"); //查看此设备号是否已被占用返回值等于0分配成功
if (re < 0)
{
printk(" can't register major number
");
return -1;
}
cdev_init(&led_cdev,&leds_fops); //初始化cdev结构体
r = cdev_add(&led_cdev,led_t,4); //注册到内核中 4 为要共用一个设备驱动的次设备号
if(r < 0)
{
printk("add wrong
");
return -1;
}
return 0;
}
字符模块退出函数组要就是删除内核中的字符设备:
Static void__exit myled_exit(void)
{
Cdev_del(&led_cdev);
Printk("myled exit now
");
}
完成了模块的初始化和退出函数后,最后向内核申明myled_init和lyled_exit函数以及申明模块license:
module_init(leds_init); 初始化字符驱动设备
module_exit(leds_exit);
MODULE_LICENSE("GPL"); 基于GPL库开发的软件在用到GPL库的这些代码必须开源
二.测试程序:
#include
#include
#include
#include
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
int main(int argc, char **argv)
{
int fd = 0;
fd = open(argv[2],0);
if(strcmp(argv[1],"on")==0)
{
ioctl(fd,IOCTL_LED_ON);
}
else if(strcmp(argv[1],"off")==0)
{
ioctl(fd,IOCTL_LED_OFF);
}
return 0;
}
如果是一个LED灯一个驱动,测试程序中的fd = open("/dev/ledS0",0)应改为fd = open(argv[2],0);用命令行的模式:
./LES_TEST on /dev/ledS0 这是第二个LED驱动
./LES_TEST on /dev/ledS1这是第二个LED驱动
三.Makefile:
obj-m:=dri_led.o
KDIR = /home/kernel/linux-2.6.30.9 //将要使用的内核
all:
make -C $(KDIR) SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o led_test led_test.c //对led_test.c进行交叉编译,否则在开发板上不能运行
clean:
@rm -rf dri_led *.o
四.字符IO设备驱动程序测试:
Leds0驱动,测试程序,makefile写好之后,把linux-2.6.30.9内核重新编译,通过超级终端把生成的zImage下载到开发板kernel中区(开发板起来之后,6-4-Y-1-4),把在linux下编译好的测试程序的二进制文件和生成的KO文件下载到开发板中去(通过超级终端的菜单栏中的“传送”-“发送文件”),这时开发板中已经有所需要用到的文件。
1.往内核添加驱动模块:
在终端shell下通过执行 Insmod myled.ko
2.改变环境变量,如下:
在终端shell下通过执行chmod来改变环境变量
chmod +x ledtest(把测试程序改成可执行的二进制文件)
3.创建设备文件节点(一个LED灯一个驱动):
在终端shell下通过执行mknod命令创建文件节点
增加节点:mknod /dev/ledS0 c 100 0
增加节点:mknod /dev/ledS1 c 100 1
增加节点:mknod /dev/ledS2 c 100 2
增加节点:mknod /dev/ledS3 c 100 3
/dev/ledS0:字符设备名,包括路径
C :设备类型,c表示字符设备
100 :主设备号
0 :次设备号
4.在shell下执行led_test测试应用程序:
./LES_TEST on /dev/ledS0
五:总结
应用程序通过调用API:open(),ioctl()函数来访问内核,ioctl函数传过来的设备号,到内核中指针数组找相对应主设备号的,通过这个设备号找到该设备的结构体然后调用相应的函数以实现相应的功能:
应用层:
fd = open("/dev/ledS0",0);
ioctl(fd,IOCTL_LED_OFF);
内核层:
标准输入
标准输出
出错
&cdev
0
1
2
100 //主设备号与cdev结构体的首地址
Cdev 结构体
Struct file_operation*ops
Struct moduleowner
Dev_t dev
Struct list_head list
*open
*write
*ioctl
*read
Struct file_opertion*ops:(对应着相应的实现函数)
六.配置menucofig
1.把要加到内核中的驱动文件拷贝到drivers/char目录下
2.修改kconfig文件添加如下代码:
config HT_LED
Bool "LED"
default y
---help---
This is my first drive -LED!
3.修改makefile添加如下代码:
obj-$(CONFIG_HT_LED) +=dri_led.o
配置成功,重新编译生成zImage