Linux下驱动开发之二(LED驱动)-------Tiny6410
2013-12-02 21:45
501 查看
裸机下控制LED灯非常方便,只需要配置好GPIO引脚功能,然后向GPIO引脚映射的内存地址处写入数据即可,但linux下驱动就不那么简单了,需要结合字符设备驱动的架构,然后将功能实现添加进去,笔者参考linux设备驱动程序(第三版)中介绍的新的接口来实现驱动。友善之臂官网提供的源码是基于miscdevice的驱动,而且接口似乎有点老,比如在linux设备驱动程序(第三版)中强调需要使用新的内存I/O接口来访问映射内存,建议使用ioread32,iowrite32等,但是它依然使用writel,readl等函数。另外笔者使用了两种方法:一种是修改友善之臂的驱动;另一种是使用标准的字符设备驱动
第一种方法实现:
混杂设备驱动中需要实现的是填充file_operations结构,然后设置miscdevices的各个字段值,然后注册到内核即可。详细流程如下:
1.编写ioctl接口函数
2.填充file_operations结构,主要是将ioctl函数注册进去
3.填充miscdevice结构,主要是次设备号、fops和设备名称。
4.编写要提供给module_init宏使用的初始化代码,设置LED端口为输出,注册到内核
5.编写要提供给module_exit宏使用的注销代码,主要是注销掉初始化中注册到内核的miscdevice设备
6.本源码修改了arg参数的值,0表示LED1~LED4全选中,1~4分别控制对应的LED灯
详细代码如下:
第2种方法使用了标准的字符设备驱动的编写方法,具体流程如下:
1.编写在file_operations中使用的ioctl和open函数,其中open函数初始化GPIO接口,ioctl函数实现LED灯的亮灭
2.填充file_operations结构
3.编写module_init宏使用的初始化代码;在这段代码中主要完成
a. 申请主设备号,默认使用动态分配的方法
b. 动态分配cdev结构,cdev表示一个字符设备
c. 初始化cdev结构
d. 注册到内核
4.编写module_exit宏使用的注销代码,主要是注销cdev结构和动态申请的主设备号
部分代码如下:
编写Makefile如下:
完成后,接着编写应用程序测试它:
编译,然后使用NFS挂载到开发板上,加载模块然后运行应用程序即可,注意标准字符设备驱动程序还需要创建设备,如下图所示:
运行led程序可以看到LED灯已经可以成功驱动了!
第一种方法实现:
混杂设备驱动中需要实现的是填充file_operations结构,然后设置miscdevices的各个字段值,然后注册到内核即可。详细流程如下:
1.编写ioctl接口函数
2.填充file_operations结构,主要是将ioctl函数注册进去
3.填充miscdevice结构,主要是次设备号、fops和设备名称。
4.编写要提供给module_init宏使用的初始化代码,设置LED端口为输出,注册到内核
5.编写要提供给module_exit宏使用的注销代码,主要是注销掉初始化中注册到内核的miscdevice设备
6.本源码修改了arg参数的值,0表示LED1~LED4全选中,1~4分别控制对应的LED灯
详细代码如下:
/* * Tiny6410开发板-led驱动(linux)使用miscdevice实现 * 使用方法: * 1.编译内核2.6.38 * 2.编写Makefile文件 * 3.编译运行 * 4.运行app/led测试(有关具体的测试细节清参考led_app.c) * 本程序中读写GPIO使用了新的访问I/O内存的函数ioread32,iowrite32 * Author:jefby * Emai:jef199006@gmail.com */ #include <linux/module.h>//MODULE_AUTHOR,MODULE_LICENSE #include <linux/init.h>//module_init,module_exit #include <linux/fs.h>//file_operations #include <linux/miscdevice.h>//misdevice #include <asm/io.h>//ioread32,iowrite32 #include <mach/gpio-bank-k.h>//定义了GPKCON #include <mach/regs-gpio.h>//定义了gpio-bank-k中使用的S3C64XX_GPK_BASE #include <mach/map.h>//定义了S3C64XX_VA_GPIO /*设备名称*/ #define DEVICE_NAME "leds" /* ioctl接口函数,cmd=0,表示关闭参数arg指定的LED灯;arg的值不能大于4; * 其中0表示所有的LED,LED1~LED4 * 1~4分别表示LED1~LED4 * 返回0或者错误-EINVAL * */ static int s3c6410_leds_ioctl( struct file*filp, unsigned int cmd, unsigned long arg ) { switch(cmd){ unsigned tmp; case 0://close case 1://open if(arg > 4){//参数错误 return -EINVAL; } else if(arg == 0){//全亮或者全灭 tmp = ioread32(S3C64XX_GPKDAT);//读出LED1~LED4所在寄存器的值 tmp &= ~(0xF<<4);//打开LED1~LED4 if(cmd == 0)//若是关闭,则关闭掉LED1~LED4 tmp |= (0xF<<4);//if close,then write 0xF to GPK4~GPK7 iowrite32(tmp,S3C64XX_GPKDAT); }else{ //参数为1~4范围内 tmp = ioread32(S3C64XX_GPKDAT); tmp &= ~(1<<(4+arg-1));//清除arg所指示的那一位值 tmp |= ((!cmd)<<(4+arg-1));//写入新值 iowrite32(tmp,S3C64XX_GPKDAT); } return 0; default: return -EINVAL; }//switch(cmd) return 0;//应该不会执行 } static struct file_operations dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = s3c6410_leds_ioctl,//定义的ioctl函数 }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR,//动态分配次设备号 .name = DEVICE_NAME,//设备名称 .fops = &dev_fops, }; /*模块初始化*/ static int __init dev_init(void) { int ret; /* * 在设备驱动程序注册的时候初始化LED1~LED4所对应的GPIO管脚为输出 * 并关闭LED1~LED4 */ unsigned tmp; tmp = ioread32(S3C64XX_GPKCON); tmp = (tmp & ~(0xFFFFU<<16)) | (0x1111U<<16);//先清除然后再设置其为输出 iowrite32(tmp,S3C64XX_GPKCON);//写入GPKCON tmp = readl(S3C64XX_GPKDAT); tmp |= (0xF<<4);//关闭LED灯 iowrite32(tmp,S3C64XX_GPKDAT); //注册misc ret = misc_register(&misc); printk(DEVICE_NAME"\tinitialized.\n"); return ret; } static void __exit dev_exit(void) { //卸载 misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("jefby");
第2种方法使用了标准的字符设备驱动的编写方法,具体流程如下:
1.编写在file_operations中使用的ioctl和open函数,其中open函数初始化GPIO接口,ioctl函数实现LED灯的亮灭
2.填充file_operations结构
3.编写module_init宏使用的初始化代码;在这段代码中主要完成
a. 申请主设备号,默认使用动态分配的方法
b. 动态分配cdev结构,cdev表示一个字符设备
c. 初始化cdev结构
d. 注册到内核
4.编写module_exit宏使用的注销代码,主要是注销cdev结构和动态申请的主设备号
部分代码如下:
static int __init leds_init(void) { /* *使用linux设备驱动中介绍的新方法来写,而不是用老的接口 *申请主设备号 *register_chrdev *新方法: * 0.获得一个或者多个设备编号(register_chrdev_region,或者alloc_chrdev_region) * 1.分配cdev结构 * 2.初始化该cdev结构 * 3.注册到内核 * */ int result; printk("Tiny 6410 leds module init.\n"); if(leds_major){ dev = MKDEV(leds_major,0); result = register_chrdev_region(dev,1,"leds"); }else{ result = alloc_chrdev_region(&dev,0,1,"leds"); leds_major = MAJOR(dev);//获得主设备号 printk(KERN_ALERT "leds major = %d.\n",leds_major); } if(result < 0){ printk(KERN_WARNING "leds:can't get major %d\n",leds_major); return result; } leds_cdev = cdev_alloc(); leds_cdev->ops = &leds_fops; //void cdev_init(struct cdev*cdev,struct file_operations *fops) cdev_init(leds_cdev,&leds_fops); //int cdev_add(struct cdev *dev,dev_t num,unsigned int count) cdev_add(leds_cdev,dev,1); printk("cdev add ok.\n"); return 0; }
int leds_ioctl( struct file * filp, unsigned int cmd, unsigned long arg ) { if(arg > 4)//确定参数必须为0,此时ON和OFF都对应的是LED1~LED4 return -EINVAL; switch(cmd){ unsigned long tmp; case 0: case 1: if(arg == 0){ tmp = ioread32(S3C64XX_GPKDAT); tmp &= ~(0xF<<4); if(cmd == 0) tmp |= 0xF<<4; iowrite32(tmp,S3C64XX_GPKDAT); }else{ tmp = ioread32(S3C64XX_GPKDAT); tmp &= ~(0x1<<(4+arg-1));//打开arg对应的LED灯 if(cmd == 0) tmp |= 0x1<<(4+arg-1); iowrite32(tmp,S3C64XX_GPKDAT); } return 0; default: return -EINVAL; } return 0; }
编写Makefile如下:
ifneq ($(KERNELRELEASE),) obj-m := leds.o else KERNELDIR ?= /opt/FriendlyARM/mini6410/linux/linux-2.6.38 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.ko *.o *.order *~ *symvers *.mod.c endif
完成后,接着编写应用程序测试它:
/* * * 控制LED的应用程序,格式 ./led arg cmd * arg=[0~4],cmd=[0.1] * cmd=0表示关闭LED,cmd=1表示打开LED * arg=0,全关或者全开LED灯 * arg=1~4表示打开或关闭指定的LED灯 * Author:jefby * Email:jef199006@gmail.com * **/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> int main(int argc, char **argv) { int on; int led_no; int fd; /* 检查led 控制的两个参数,如果没有参数输入则退出。*/ if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||\ on < 0 || on > 1 || led_no < 0 || led_no > 4) { fprintf(stderr, "Usage: leds led_no 0|1\n"); exit(1); } /*打开/dev/leds 设备文件*/ fd = open("/dev/leds0", 0); if (fd < 0) { fd = open("/dev/leds", 0); } if (fd < 0) { perror("open device leds"); exit(1); } /*通过系统调用ioctl 和输入的参数控制led*/ ioctl(fd, on, led_no); /*关闭设备句柄*/ close(fd); return 0; }
编译,然后使用NFS挂载到开发板上,加载模块然后运行应用程序即可,注意标准字符设备驱动程序还需要创建设备,如下图所示:
运行led程序可以看到LED灯已经可以成功驱动了!
相关文章推荐
- 慢慢学Linux驱动开发,第九篇,tiny6410_LED驱动
- 慢慢学Linux驱动开发,第九篇,tiny6410_LED驱动
- 慢慢学Linux驱动开发,第九篇,tiny6410_LED驱动
- Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动
- 手把手教你学linux驱动开发:模块编程、虚拟字符设备编程、LED字符设备驱动
- “手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- tiny6410 Linux 按键控制LED驱动
- Linux驱动开发③--LED流水灯驱动示例
- Linux驱动开发之四-----LED改进测试(增加自动创建设备节点)
- Linux驱动开发之五---按键驱动(查询方式)(Tiny6410)
- 手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动
- Linux驱动程序开发(4) - 字符设备驱动(3)-LED设备驱动和应用程序
- tiny6410 linux混杂设备 led驱动
- linux驱动开发FL2440开发板-LED驱动及其测试程序
- Linux驱动开发-OK6410-LED字符设备驱动实现过程
- “手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- Linux驱动开发之S3C2440按键点亮LED
- linux驱动开发“飞凌ok6410开发板之系列led驱动的开发”
- ARM开发之linux字符型驱动的编写----LED驱动为例