一、开发环境
主 机:fedora 14 (2.6.33.7) 开发板:FL2440(nandflash:K9F1G08 128m) 编译器:arm-linux-gcc 4.3.2
二、原理分析
结合阻塞与非阻塞访问、poll 函数可以较好地解决设备的读写,但是如果有了异步通知就更方便了。异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”地概念,比较准确的称谓是“信号驱动(SIGIO)的异步I/O”。
三、实现步骤
1. 编写按键驱动代码,取名为fl2440_keys.c
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/fs.h>
- #include <linux/wait.h>
- #include <linux/sched.h>
- #include <linux/semaphore.h>
- #include <linux/interrupt.h>
- #include <linux/irq.h>
- #include <linux/device.h>
- #include <mach/regs-gpio.h>
- #include <mach/irqs.h>
- #include <mach/gpio-nrs.h>
- #include <mach/gpio-fns.h>
- #include <asm/irq.h>
- #include <asm/uaccess.h>
- #include <asm/signal.h>
- #define DEVICE_MAJOR 0 //主设备号,0表示动态分配
- #define DEVICE_NAME "fl2440_keys" //设备名
- #define KEY_DOWN 0 //按键按下
- #define KEY_UP 1 //按键抬起
- #define KEY_UNCERTAIN 2 //按键不确定
- #define KEY_COUNT 4 //4个按键
- #define KEY_TIMER_DELAY1 (HZ/50) //按键按下去抖延时20毫秒
- #define KEY_TIMER_DELAY2 (HZ/10) //按键按下去抖延时100毫秒
- static volatile int key_status[KEY_COUNT]; //记录4个按键的状态
- static struct timer_list key_timers[KEY_COUNT]; //4个按键去抖动定时器
- static volatile int ev_press = 0; //按键按下产生标识,用于在读设备的时候来判断是否有数据可读
- static struct class *keys_class; //声明一个按键类
- static struct fasync_struct *fasync_queue; //声明一个异步队列
- static struct semaphore sem; //声明一个信号量,用于对按键状态读取的并发控制
- static spinlock_t spin = SPIN_LOCK_UNLOCKED; //声明一个自旋锁,用于对按键设备打开的并发控制
- static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //等待队列的定义并初始化
- static int major = DEVICE_MAJOR; //major用于保存动态申请成功后返回的主设备号
- static int g_count = 0; //设备打开计数,同一个时间只允许一个用户打开设备
- //硬件设备资源结构体
- struct key_irq_desc
- {
- int irq; //中断号
- int pin; //对应的IO引脚
- int pin_setting; //引脚配置
- char *name; //按键名称,注意这个名称,在后面一个现象中会出现
- };
- //定义4个按键资源结构体数组
- static struct key_irq_desc key_irqs[] =
- {
- {IRQ_EINT0,S3C2410_GPF(0),S3C2410_GPF0_EINT0,"KEY0"},
- {IRQ_EINT2,S3C2410_GPF(2),S3C2410_GPF2_EINT2,"KEY1"},
- {IRQ_EINT3,S3C2410_GPF(3),S3C2410_GPF3_EINT3,"KEY2"},
- {IRQ_EINT4,S3C2410_GPF(4),S3C2410_GPF4_EINT4,"KEY3"},
- };
- /*
- *按键按下产生中断的处理函数
- *
- *按键按下产生中断,首先判断按键的状态,如果处于抬起状态,则标志状态为不确定,
- *然后延时避免抖动,再启动按键对应的定时器,用于判断按键是否真的按下
- */
- static irqreturn_t keys_interrupt(int irq, void *dev_id)
- {
- int key = (int)dev_id;
-
- if(key_status[key] == KEY_UP)
- {
- key_status[key] = KEY_UNCERTAIN;
-
- key_timers[key].expires = jiffies + KEY_TIMER_DELAY1;
- add_timer(&key_timers[key]);
- }
- return IRQ_RETVAL(IRQ_HANDLED);
- }
- //定时器处理函数,由按键中断调用,用于判断按键是否真的按下
- static void keys_timer(unsigned long arg)
- {
- //获取当前按键资源的索引
- int key = arg;
-
- //获取当前按键上的电平值来判断按键是按下还是抬起
- int up = s3c2410_gpio_getpin(key_irqs[key].pin);
-
- if(!up) //低电平,按键按下
- {
- if(key_status[key] == KEY_UNCERTAIN)
- {
- //标识当前按键状态为按下
- key_status[key] = KEY_DOWN;
-
- //标识当前按键已按下并唤醒等待队列让设备进行读取
- ev_press = 1;
-
- //调用kill_fasync向用户释放SIGIO信号,与用户空间的signal对应
- if(fasync_queue)
- kill_fasync(&fasync_queue, SIGIO, POLL_IN);
- }
-
- //设置当前按键抬起去抖动定时器的延时并启动定时器
- key_timers[key].expires = jiffies + KEY_TIMER_DELAY2;
- add_timer(&key_timers[key]);
- }
- else //高电平,按键抬起
- {
- //标识当前按键状态为抬起
- key_status[key] = KEY_UP;
- }
-
- }
- //按键设备打开函数
- static int keys_open(struct inode *inode, struct file *filp)
- {
- int i, ret;
- //自旋锁和g_count用于确保设备不能同时打开多次
- spin_lock(&spin);
-
- //如果设备已经被打开,则释放自旋锁并返回
- if(g_count)
- {
- spin_unlock(&spin);
- return -EBUSY;
- }
- for(i=0; i<KEY_COUNT; i++)
- {
- //设置GPF0 2 3 4为中断模式
- s3c2410_gpio_cfgpin(key_irqs[i].pin, key_irqs[i].pin_setting);
-
- //设置中断类型为下降沿有效
- set_irq_type(key_irqs[i].irq, IRQ_TYPE_EDGE_FALLING);
-
- //向系统申请中断
- ret = request_irq(key_irqs[i].irq, keys_interrupt, IRQ_DISABLED,key_irqs[i].name, (void *)i);
-
- if(ret)
- {
- break;
- }
- key_status[i] = KEY_UP;
-
- //建立4个定时器
- setup_timer(&key_timers[i],keys_timer, i);
- }
-
- if(ret)
- {
- //中断申请失败处理
- i --;
-
- for(; i>=0; i--)
- {
- //释放已注册成功的中断
- disable_irq(key_irqs[i].irq);
- free_irq(key_irqs[i].irq, (void *)i);
- }
- spin_unlock(&spin);
- return -EBUSY;
- }
-
- g_count ++;
- spin_unlock(&spin);
-
- return 0;
- }
- //按键设备读取函数
- static int keys_read(struct file *file, char __user *buf, size_t count, loff_t *offp)
- {
- int ret;
- //如果ev_press未置1,即按键未按下,则返回
- if(!ev_press)
- {
- return -1;
- }
- if(down_interruptible(&sem))
- {
- return -ERESTARTSYS;
- }
-
- //将内核中的按键状态数据拷贝到用户空间给应用程序使用
- ret = copy_to_user(buf, (void *)key_status, min(sizeof(key_status),count));
-
- ev_press = 0;
- up(&sem);
-
- return ret?-EFAULT:min(sizeof(key_status),count);
-
- up(&sem);
-
- }
- //按键设备异步函数
- static int keys_fasync(int fd, struct file *filp, int on)
- {
- int ret;
-
- //处理FASYNC标志变更的
- ret = fasync_helper(fd, filp, on, &fasync_queue);
-
- if(ret < 0)
- return ret;
-
- return 0;
-
- }
- static int keys_release(struct inode *inode, struct file *filp)
- {
- int i;
-
- //释放4个定时器和中断
- for(i=0; i<KEY_COUNT; i++)
- {
- del_timer(&key_timers[i]);
-
- disable_irq(key_irqs[i].irq);
- free_irq(key_irqs[i].irq, (void *)i);
- }
- spin_lock(&spin);
g_count --; spin_unlock(&spin);
-
- keys_fasync(-1, filp, 0);
- return 0;
- }
- static struct file_operations key_fops =
- {
- .owner = THIS_MODULE,
- .open = keys_open,
- .release = keys_release,
- .read = keys_read,
- .fasync = keys_fasync,
- };
- static int __init keys_init(void)
- {
- //动态注册设备,返回主设备号
- major = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &key_fops);
- if(major<0)
- {
- printk("fl2440_keys register failed!\n");
- return major;
- }
-
- printk("fl2440_keys register sucess!\n");
- init_MUTEX(&sem);
-
- //创建类设备
- keys_class = class_create(THIS_MODULE, DEVICE_NAME);
- if(IS_ERR(keys_class))
- {
- printk(DEVICE_NAME "register class failed!");
- return -1;
- }
- //根据类创建设备,即模块加载的时候就会自动在/dev创建设备节点
- device_create(keys_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
-
- return major;
- }
- static void __exit keys_exit(void)
- {
- device_destroy(keys_class, MKDEV(major, 0)); //销毁类设备
-
- class_destroy(keys_class); //销毁类
-
- unregister_chrdev(major, DEVICE_NAME); //注销设备
-
- }
- module_init(keys_init);
- module_exit(keys_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("y.q.yang");
- MODULE_DESCRIPTION("a fasync driver for fl2440 keys");
四、将按键驱动部署到内核中
1. 修改相应配置文件。
- #cp -f fl2440_keys.c drivers/char //把驱动源码复制到内核驱动的字符设备下
- #vim drivers/char/Kconfig //添加按键设备配置
- config FL2440_KEYS
- tristate "FL2440 Keys Device"
- depends on ARCH_S3C2440
- default y
- ---help---
- Fl2440 User Keys
- #gedit drivers/char/Makefile //添加按键设备配置
- obj-$(CONFIG_FL2440_KEYS) += fl2440_keys.o
2. 配置内核,选择按键设备选项
- #make menuconfig
- Device Drivers --->
- Character devices --->
- <*> FL2440 Keys Device (NEW)
3. 编译内核并下载到开发板上,启动的时候可以看到按键设备注册成功,
- io scheduler noop registered
- io scheduler deadline registered
- io scheduler cfq registered (default)
- Console: switching to colour frame buffer device 80x40
- fb0: s3c2410fb frame buffer device
- adc initialized
- register sucessed, pwm major is 253
- fl2440_keys register
不过并没有自动生成/dev下的设备节点,可能是我的文件系统做的不对。
- [root@yyq2440 /]# ls dev
- console null
查看已加载的设备:#cat /proc/devices,可以看到fl2440_keys的主设备号为252
- 136 pts
- 180 usb
- 188 ttyUSB
- 189 usb_device
- 204 s3c2410_serial
- 230 fl2440_backlight
- 231 fl2440_leds
- 252 fl2440_keys
- 253 fl2440_pwm
- 254 rtc
- Block devices:
4. 编写用户空间测试程序,名为key_test.c
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <unistd.h>
- static int fd;
- int key_status[4];
- void key_handler(int num)
- {
- int i;
- int ret;
-
- ret = read(fd, &key_status, sizeof(key_status));
-
- if(ret != sizeof(key_status))
- {
- if(errno != EAGAIN)
- {
- printf("Read Button Device Faild!\n");
-
- return ;
- }
- }
- else
- {
- for(i=0; i<4; i++)
- {
- if(key_status[i] == 0)
- {
- printf("Key%d Down\n", i+1);
- }
- }
- }
-
- }
- void main()
- {
- int oflags;
- //打开设备
- fd = open("/dev/fl2440_key", O_RDWR, S_IRUSR | S_IWUSR);
- //启动信号驱动机制
- signal(SIGIO, key_handler);
- //设置信号所要发送的进程,getpid()获得当前进程号
- fcntl(fd, F_SETOWN, getpid());
- //获得文件状态标记
- oflags = fcntl(fd, F_GETFL);
- //设置打开的文件为异步操作模式
- fcntl(fd, F_SETFL, oflags | FASYNC);
- //最后进入一个死循环,程序什么都不干了,只有信号能激发input_handler 的运行
- //如果程序中没有这个死循环,会立即执行完毕
- while (1);
- }
5. 在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上
- #arm-linux-gcc -o buttons_test buttons_test.c
6. 在开发板上的文件系统中创建一个按键设备的节点,然后运行测试程序,效果图如下,观测按开发板上的按键时,在串口工具中会输出对应按键被按下的信息,也不会出现抖动现象(即按某个按键时,不会多次产生该按键按下的情况)
- [root@yyq2440 /]# mknod /dev/fl2440_keys c 252 0
- [root@yyq2440 /]# key_test
- Key1 Down
- Key2 Down
- Key1 Down
- Key3 Down
- Key4 Down
- Key1 Down
- Key2 Down
- Key3 Down
- Key4 Down
- Key4 Down
- Key3 Down
- Key2 Down
- Key1 Down
即可看到异步通知驱动正常工作,跟轮询方式的驱动一样。
2011-03-04
|