完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
嵌入式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 #include static int hellomodule_init(void) { printk("hello wordn"); return 0; } static void hellomodule_exit(void) { printk("goodbye wordn"); return; } module_init(hellomodule_init); module_exit(hellomodule_exit); MODULE_LICENSE("GPL"); 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; } 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 四. 字符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 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 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 #include #include #include #include #include #include #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 #include #include #include #include #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日的就业班” 报名既享受更多的优惠 。
|
|
|
|
|
|
如何配置Linux操作系统设备树让我的开发板可以将板子上的GPIO接口用作 I2S输出??
1675 浏览 1 评论
1450 浏览 0 评论
2078 浏览 0 评论
2019 浏览 2 评论
1118 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-20 07:00 , Processed in 0.826359 second(s), Total 83, Slave 66 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号