LED设备驱动
来源:互联网 发布:优化驱动器多少遍 编辑:程序博客网 时间:2024/05/17 03:44
去年开的博客,到今天只有两篇文章,再看看同学的博客,惭愧啊。
写博客虽然额外多花了些时间,不过对自学知识有复习与总结的作用,还能提高语言组织能力,而且,如果有朋友能从我的博客中获益那就再好不过了。希望自己养成写博客的习惯,就从这一篇开始吧。
LED驱动是最简单的字符设备,可以说是Linux设备驱动程序里的HelloWorld,适合用来熟悉字符设备驱动程序开发的基本流程
笔者使用的是JZ2440开发板,板上有三个LED,分别对应GPF4,GPF5,GPF6引脚。本节将实现4个led设备:
/dev/leds
/dev/led0
/dev/led1
/dev/led2
第一个设备对应3个led的整体,后面3个分别对应其中一个led
先看看设备驱动编写时涉及到的基本知识,包括一些数据结构和函数:
设备编号
设备编号包括主次编号,主编号用来告诉系统使用哪个驱动程序去驱动该设备,次编号用来指定特定的设备
以下结构用于记录设备的编号:
dev_t dev;
在32位机上dev_t为32位数据,其中12位用来记录主编号,20位用于记录次编号。
我们使用以下两个宏来从dev_t中获取主次编号:
MAJOR(dev_t dev);MINOR(dev_t dev);
反之,在已知主次编号时用以下的宏来得出dev_t:
MKDEV(int major, int minor);
分配和释放设备编号
分配设备编号有几种方法,第一种是静态分配:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first 是要分配的起始设备编号;count是要分配的设备编号总数。
通常编译到内核的设备驱动或常用的设备驱动使用静态方法,为了保证主编号的唯一性,我们自己编写驱动时推荐使用动态方法:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
firstminor为要分配的第一个次编号,通常取0;count同上
这两个函数通常在模块初始化中调用
释放设备编号:
void unregister_chrdev_region(dev_t first, unsigned int count);
通常在模块退出时调用
加载设备驱动后可以在/proc/devices中找到设备的主次编号
其他重要的数据结构
struct file_operations f_ops;
一个函数指针的集合,这些函数负责实现系统调用如open,close,read,write,ioctl等
struct cdev cdev;
系统用来记录字符设备的结构
字符设备注册与注销
方法一:
注册:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
这里其实用一个函数完成了两个功能:使用fops初始化主编号为major的字符设备并且向内核注册该设备
注销:
int unregister_chrdev(unsigned int major, const char *name);
方法二:
把方法一中提到的“两个功能”分两步完成
先调用
void cdev_init(struct cdev *cdev, struct file_operations *fops);
这一步使用fops初始化cdev
再调用
int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);
这一步向系统注册cdev
如果你想定义自己的设备结构并且在其中包含struct cdev成员,显然你只能使用这个方法
使用这个方法注册的设备要用以下函数注销:
void cdev_del(struct cdev *dev);
用户空间的访问
在f_ops中的read,write函数通常需要调用以下函数来与用户空间进行数据交互:
unsigned long copy_to_user(void __user *to, const void *from,unsigned long count);unsigned long copy_from_user(void *to, const void __user *from,unsigned long count);
为何不直接用memcpy()?
很显然to和from不在同一个地址空间,所以直接memcpy的话将会发生不可预料的事。
有了这些基础之后,就可以开始编写基本的led驱动程序了
以下是完整代码:
//leds.c#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#define DEVICE_NAME "led"#define LED_DEV_NR 4#define LED_ON 0#define LED_OFF 1/* * 笔者自己定义的表示led设备的结构,之所以写成这样是为了以后编写更复杂的设备 * 驱动时可以拿这个当模板用,到时侯可能往此结构中添加成员 */struct led_dev { struct cdev cdev;};struct led_dev *led_devs; //指向led_dev数组的指针char leds_status; //记录3个led状态的位图,灯亮对应位置位,灯灭对应位复位DECLARE_MUTEX(leds_lock); //对全局变量操作时要锁住此互斥锁,这是为了保证操作的可重入性/* * open()系统调用的实现,这里什么都不做 */static int led_open(struct inode *inode, struct file *filp){ return 0;}/* * read()系统调用的实现,功能是读取led设备的状态 */static int led_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){ int minor = MINOR(filp->f_dentry->d_inode->i_rdev); //获得目标文件对应的设备的次编号 char val; switch (minor) { case 0: // minor=0对应/dev/leds,对其进行读操作将读到3个led的状态对应的二进制值 down(&leds_lock); val = leds_status; up(&leds_lock); if (copy_to_user(buff, (const void *)&val, 1)) return -EFAULT; break; case 1: //minor=1,2,3时对应/dev/led0,1,2,对其进行读操作将读到对应led的状态(亮1灭0) down(&leds_lock); val = leds_status & 0x1; up(&leds_lock); if(copy_to_user(buff, (const void *)&val, 1)) return -EFAULT; break; case 2: down(&leds_lock); val = (leds_status >> 1) & 0x1; up(&leds_lock); if (copy_to_user(buff, (const void *)&val, 1)) return -EFAULT; break; case 3: down(&leds_lock); val = (leds_status >> 2) & 0x1; up(&leds_lock); if (copy_to_user(buff, (const void *)&val, 1)) return -EFAULT; break; } return 1;}/* * write系统调用的实现,将改变led设备的状态 */static ssize_t led_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp){ int minor = MINOR(filp->f_dentry->d_inode->i_rdev); char val; if (copy_from_user(&val, buff, 1)) return -EFAULT; switch (minor) { case 0: //对应/dev/leds,同时改变3个led的状态 down(&leds_lock); s3c2410_gpio_setpin(S3C2410_GPF4, !(val & 0x1)); s3c2410_gpio_setpin(S3C2410_GPF5, !((val >> 1) & 0x1)); s3c2410_gpio_setpin(S3C2410_GPF6, !((val >> 2) & 0x1)); leds_status = val; up(&leds_lock); //printk("led: %d\n", leds_status); break; case 1: s3c2410_gpio_setpin(S3C2410_GPF4, !val); if (val == 0) { down(&leds_lock); leds_status &= ~(1<<0); up(&leds_lock); } else { down(&leds_lock); leds_status |= (1<<0); up(&leds_lock); } break; case 2: s3c2410_gpio_setpin(S3C2410_GPF5, !val); if (val == 0) { down(&leds_lock); leds_status &= ~(1<<1); up(&leds_lock); } else { down(&leds_lock); leds_status |= (1<<1); up(&leds_lock); } break; case 3: s3c2410_gpio_setpin(S3C2410_GPF6, !val); if (val == 0) { down(&leds_lock); leds_status &= ~(1<<2); up(&leds_lock); } else { down(&leds_lock); leds_status |= (1<<2); up(&leds_lock); } break; } return 1;}/* * 模块close()时将调用的函数 */int led_release(struct inode *inode, struct file *filp){ return 0;}struct file_operations led_fops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, .open = led_open, .release = led_release,};dev_t led_dev_num; //记录起始led设备号int led_major; //记录led设备主编号/* * 模块卸载(rmmod)时将调用的函数 */static void __exit leds_cleanup_module(void){ int i; if (led_devs) { for (i = 0; i < LED_DEV_NR; i ++) { cdev_del(&led_devs[i].cdev); } kfree(led_devs); } unregister_chrdev_region(led_dev_num, LED_DEV_NR); printk(DEVICE_NAME " uninstalled.\n");}static void leds_lowlevel_init(void){ down(&leds_lock); s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP); s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP); s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP); s3c2410_gpio_setpin(S3C2410_GPF4, !0); s3c2410_gpio_setpin(S3C2410_GPF5, !0); s3c2410_gpio_setpin(S3C2410_GPF6, !0); leds_status = 0; up(&leds_lock);}static int __init leds_init_module(void){ int result; int i; struct cdev *cdev = NULL; if ((result = alloc_chrdev_region(&led_dev_num, 0, LED_DEV_NR, DEVICE_NAME)) < 0) { //分配LED_DEV_NR个设备号,起始次编号为0,起始设备号存储在led_dev_num中 printk(DEVICE_NAME " fail to get major.\n"); return result; } led_major = MAJOR(led_dev_num); led_devs = kmalloc(LED_DEV_NR * sizeof(struct led_dev), GFP_KERNEL); //分配struct led_dev结构,这是我自己定义的一个表示led设备的结构,其中包含struct cdev if (!led_devs) { printk(DEVICE_NAME " fail to allocate mem.\n"); result = -ENOMEM; goto fail; } memset(led_devs, 0, LED_DEV_NR * sizeof(struct led_dev)); leds_lowlevel_init(); //底层硬件初始化可在模块加载时完成 for (i = 0; i < LED_DEV_NR; i ++) { cdev = &led_devs[i].cdev; cdev_init(cdev, &led_fops); //初始化cdev cdev->owner = THIS_MODULE; if(cdev_add(cdev, MKDEV(led_major, i), 1)) { //注册cdev printk(DEVICE_NAME": error when adding led%d", i); result = -1000; goto fail; } } printk(DEVICE_NAME " initialized.\n"); return 0;fail: leds_cleanup_module(); //如果初始化过程中出错,务必将已经分配的资源还给系统再返回! return result;}module_init(leds_init_module);module_exit(leds_cleanup_module);MODULE_AUTHOR("ZZ");MODULE_LICENSE("Dual BSD/GPL");//end of leds.c
make生成leds.ko,将其拷贝到开发板上,接着加载之:
insmod leds.ko
接下来创建设备节点。先找到主编号:
cat /proc/device
找到上面定义的DEVICE_NAME(即”led”)对应的编号,即led设备的主编号,我这里为252。接着创建节点:
mknod /dev/leds c 252 0mknod /dev/led0 c 252 1mknod /dev/led1 c 252 2mknod /dev/led2 c 252 3
然后就可以向设备文件里读写数据测试了!
下面是我写的测试程序:
//chled.c#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc, char **argv){ int led_no; int fd; char val; char *filename = NULL; if (argc < 2) { printf("Input filename.\n"); } else { filename = argv[1]; //第一个参数是设备文件名 fd = open (filename, O_RDWR); if (fd < 0) { printf("Fail to open %s\n", filename); return 1; } if (argc == 2) { //若(除命令名外)只有1个参数,则读出设备状态 read(fd, &val, 1); printf("%d\n", val); return 0; } else if (argc == 3) { //若有2个参数则将第二个参数指定的数字写入设备 val = argv[2][0] - '0'; write(fd, &val, 1); return 0; } close(fd); } return 1;}//end of chled.c
- led驱动(混杂设备)
- LED字符设备驱动
- led字符设备驱动
- LED设备驱动
- Linux设备驱动--LED
- 字符设备驱动---Led
- linux设备驱动--LED驱动
- linux设备驱动--LED驱动
- 字符设备驱动-LED驱动
- 字符设备驱动之LED
- linux--LED设备驱动1
- 总线设备模型-LED驱动
- 字符设备驱动点亮led
- 字符设备驱动----LED驱动程序
- Linux 字符设备驱动 LED
- 字符设备驱动--LED驱动程序
- Linux字符设备驱动之LED驱动
- linux字符设备驱动-LED驱动
- 7、编程珠玑笔记七粗略估算
- 如何用unity发布Android程序APK
- HDU 5432 Pyramid Split
- findFirstMissingPositive
- 每月书单
- LED设备驱动
- 32/64位平台printf uint64的方法
- oracle的type类型
- 8、编程珠玑笔记八算法设计技术
- 关于Tomcat7无法编译jsp页面的解决方法
- RMQ算法详解
- iOS开发之有趣的UI —— 自定义不等高cell
- 数组
- lintcode-单词接龙-120