本帖最后由 zxl_zxl 于 2016-7-11 11:16 编辑
【创龙AM4379 Cortex-A9试用体验】之I/O中断异步通知驱动程序+QT捕获Linux系统信号+测试信号通知
之前在写过一篇按键中断+去抖的试用报告,那篇报告只是在中断处理程序中做了LED灯亮灭操作,而没有将中断信息通知到用户层应用程序中,如果用户应用程序需要获取底层的I/O状态时,还必须通过轮询或阻塞的方式读取,显然,这种读取I/O状态信息的方式,效率非常低下,浪费了大量的系统执行时间。
这篇试用报告,我们主要介绍GPIO外部中断的驱动程序开发,在内核的中断处理程序中完按键的“按下”、“松开”判断,并将按键状态信息通知应用层。本报告应用层采用QT图形界面的形式展示,当按键被按下时,QT界面上对应的“复选框”被选中,当按键被松开时,QT界面上对应的“复选框”勾选消失。
1. 搭建硬件
查看TL-4379
开发板的硬件手册,结合原理图,我们选择GPIO3-14,GPIO3-16,GPIO4-2,GPIO4_3作为外部按键中断源,如图所示:
硬件连接如图所示:
简单起见,我这里只连接了一个按键,驱动程序中实现了4个按键的功能。
由于扩展的I/O接口中没有DC3.3V
电源,我们从IIC接口引入DC3.3V电源。
2. 驱动程序
安装字符设备驱动程序开发流程开发。
2.1 资源定义
定义按键I/O端口号、I/O中断号,以及字符设备的主设备号变量:
#define GPIO_KEY1_PIN_NUM (3*32 + 14) /*gpio 3_14 */
#define GPIO_KEY2_PIN_NUM (3*32 + 16) /*gpio 3_16 */
#define GPIO_KEY3_PIN_NUM (4*32 + 3) /*gpio 4_3 */
#define GPIO_KEY4_PIN_NUM (4*32 + 2) /*gpio 4_2 */
#define KEY1_IRQ gpio_to_irq(GPIO_KEY1_PIN_NUM) /*获取KEY1中断号*/
#define KEY2_IRQ gpio_to_irq(GPIO_KEY2_PIN_NUM) /*获取KEY2中断号*/
#define KEY3_IRQ gpio_to_irq(GPIO_KEY3_PIN_NUM) /*获取KEY3中断号*/
#define KEY4_IRQ gpio_to_irq(GPIO_KEY4_PIN_NUM) /*获取KEY4中断号*/
#define DEVICE_NAME "keys"
static int minor;
struct cdev *key_irq; /* cdev 数据结构 */
static dev_t devno; /* 设备编号 */
static struct class *key_irq_class;
static struct class *key_async_class;
static struct class_device *key_async_class_dev;
2.2 定义等待队列、互斥锁
本驱动程序支持阻塞读取、异步通知,所以设计了相应的等待队列,和互斥锁结构体,以及按键完成标志位:
*定义按键等待队列*/
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
/* 中断事件标志, 中断服务程序将它置1,key_drv_read将它清0 */
static volatile int ev_press = 0;
static struct fasync_struct *key_async;
struct pin_desc{
unsignedint pin;
unsignedint key_val;
};
/* 键值: 按下时, 0x01, 0x02 */
/* 键值: 松开时, 0x81, 0x82 */
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{GPIO_KEY1_PIN_NUM,0x01},
{GPIO_KEY2_PIN_NUM,0x02},
{GPIO_KEY3_PIN_NUM,0x03},
{GPIO_KEY4_PIN_NUM,0x04},
};
/*按键驱动同一时间只能由一个应用程序打开*/
static DEFINE_SEMAPHORE(key_lock); //定义互斥锁
2.3 中断处理函数
在中断处理函数中,我们判断触发该中断的device编号,然后读取该编号对应按键的高、低电平,生产keycode,设置按键中断完成标志,唤醒等待队列中的进程,并将中断信息异步通知应用层。
static irqreturn_t keys_irq(int irq, void*dev_id)
{
structpin_desc * pindesc = (struct pin_desc *)dev_id;
unsignedint pinval;
//printk("Interruptproduced!n");
pinval= gpio_get_value(pindesc->pin);
if(pinval) //高电平代表按键松开
{
/*松开 */
key_val= 0x80 | pindesc->key_val;
}
else //低电平代表按键按下
{
/*按下 */
key_val= pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&key_waitq); /* 唤醒休眠的进程 */
kill_fasync(&key_async, SIGIO, POLL_IN); /*向应用层发送IO信号*/
returnIRQ_HANDLED;
}
2.4 open函数
系统的中断资源时非常宝贵的,我们一般在真正开始使用中断时才申请中断,文件关闭时立即释放中断。
static int key_drv_open(struct inode*inode, struct file *file)
{
inti;
intret;
if(file->f_flags & O_NONBLOCK) //非阻塞式打开文件
{
if(down_trylock(&key_lock))
return-EBUSY;
}
else //阻塞式打开文件
{
/*获取信号量 */
down(&key_lock);
}
ret= gpio_request_one(pins_desc[0].pin, GPIOF_IN, "KEY1 IRQ"); /* 申请 IO ,为输入*/
if(ret < 0) {
printk(KERN_ERR"Failed to request GPIO for KEY1n");
}
ret= gpio_request_one(pins_desc[1].pin, GPIOF_IN, "KEY2 IRQ"); /* 申请 IO ,为输入*/
if(ret < 0) {
printk(KERN_ERR"Failed to request GPIO for KEY2n");
}
ret= gpio_request_one(pins_desc[2].pin, GPIOF_IN, "KEY3 IRQ"); /* 申请 IO ,为输入*/
if(ret < 0) {
printk(KERN_ERR"Failed to request GPIO for KEY3n");
}
ret= gpio_request_one(pins_desc[3].pin, GPIOF_IN, "KEY4 IRQ"); /* 申请 IO ,为输入*/
if(ret < 0) {
printk(KERN_ERR"Failed to request GPIO for KEY4n");
}
/*设置 GPIO 为输入 */
for(i=0;i<4;i++)
gpio_direction_input(pins_desc.pin);
/*为GPIO3_14,GPIO3_16,GPIO4_2,GPIO4_3申请中断 */
if(request_irq(KEY1_IRQ,keys_irq, IRQ_TYPE_EDGE_BOTH, "K1", &pins_desc[0]))
{
printk(KERN_ERR"Failed to request IRQ for KEY1n");
}
if(request_irq(KEY2_IRQ,keys_irq, IRQ_TYPE_EDGE_BOTH, "K2", &pins_desc[1]))
{
printk(KERN_ERR"Failed to request IRQ for KEY2n");
}
if(request_irq(KEY3_IRQ,keys_irq, IRQ_TYPE_EDGE_BOTH, "K3", &pins_desc[2]))
{
printk(KERN_ERR"Failed to request IRQ for KEY3n");
}
if(request_irq(KEY4_IRQ,keys_irq, IRQ_TYPE_EDGE_BOTH, "K4", &pins_desc[3]))
{
printk(KERN_ERR"Failed to request IRQ for KEY4n");
}
return0;
}
2.5 read函数
ssize_t key_drv_read(struct file *file,char __user *buf, size_t size, loff_t *ppos)
{
if(size != 1)
return-EINVAL;
if(file->f_flags & O_NONBLOCK)
{
if(!ev_press)
return-EAGAIN;
}
else
{
/*如果没有按键动作, 休眠,等待ev_press变为1,即等待按键触发中断 */
wait_event_interruptible(key_waitq,ev_press);
}
/*如果有按键动作, 返回键值 */
copy_to_user(buf,&key_val, 1);
ev_press= 0;
return1;
}
2.6 close函数
文件关闭时,释放中断资源。
int key_drv_close(struct inode *inode,struct file *file)
{
free_irq(KEY1_IRQ,&pins_desc[0]);
free_irq(KEY2_IRQ,&pins_desc[1]);
free_irq(KEY3_IRQ,&pins_desc[2]);
free_irq(KEY4_IRQ,&pins_desc[3]);
up(&key_lock);
return0;
}
2.7 查询与异步函数与文件操作结构体
static unsigned key_drv_poll(struct file*file, poll_table *wait)
{
unsignedint mask = 0;
poll_wait(file,&key_waitq, wait); // 不会立即休眠
if(ev_press)
mask|= POLLIN | POLLRDNORM;
returnmask;
}
static int key_drv_fasync (int fd, structfile *filp, int on)
{
printk("driver:key_drv_fasyncn");
returnfasync_helper (fd, filp, on, &key_async);
}
static struct file_operations key_drv_fops= {
.owner = THIS_MODULE,
.open = key_drv_open,
.read = key_drv_read,
.release= key_drv_close,
.poll = key_drv_poll,
.fasync = key_drv_fasync,
};
2.8 内核模块初始化函数
内核模块初始化函数,负责申请驱动程序主设备号,创建设备类,和/dev目录下的设备。
static int key_drv_init(void)
{
intret;
ret= alloc_chrdev_region(&devno, minor, 1, DEVICE_NAME); /* 从系统获取主设备号 */
major= MAJOR(devno);
if(ret < 0) {
printk(KERN_ERR"cannot get major %d n", major);
return-1;
}
key_irq= cdev_alloc(); /* 分配 key_irq 结构 */
if(key_irq != NULL) {
cdev_init(key_irq,&key_drv_fops); /* 初始化 key_irq 结构 */
key_irq->owner= THIS_MODULE;
if(cdev_add(key_irq, devno, 1) != 0) { /* 增加 key_irq 到系统中 */
printk(KERN_ERR"add cdev error!n");
gotoerror;
}
}
else{
printk(KERN_ERR"cdev_alloc error!n");
return-1;
}
key_irq_class= class_create(THIS_MODULE, "key_irq_class");
if(IS_ERR(key_irq_class)) {
printk(KERN_INFO"create class errorn");
return-1;
}
device_create(key_irq_class,NULL, devno, NULL, DEVICE_NAME);
printk("Initcompleted!n");
return0;
error:
unregister_chrdev_region(devno,1); /* 释放已经获得的设备号 */
returnret;
return0;
}
2.8 内核模块退出函数
释放初始化内核模块时申请的资源:
static void key_drv_exit(void)
{
inti;
for(i=0;i<4;i++)
gpio_free(pins_desc.pin); /*释放为按键KEY申请的GPIO资源*/
cdev_del(key_irq);/* 移除字符设备 */
unregister_chrdev_region(devno,1); /* 释放设备号 */
device_destroy(key_irq_class,devno);
class_destroy(key_irq_class);
printk(KERN_INFO"Exit completed!n");
return0;
}
3 驱动程序Makefile与编译
在编译驱动之前首选要编译与TL-4379开发板当前正在运行的系统版本相一致的内核,然后编写该驱动模块的Makefile。我们在本系列使用报告的第三批就已完成了内核的编译。
Makefile如下:
上图中,一定要注意内核源码的路径。
执行命令编译内核模块:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
编译结果如图所示:
执行命令,将key_async.ko驱动模块拷贝到NFS共享目录:
cp key_async.ko /nfsshare
4 编程QT测试程序
我们需要QT应用程序能够捕获系统的SIGNAL信号,但是该信号是用普通的C语言函数实现的,而QT是基于C++的面向对象的开发模式,信号拦击函数只能以界面类的静态成员函数存在。但是问题又来了,我们必须在信号处理函数中操作界面元素,而C++中静态成员函数是无法直接调用非静态成员变量的,我们采用一种全局变量保存界面实体对象的方法绕过去。
1)创建界面如图所示:
当按键按下时,相应的key复选框被勾选,按键松开时,勾选取消。keycode显示当前按键的状态码。
2)创建主窗口全局变量
3)定义异步通知静态函数及文件句柄
4)注册信号异步通知函数
5)实现信号异步通知与状态显示函数
6)编译QT测试程序
编译结果如图所示:
将qt_key_ansync测试程序拷贝到NFS共享目录
5. TL-4379上电测试
给TL-4379上电,并执行命令挂载NFS:
mount -t nfs 192.168.1.103:/nfsshare /mnt-o nolock
cd /mnt
ls
命令执行如图所示:
执行命令,关闭TL-4379自带的图形界面:
/etc/init.d/matrix-gui-2.0 stop
执行命令,加载key_async.ko驱动程序,
insmod key_async.ko
执行结果如图所示:
执行命令,启动QT测试程序:
./qt_key_async -plugins tslib:/dev/input/touchscreen0
命令执行结果如图示:
按下“KEY2”,图形界面中对应的Key2复选框被选中,如图所示:
按下“KEY4”,图形界面中对应的Key4复选框被选中,如图所示:
6. 小结
本试用报告的内容有两个难点,一个是按键驱动程序的异步通知,另一个是QT如何正确拦截Linux系统信号,我们这里采用了全局变量保存Widget窗口对象,然后在系统信号处理函数中调用主窗口Widget容器内的相应复选框元素。通过对功能的测试,完全达到了无需轮询、阻塞,QT程序即可被系统高效通知,处理完通知消息后,QT程序又可继续执行其他任务,大大提高了系统工作效率。
1