/** 代码演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定义信号量 */struct semaphore btn_sem;/* 定义等待队列头 */wait_queue_head_t btn_wqh;int ev_PRess = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0char key_buf; // 按键缓冲区 == 1个字节/* 中断数据结构 */typedef struct btn_desc {char key_val; // 键值int irq; // 中断号char* name; // 名称} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, "Key3"},{0x40, IRQ_GPIO_B_START + 9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 获取信号量 */// 1. 获取信号量成功,返回0// 2. 被信号打断,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 释放信号量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有数据供用户空间读,返回数据给用户空间 无数据供用户空间读,调用者进程进入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 读走缓冲数据后的清0标记return len; // 读取成功的字节个数}struct file_Operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read, // 新增};/* 中断处理函数 */irqreturn_t btn_isr (int irq, void* dev){btn_desc_t* pdata = (btn_desc_t*)dev;/* 保存按键值 */key_buf = pdata->key_val;ev_press = 1;/* 唤醒因按键值不足而睡眠的进程 */wake_up_interruptible (&btn_wqh);return IRQ_HANDLED;}int __init btn_drv_init (void){int i = 0;/* 1.申请设备号 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数/* 3.注册cdev */cdev_add (&btn_cdev, dev, 1);/* 4.创建设备文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "btns");/* 初始化信号量 - 只允许1个持有者 */sema_init (&btn_sem, 1);/* 初始化等待队列头 */init_waitqueue_head (&btn_wqh);/* 注册中断处理函数 - 可以处理4个按键中断事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}}return 0;}void __exit btn_drv_exit (void){int i = 0;/* 注销中断处理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));}/* 销毁设备文件 */device_destroy (cls, dev);class_destroy (cls);/* 注销cdev */cdev_del (&btn_cdev);/* 释放设备号 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 测试代码 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;fd = open ("/dev/btns", O_RDWR);if (fd < 0) {perror ("open /dev/btns failed");return -1;}printf ("open /dev/btns success.../n");/* 新增 */ while (1) {read (fd, &key, sizeof (char));printf ("user space key = %#x/n", key);}close (fd);return 0;}2、延时去抖 按键抖动的延时间隔,可以用'示波器'抓波形得出。 如果小于10ms,在定时器设置就延时10ms; 假如抓出15ms,在定时器设置就延时20ms,以此类推。/** 代码演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定义信号量 */struct semaphore btn_sem;/* 定义等待队列头 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0char key_buf; // 按键缓冲区 == 1个字节/* 定义定时器变量 */struct timer_list btn_timer;/* 中断数据结构 */typedef struct btn_desc {char key_val; // 键值int irq; // 中断号char* name; // 名称} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, "Key3"},{0x40, IRQ_GPIO_B_START + 9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 获取信号量 */// 1. 获取信号量成功,返回0// 2. 被信号打断,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 释放信号量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有数据供用户空间读,返回数据给用户空间 无数据供用户空间读,调用者进程进入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 读走缓冲数据后的清0标记return len; // 读取成功的字节个数}struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中断处理函数 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){btn_desc_t* pdata = (btn_desc_t*)data; // 这里写成dev了/* 保存按键值 */key_buf = pdata->key_val;ev_press = 1;/* 唤醒因按键值不足而睡眠的进程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申请设备号 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数/* 3.注册cdev */cdev_add (&btn_cdev, dev, 1);/* 4.创建设备文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信号量 - 只允许1个持有者 */sema_init (&btn_sem, 1);/* 初始化等待队列头 */init_waitqueue_head (&btn_wqh);/* 注册中断处理函数 - 可以处理4个按键中断事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注销中断处理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));}/* 销毁设备文件 */device_destroy (cls, dev);class_destroy (cls);/* 注销cdev */cdev_del (&btn_cdev);/* 释放设备号 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 测试代码 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;fd = open ("/dev/buttons", O_RDWR);if (fd < 0) {perror ("open /dev/buttons failed");return -1;}printf ("open /dev/btns success.../n");/* 新增 */ while (1) {read (fd, &key, sizeof (char));printf ("user space key = %#x/n", key);}close (fd);return 0;}3、按下和释放去抖触发中断 'IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING' request_irq () // 可以指定双沿触发 保存按键前要判断按下触发还是释放触发 通过管脚状态来判断。/** 代码演示 **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>#include <linux/gpio.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定义信号量 */struct semaphore btn_sem;/* 定义等待队列头 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0char key_buf; // 按键缓冲区 == 1个字节/* 定义定时器变量 */struct timer_list btn_timer;/* 中断数据结构 */typedef struct btn_desc {char key_val; // 键值int irq; // 中断号int gpio; // 管脚编号char* name; // 名称} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},{0x40, IRQ_GPIO_B_START + 9, PAD_GPIO_B + 9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 获取信号量 */// 1. 获取信号量成功,返回0// 2. 被信号打断,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 释放信号量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有数据供用户空间读,返回数据给用户空间 无数据供用户空间读,调用者进程进入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 读走缓冲数据后的清0标记return len; // 读取成功的字节个数}struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中断处理函数 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){int stat = 0;/* 确定是哪个按键触发的 */btn_desc_t* pdata = (btn_desc_t*)data;/* 根据管脚电平状态,判断是按下触发?释放触发? */stat = gpio_get_value (pdata->gpio); // 获取管脚上的电平状态:0低非0高/* 保存按键值 stat==100 ---> !stat==0 ---> !!stat==1 */key_buf = pdata->key_val + !!stat;ev_press = 1;/* 唤醒因按键值不足而睡眠的进程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申请设备号 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数/* 3.注册cdev */cdev_add (&btn_cdev, dev, 1);/* 4.创建设备文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信号量 - 只允许1个持有者 */sema_init (&btn_sem, 1);/* 初始化等待队列头 */init_waitqueue_head (&btn_wqh);/* 注册中断处理函数 - 可以处理4个按键中断事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}/* GPIO管脚的申请 */gpio_request (buttons[i].gpio, buttons[i].name);}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注销中断处理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));gpio_free (buttons[i].gpio);}/* 销毁设备文件 */device_destroy (cls, dev);class_destroy (cls);/* 注销cdev */cdev_del (&btn_cdev);/* 释放设备号 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);4、用户态对设备的非阻塞方式访问 实际上,应用程序并不关心驱动里面read/write具体实现,只管调用并获取返回值。 如果设备没有准备好数据给应用程序读或者没有准备好接收用户程序写,驱动程序应当阻塞进程,使它进入睡眠,直到请求可以得到满足。 open ("/dev/xxx", O_RDWR);// 默认是阻塞方式访问 open ("/dev/xxx", O_RDWR |O_NONBLOCK); // 非阻塞方式进行访问。 struct file { // open函数的系统调用从f_op指针指向了file_operations里面的open函数 const struct file_operations *f_op; // 记录了open(..., flag)时flag的绝大多数信息 unsigned int f_flags; }; '简言之:f_flags 记录了文件open时的打开方式。' #ifndef O_NONBLOCK #define O_NONBLOCK 00004000 #endif/** 代码演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>#include <linux/gpio.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定义信号量 */struct semaphore btn_sem;/* 定义等待队列头 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0char key_buf; // 按键缓冲区 == 1个字节/* 定义定时器变量 */struct timer_list btn_timer;/* 中断数据结构 */typedef struct btn_desc {char key_val; // 键值int irq; // 中断号int gpio; // 管脚编号char* name; // 名称} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},{0x40, IRQ_GPIO_B_START + 9, PAD_GPIO_B + 9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 获取信号量 */// 1. 获取信号量成功,返回0// 2. 被信号打断,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 释放信号量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 判断open文件时是否加了O_NONBLOCK的打开方式 */if ( (filp->f_flags & O_NONBLOCK) && ev_press == 0)return -EAGAIN;/* 有数据供用户空间读,返回数据给用户空间 无数据供用户空间读,调用者进程进入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 读走缓冲数据后的清0标记return len; // 读取成功的字节个数}struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中断处理函数 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){int stat = 0;/* 确定是哪个按键触发的 */btn_desc_t* pdata = (btn_desc_t*)data;/* 根据管脚电平状态,判断是按下触发?释放触发? */stat = gpio_get_value (pdata->gpio); // 获取管脚上的电平状态:0低非0高/* 保存按键值 stat==100 ---> !stat==0 ---> !!stat==1 */key_buf = pdata->key_val + !!stat;ev_press = 1;/* 唤醒因按键值不足而睡眠的进程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申请设备号 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数/* 3.注册cdev */cdev_add (&btn_cdev, dev, 1);/* 4.创建设备文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信号量 - 只允许1个持有者 */sema_init (&btn_sem, 1);/* 初始化等待队列头 */init_waitqueue_head (&btn_wqh);/* 注册中断处理函数 - 可以处理4个按键中断事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}/* GPIO管脚的申请 */gpio_request (buttons[i].gpio, buttons[i].name);}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注销中断处理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));gpio_free (buttons[i].gpio);}/* 销毁设备文件 */device_destroy (cls, dev);class_destroy (cls);/* 注销cdev */cdev_del (&btn_cdev);/* 释放设备号 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 测试代码 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;/* 非阻塞方式+可读可写方式打开 */fd = open ("/dev/buttons", O_RDWR | O_NONBLOCK);if (fd < 0) {perror ("open /dev/buttons failed");return -1;}printf ("open /dev/btns success.../n");while (1) {if (read (fd, &key, sizeof (char)) > 0)printf ("user space key = %#x/n", key);elseperror ("read failed");sleep (3);}close (fd);return 0;}
新闻热点
疑难解答