本文共 4547 字,大约阅读时间需要 15 分钟。
驱动篇:字符设备驱动综合实例(一)
1.按键的设备驱动
在嵌入式系统中,按键的硬件原理比较简单,通过一个上拉电阻将处理器的外部中断(或 GPIO)引脚拉高,电阻的另一端连接按钮并接地即可实现。如图 12.1 所示,当按钮被按下时,EINT10、EIN13、EINT14、EINT15 上将产生低电平,这个低电平将中断 CPU(图中的 CPU 为 S3C2410),CPU 可以依据中断判断按键被按下。 如果按键对应的引脚本身不具备中断输入功能,则可以改为完全查询方式如图b设备驱动中主要要设计的数据结构是设备结构体,按键的设备结构体中应包含一个缓冲区,因为多次按键可能无法被及时处理,可以用该缓冲区缓存按键。此外,在按键设备结构体中,还包含按键状态标志和一个实现过程中要借助的等待队列、cdev结构体。为了实现软件延时,定时器也是必要的,但可以不包含在设备结构体中。
#define MAX_KEY_BUF 16 //按键缓冲区大小typedef unsigned char KEY_RET;//设备结构体:typedef struct{ unsigned int keyStatus[KEY_NUM]; //4 个按键的按键状态KEY_RET buf[MAX_KEY_BUF]; //按键缓冲区unsigned int head, tail; //按键缓冲区头和尾wait_queue_head_t wq; //等待队列struct cdev cdev;//cdev 结构体} KEY_DEV;static struct timer_list key_timer[KEY_NUM];//4 个按键去抖定时器
在按键设备驱动中,可用一个结构体记录每个按键所对应的中断/GPIO 引脚及键值
static struct key_info{ int irq_no;//中断号unsigned int gpio_port; //GPIO 端口int key_no;//键值} key_info_tab[4] ={ /*按键所使用的 CPU 资源*/{ IRQ_EINT10, GPIO_G2, 1},{ IRQ_EINT13, GPIO_G5, 2},{ IRQ_EINT14, GPIO_G6, 3},{ IRQ_EINT15, GPIO_G7, 4},};
按键设备驱动文件操作结构体主要实现了打开、释放和读函数,因为按键只是一个输入设备,所以不存在写函数
static struct file_operations s3c2410_key_fops = { owner: THIS_MODULE, open: s3c2410_key_open, //启动设备 release: s3c2410_key_release, //关闭设备 read: s3c2410_key_read, //读取按键的键值 };
按键设备作为一种字符设备,在其模块加载和卸载函数中分别包含了设备号申请和释放、cdev 的添加和删除行为,在模块加载函数中,还需申请中断、初始化定时器和等待队列等,模块卸载函数完成相反的行为
模块加载函数
static int __init s3c2410_key_init(void) { ...//申请设备号,添加 cdevrequest_irqs(); //注册中断函数keydev.head = keydev.tail = 0; //初始化结构体for (i = 0; i < KEY_NUM; i++)keydev.keyStatus[i] = KEYSTATUS_UP;init_waitqueue_head(&(keydev.wq)); //等待队列//初始化定时器,实现软件的去抖动 for (i = 0; i < KEY_NUM; i++)setup_timer(&key_timer[i], key_timer_handler, i); //把按键的序号作为传入定时器处理函数的参数 }
模块卸载函数
static void __exit s3c2410_key_exit(void) { free_irqs(); //注销中断 ...//释放设备号,删除cdev }
按键设备驱动的中断申请函数
/*申请系统中断,中断方式为下降沿触发*/ static int request_irqs(void) { struct key_info *k; int i; for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++) { k = key_info_tab + i; set_external_irq(k->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS); //设置低电平触发 if (request_irq(k->irq_no, &buttons_irq, SA_INTERRUPT,DEVICE_NAME, i)) //申请中断,将按键序号作为参数传入中断服务程序 { return - 1; } } return 0; }
中断释放函数
/*释放中断*/static void free_irqs(void){ struct key_info *k;int i;for (i = 0; i < sizeof(key_info_tab) / sizeof(key_info_tab[1]); i++){ k = key_info_tab + i;free_irq(k->irq_no, buttons_irq); //释放中断}}
在键被按下后,将发生中断,在中断处理程序中,应该关闭中断进入查询模式,延迟 20ms 以实现去抖动这个中断处理过程只包含顶半部,无底半部。
static void s3c2410_eint_key(int irq, void *dev_id, struct pt_regs *reg) { int key = dev_id; disable_irq(key_info_tab[key].irq_no); //关中断,转入查询模式 keydev.keyStatus[key] = KEYSTATUS_DOWNX;//状态为按下 key_timer[key].expires == jiffies + KEY_TIMER_DELAY1;//延迟 add_timer(&key_timer[key]); //启动定时器 }
在定时器处理程序中,查询按键是否仍然被按下,如果是被按下的状态,则将该按键记录入缓冲区。同时启动新的定时器延迟,延迟一个相对于去抖更长的时间(如100ms),每次定时器到期后,查询按键是否仍然处于按下状态,如果是,则重新启用新的 100ms 延迟;若查询到已经没有按下,则认定键已抬起,这个时候应该开启对应按键的中断,等待新的按键。每次记录新的键值时,应唤醒等待队列。
定时器处理函数static void key_timer_handler(unsigned long data) { int key = data;if (ISKEY_DOWN(key)){ if (keydev.keyStatus[key] == KEYSTATUS_DOWNX)//从中断进入{ keydev.keyStatus[key] = KEYSTATUS_DOWN;key_timer[key].expires == jiffies + KEY_TIMER_DELAY; //延迟keyEvent(); //记录键值,唤醒等待队列add_timer(&key_timer[key]);}else{ key_timer[key].expires == jiffies + KEY_TIMER_DELAY; //延迟add_timer(&key_timer[key]);} } else//键已抬起 { keydev.keyStatus[key] = KEYSTATUS_UP;enable_irq(key_info_tab[key].irq_no); } }
按键设备驱动的打开和释放函数比较简单,主要是设置 keydev.head、keydev.tail和按键事件函数指针 keyEvent 的值
static int s3c2410_key_open(struct inode *inode, struct file *filp){ keydev.head = keydev.tail = 0; //清空按键动作缓冲区keyEvent = keyEvent_raw; //函数指针指向按键处理函数 keyEvent_rawreturn 0;}static int s3c2410_key_release(struct inode *inode, struct file{ keyEvent = keyEvent_dummy; //函数指针指向空函数return 0;}
按键设备驱动的读函数主要提供对按键设备结构体中缓冲区的读并复制到用户空间。当keydev.head ! = keydev.tail 时,意味着缓冲区有数据,使用 copy_to_user()拷贝到用户空间,否则,根据用户空间是阻塞读还是非阻塞读,分为如下两种情况。
l 若采用非阻塞读,则因为没有按键缓存,直接返回- EAGAIN; l 若采用阻塞读,则在 keydev.wq 等待队列上睡眠,直到有按键被记录入缓冲区后被唤醒。 驱动读函数static ssize_t s3c2410_key_read(struct file *filp, char *buf, ssize_t count,loff_t*ppos){ retry: if (keydev.head != keydev.tail)//当前循环队列中有数据{ key_ret = keyRead(); //读取按键copy_to_user(..); //把数据从内核空间传送到用户空间} else { if (filp->f_flags &O_NONBLOCK)//若用户采用非阻塞方式读取{ return - EAGAIN;}interruptible_sleep_on(&(keydev.wq));//用户采用阻塞方式读取,调用该函数使进程睡眠goto retry; } return 0; }
在设备驱动的打开函数中,keyEvent 被赋值为 keyEvent_raw,这个函数完成记录键值, 并使用 wait_up_interrupt(&(keydev.wq))语句唤醒 s3c2410_key_read()第 17 行所期待的等待队列。而 keyRead()函数则直接从按键缓冲区中读取键值。
转载地址:https://blog.csdn.net/zytgg123456/article/details/110086508 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!