免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1328 | 回复: 0

按键驱动(异步通知方式)在FL2440的开发(2.6.33.7内核) [复制链接]

论坛徽章:
0
发表于 2011-12-20 09:44 |显示全部楼层
一、开发环境
    主   机:fedora 14 (2.6.33.7)
    开发板:FL2440(nandflash:K9F1G08 128m)
    编译器:arm-linux-gcc 4.3.2
 
二、原理分析
    具体原来见《按键驱动(轮询方式)在FL2440的开发(2.6.33.7内核)》,这里不再重复。
    结合阻塞与非阻塞访问、poll 函数可以较好地解决设备的读写,但是如果有了异步通知就更方便了。异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”地概念,比较准确的称谓是“信号驱动(SIGIO)的异步I/O”。
 
三、实现步骤
    1. 编写按键驱动代码,取名为fl2440_keys.c
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/init.h>
  4. #include <linux/fs.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/semaphore.h>
  8. #include <linux/interrupt.h>
  9. #include <linux/irq.h>
  10. #include <linux/device.h>

  11. #include <mach/regs-gpio.h>
  12. #include <mach/irqs.h>
  13. #include <mach/gpio-nrs.h>
  14. #include <mach/gpio-fns.h>

  15. #include <asm/irq.h>
  16. #include <asm/uaccess.h>
  17. #include <asm/signal.h>

  18. #define DEVICE_MAJOR    0                //主设备号,0表示动态分配
  19. #define DEVICE_NAME        "fl2440_keys"    //设备名


  20. #define KEY_DOWN        0    //按键按下
  21. #define KEY_UP            1    //按键抬起
  22. #define KEY_UNCERTAIN    2    //按键不确定
  23. #define KEY_COUNT        4    //4个按键

  24. #define KEY_TIMER_DELAY1    (HZ/50)    //按键按下去抖延时20毫秒
  25. #define KEY_TIMER_DELAY2    (HZ/10)    //按键按下去抖延时100毫秒

  26. static volatile int key_status[KEY_COUNT];        //记录4个按键的状态
  27. static struct timer_list key_timers[KEY_COUNT];    //4个按键去抖动定时器
  28. static volatile int ev_press = 0;                //按键按下产生标识,用于在读设备的时候来判断是否有数据可读

  29. static struct class *keys_class;                //声明一个按键类
  30. static struct fasync_struct *fasync_queue;        //声明一个异步队列
  31. static struct semaphore sem;                    //声明一个信号量,用于对按键状态读取的并发控制
  32. static spinlock_t spin = SPIN_LOCK_UNLOCKED;    //声明一个自旋锁,用于对按键设备打开的并发控制
  33. static DECLARE_WAIT_QUEUE_HEAD(button_waitq);    //等待队列的定义并初始化

  34. static int major = DEVICE_MAJOR;                //major用于保存动态申请成功后返回的主设备号
  35. static int g_count = 0;                            //设备打开计数,同一个时间只允许一个用户打开设备

  36. //硬件设备资源结构体
  37. struct key_irq_desc
  38. {
  39.     int irq;            //中断号
  40.     int pin;            //对应的IO引脚
  41.     int pin_setting;    //引脚配置
  42.     char *name;            //按键名称,注意这个名称,在后面一个现象中会出现
  43. };

  44. //定义4个按键资源结构体数组
  45. static struct key_irq_desc key_irqs[] =
  46. {
  47.     {IRQ_EINT0,S3C2410_GPF(0),S3C2410_GPF0_EINT0,"KEY0"},
  48.     {IRQ_EINT2,S3C2410_GPF(2),S3C2410_GPF2_EINT2,"KEY1"},
  49.     {IRQ_EINT3,S3C2410_GPF(3),S3C2410_GPF3_EINT3,"KEY2"},
  50.     {IRQ_EINT4,S3C2410_GPF(4),S3C2410_GPF4_EINT4,"KEY3"},
  51. };

  52. /*
  53.  *按键按下产生中断的处理函数
  54.  *
  55.  *按键按下产生中断,首先判断按键的状态,如果处于抬起状态,则标志状态为不确定,
  56.  *然后延时避免抖动,再启动按键对应的定时器,用于判断按键是否真的按下
  57.  */
  58. static irqreturn_t keys_interrupt(int irq, void *dev_id)
  59. {
  60.     int key = (int)dev_id;
  61.     
  62.     if(key_status[key] == KEY_UP)
  63.     {
  64.         key_status[key] = KEY_UNCERTAIN;
  65.         
  66.         key_timers[key].expires = jiffies + KEY_TIMER_DELAY1;
  67.         add_timer(&key_timers[key]);
  68.     }    
  69.     return IRQ_RETVAL(IRQ_HANDLED);
  70. }

  71. //定时器处理函数,由按键中断调用,用于判断按键是否真的按下
  72. static void keys_timer(unsigned long arg)
  73. {
  74.     //获取当前按键资源的索引
  75.     int key = arg;
  76.     
  77.     //获取当前按键上的电平值来判断按键是按下还是抬起
  78.     int up = s3c2410_gpio_getpin(key_irqs[key].pin);
  79.     
  80.     if(!up)    //低电平,按键按下
  81.     {
  82.         if(key_status[key] == KEY_UNCERTAIN)
  83.         {
  84.             //标识当前按键状态为按下
  85.             key_status[key] = KEY_DOWN;
  86.             
  87.             //标识当前按键已按下并唤醒等待队列让设备进行读取
  88.             ev_press = 1;
  89.             
  90.             //调用kill_fasync向用户释放SIGIO信号,与用户空间的signal对应
  91.             if(fasync_queue)
  92.                 kill_fasync(&fasync_queue, SIGIO, POLL_IN);
  93.         }
  94.         
  95.         //设置当前按键抬起去抖动定时器的延时并启动定时器
  96.         key_timers[key].expires = jiffies + KEY_TIMER_DELAY2;
  97.         add_timer(&key_timers[key]);
  98.     }
  99.     else    //高电平,按键抬起
  100.     {
  101.         //标识当前按键状态为抬起
  102.         key_status[key] = KEY_UP;
  103.     }    
  104.     
  105. }

  106. //按键设备打开函数
  107. static int keys_open(struct inode *inode, struct file *filp)
  108. {
  109.     int i, ret;

  110.     //自旋锁和g_count用于确保设备不能同时打开多次
  111.     spin_lock(&spin);
  112.     
  113.     //如果设备已经被打开,则释放自旋锁并返回
  114.     if(g_count)
  115.     {
  116.         spin_unlock(&spin);
  117.         return -EBUSY;
  118.     }

  119.     for(i=0; i<KEY_COUNT; i++)
  120.     {
  121.         //设置GPF0 2 3 4为中断模式
  122.         s3c2410_gpio_cfgpin(key_irqs[i].pin, key_irqs[i].pin_setting);
  123.         
  124.         //设置中断类型为下降沿有效
  125.         set_irq_type(key_irqs[i].irq, IRQ_TYPE_EDGE_FALLING);
  126.         
  127.         //向系统申请中断
  128.         ret = request_irq(key_irqs[i].irq, keys_interrupt, IRQ_DISABLED,key_irqs[i].name, (void *)i);
  129.         
  130.         if(ret)
  131.         {
  132.             break;
  133.         }
  134.         key_status[i] = KEY_UP;
  135.         
  136.         //建立4个定时器
  137.         setup_timer(&key_timers[i],keys_timer, i);    
  138.     }
  139.     
  140.     if(ret)
  141.     {
  142.         //中断申请失败处理
  143.         i --;
  144.         
  145.         for(; i>=0; i--)
  146.         {
  147.             //释放已注册成功的中断
  148.             disable_irq(key_irqs[i].irq);
  149.             free_irq(key_irqs[i].irq, (void *)i);
  150.         }
  151.         spin_unlock(&spin);    
  152.         return -EBUSY;
  153.     }
  154.     
  155.     g_count ++;
  156.     spin_unlock(&spin);
  157.     
  158.     return 0;
  159. }

  160. //按键设备读取函数
  161. static int keys_read(struct file *file, char __user *buf, size_t count, loff_t *offp)
  162. {
  163.     int ret;

  164.     //如果ev_press未置1,即按键未按下,则返回
  165.     if(!ev_press)
  166.     {
  167.         return -1;
  168.     }
  169.     if(down_interruptible(&sem))
  170.     {
  171.         return -ERESTARTSYS;
  172.     }
  173.     
  174.     //将内核中的按键状态数据拷贝到用户空间给应用程序使用
  175.     ret = copy_to_user(buf, (void *)key_status, min(sizeof(key_status),count));
  176.     
  177.     ev_press = 0;
  178.     up(&sem);
  179.     
  180.     return ret?-EFAULT:min(sizeof(key_status),count);    
  181.         
  182.     up(&sem);
  183.         
  184. }

  185. //按键设备异步函数
  186. static int keys_fasync(int fd, struct file *filp, int on)
  187. {
  188.     int ret;
  189.     
  190.     //处理FASYNC标志变更的
  191.     ret = fasync_helper(fd, filp, on, &fasync_queue);
  192.     
  193.     if(ret < 0)
  194.         return ret;
  195.         
  196.     return 0;
  197.     
  198. }

  199. static int keys_release(struct inode *inode, struct file *filp)
  200. {
  201.     int i;
  202.     
  203.     //释放4个定时器和中断
  204.     for(i=0; i<KEY_COUNT; i++)
  205.     {
  206.         del_timer(&key_timers[i]);
  207.         
  208.         disable_irq(key_irqs[i].irq);
  209.         free_irq(key_irqs[i].irq, (void *)i);
  210.     }
  211.     spin_lock(&spin);
        g_count --;
        spin_unlock(&spin);
  212.     
  213.     keys_fasync(-1, filp, 0);
  214.     return 0;
  215. }

  216. static struct file_operations key_fops =
  217. {
  218.     .owner        = THIS_MODULE,
  219.     .open         = keys_open,
  220.     .release    = keys_release,
  221.     .read        = keys_read,
  222.     .fasync        = keys_fasync,
  223. };


  224. static int __init keys_init(void)
  225. {    
  226.     //动态注册设备,返回主设备号
  227.     major = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &key_fops);
  228.     if(major<0)
  229.     {
  230.         printk("fl2440_keys register failed!\n");
  231.         return major;    
  232.     }
  233.     
  234.     printk("fl2440_keys register sucess!\n");
  235.     init_MUTEX(&sem);
  236.     
  237.     //创建类设备
  238.     keys_class = class_create(THIS_MODULE, DEVICE_NAME);
  239.     if(IS_ERR(keys_class))
  240.     {
  241.         printk(DEVICE_NAME "register class failed!");
  242.         return -1;
  243.     }
  244.     //根据类创建设备,即模块加载的时候就会自动在/dev创建设备节点
  245.     device_create(keys_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
  246.     
  247.     return major;    
  248. }

  249. static void __exit keys_exit(void)
  250. {
  251.     device_destroy(keys_class, MKDEV(major, 0));    //销毁类设备
  252.     
  253.     class_destroy(keys_class);                        //销毁类
  254.     
  255.     unregister_chrdev(major, DEVICE_NAME);            //注销设备
  256.     
  257. }

  258. module_init(keys_init);
  259. module_exit(keys_exit);

  260. MODULE_LICENSE("GPL");
  261. MODULE_AUTHOR("y.q.yang");
  262. MODULE_DESCRIPTION("a fasync driver for fl2440 keys");

四、将按键驱动部署到内核中
    1. 修改相应配置文件。
  1. #cp -f fl2440_keys.c drivers/char //把驱动源码复制到内核驱动的字符设备下

  2. #vim drivers/char/Kconfig //添加按键设备配置
  3. config FL2440_KEYS
  4.     tristate "FL2440 Keys Device"
  5.     depends on ARCH_S3C2440
  6.     default y
  7.     ---help---
  8.       Fl2440 User Keys

  9. #gedit drivers/char/Makefile //添加按键设备配置
  10. obj-$(CONFIG_FL2440_KEYS) += fl2440_keys.o
    2. 配置内核,选择按键设备选项
  1. #make menuconfig
  2. Device Drivers --->
  3.     Character devices --->
  4.         <*> FL2440 Keys Device (NEW)
     3. 编译内核并下载到开发板上,启动的时候可以看到按键设备注册成功,
  1. io scheduler noop registered
  2. io scheduler deadline registered
  3. io scheduler cfq registered (default)
  4. Console: switching to colour frame buffer device 80x40
  5. fb0: s3c2410fb frame buffer device
  6. adc    initialized
  7. register sucessed, pwm major is 253
  8. fl2440_keys register
    不过并没有自动生成/dev下的设备节点,可能是我的文件系统做的不对。
  1. [root@yyq2440 /]# ls dev
  2. console null
    查看已加载的设备:#cat /proc/devices,可以看到fl2440_keys的主设备号为252
  1. 136 pts
  2. 180 usb
  3. 188 ttyUSB
  4. 189 usb_device
  5. 204 s3c2410_serial
  6. 230 fl2440_backlight
  7. 231 fl2440_leds
  8. 252 fl2440_keys
  9. 253 fl2440_pwm
  10. 254 rtc

  11. Block devices:
     4. 编写用户空间测试程序,名为key_test.c
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <signal.h>
  6. #include <unistd.h>

  7. static int fd;
  8. int key_status[4];

  9. void key_handler(int num)
  10. {
  11.     int i;
  12.     int ret;
  13.     
  14.     ret = read(fd, &key_status, sizeof(key_status));
  15.     
  16.     if(ret != sizeof(key_status))
  17.     {
  18.         if(errno != EAGAIN)
  19.         {
  20.             printf("Read Button Device Faild!\n");
  21.             
  22.             return ;
  23.         }
  24.     }
  25.     else
  26.     {
  27.         for(i=0; i<4; i++)
  28.         {
  29.             if(key_status[i] == 0)
  30.             {
  31.                 printf("Key%d Down\n", i+1);
  32.             }
  33.         }
  34.     }
  35.     
  36. }

  37. void main()
  38. {
  39.     int oflags;
  40.     //打开设备
  41.     fd = open("/dev/fl2440_key", O_RDWR, S_IRUSR | S_IWUSR);
  42.     //启动信号驱动机制
  43.     signal(SIGIO, key_handler);
  44.     //设置信号所要发送的进程,getpid()获得当前进程号
  45.     fcntl(fd, F_SETOWN, getpid());
  46.     //获得文件状态标记
  47.     oflags = fcntl(fd, F_GETFL);
  48.     //设置打开的文件为异步操作模式
  49.     fcntl(fd, F_SETFL, oflags | FASYNC);
  50.     //最后进入一个死循环,程序什么都不干了,只有信号能激发input_handler 的运行
  51.     //如果程序中没有这个死循环,会立即执行完毕
  52.     while (1);
  53. }
    5. 在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上
  1. #arm-linux-gcc -o buttons_test buttons_test.c
     6. 在开发板上的文件系统中创建一个按键设备的节点,然后运行测试程序,效果图如下,观测按开发板上的按键时,在串口工具中会输出对应按键被按下的信息,也不会出现抖动现象(即按某个按键时,不会多次产生该按键按下的情况)
  1. [root@yyq2440 /]# mknod /dev/fl2440_keys c 252 0
  2. [root@yyq2440 /]# key_test
  3. Key1 Down
  4. Key2 Down
  5. Key1 Down
  6. Key3 Down
  7. Key4 Down
  8. Key1 Down
  9. Key2 Down
  10. Key3 Down
  11. Key4 Down
  12. Key4 Down
  13. Key3 Down
  14. Key2 Down
  15. Key1 Down
    即可看到异步通知驱动正常工作,跟轮询方式的驱动一样。


2011-03-04


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP