驱动框架:
采用混杂驱动程序的框架,具体细节在下面这个帖子二楼做过描述
https://bbs.eeworld.com.cn/thread-495558-1-1.html
硬件电路图:
分析:
对于按键程序的编写,在我用stm32单片机的时候,就有多种控制方式,ARM-linux 下也有多种方式去做,查询,中断,poll机制,信号,用内核定时器........
查询方式:
这种方式的实现,内核只需要提供read,open接口即可,在open()里面将对应引脚初始化成输入配置,在应用程序中,C语言写的话,while(1){}无限循环,读按键,发现读出来的值有变化,就证明按键按下去了,就打印到终端,用qt,C++写的话,需要自己创建一个线程,在这个线程里不断的执行读,打印,不管是用什么应用程序写,用top命令可以发现自己的应用程序占据cpu 77.7%甚至更多。这种方式显然不适合。
中断方式:
对于中断更多的解释可以查看《宋宝华-精通LINUX设备驱动开发》第四章第2节
从中提出我需要写的部分,中断配置添加进我写好的混杂驱动程序框架中,在函数外定义一个结构体:
- static unsigned char key_value; //保存键值,作为全局变量使用
/* 硬件引脚 定义 */
struct pin_des {
int pin;
unsigned char key_val;
};
static struct pin_des pins_desc[3] = {
{MXS_PIN_TO_GPIO(PINID_SSP0_DATA4), 0x01},
{MXS_PIN_TO_GPIO(PINID_SSP0_DATA5), 0x02},
{MXS_PIN_TO_GPIO(PINID_SSP0_DATA6),0x03},
};
注意发现,电路是5个按键,而我只定义了3个按键,是因为我用查询方式的时候,发现有两个按键不管按没按下,读到的值都是0,是坏了还是怎么的,没有多想,按键用3个也是可以实现的,学习不去计较这些。
在open()函数主要是初始化硬件,配置中断,这个尽量不要在程序入口函数里,而官方的按键驱动偏偏写在了程序入口函数,不知道他们是怎么想的。
- static int button_open(struct inode *inode, struct file *filp)
- {
- unsigned char i ;
- int irq_no;
- int iRet;
- for(i=0;i<3;i++){
- gpio_free(pins_desc[i].pin);
- iRet = gpio_request(pins_desc[i].pin, DEVICE_NAME);
- if(iRet != 0){
- printk("request button failed \n");
- return -EBUSY;
- }
- gpio_direction_input(pins_desc[i].pin);
- printk("direction input OK \n");
- irq_no = gpio_to_irq(pins_desc[i].pin);
- set_irq_type(irq_no, IRQF_TRIGGER_FALLING); //下降沿中断
- printk("IRQF_TRIGGER_FALLING \n");
- //申请中断并设置中断处理函数
- iRet = request_irq(irq_no, button_irq, IRQF_DISABLED, "button_exit", &pins_desc[i]);
- if (iRet != 0){
- printk("request irq failed!! ret: %d irq:%d gpio:%d \n", iRet, irq_no, pins_desc[i].pin);
- return -EBUSY;
- }
- }
-
- return 0;
- }
中断函数是需要自己写,名字也是需要自己想,至于中断为什么可以找到你写的中断函数,这个中断的框架linux内核早就做好了,做驱动开发的人只需要拿来用,而且中断也是等着你用就行。
- static irqreturn_t button_irq(int irq, void *dev_id)
- {
- printk("irq test \n");
- struct pin_des * pindesc = (struct pin_desc *)dev_id;
- unsigned int val;
- val = (unsigned char)gpio_get_value(pins_desc->pin);
- if(val) key_value = 0x10 |pindesc->key_val;
- else key_value = pindesc->key_val;
- ev_press = 1; /* 表示中断发生了 */
- wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
- return IRQ_RETVAL(IRQ_HANDLED);
- }
会发现,中断需要唤醒休眠,为什么用休眠?如果是单纯的查询按键,知道CPU的占用率太高了,所以就引进休眠的手段,为CPU考虑一下,什么时候休眠呢?这里你可以在open里面主动休眠,中断唤醒,你也可以使用poll机制,让poll去休眠。
配合休眠的手段,那read()函数也要配合着改进
- ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
- {
- unsigned char i;
- unsigned char key_val[5];
- /* 如果没有按键动作, 休眠 */
- wait_event_interruptible(button_waitq, ev_press);
-
- copy_to_user(buf, &key_value, 1);
- ev_press = 0;
- return 1;
- }
应用程序和查询方式的应用程序一样。
poll机制:
在中断方式里提到的休眠手段,可以用poll去休眠,这样对应用层好,对内核也好,这种方式改进了只要你按键按下,终端持续的打印出键值,不是打印一下,为什么?因为按键按下,这个动作对于内核来说是一段持续的时间,会持续响应,导致应用程序也会持续响应,终端也就持续打印。所以加入poll机制,就是内核响应了,应用层响应,下次响应就需要等一段时间,几百毫秒或者几十个毫秒不等,看你应用层怎么设置了。
poll实现;
驱动程序中poll函数在file_operations结构里面,所以需要再增加一个函数就行
- static unsigned button_poll(struct file *file, poll_table *wait)
- {
- unsigned int mask = 0;
- poll_wait(file, &button_waitq, wait); // 不会立即休眠
-
- if (ev_press)
- mask |= POLLIN | POLLRDNORM;
-
- return mask;
- }
内核配置完了,那么应用层的配置:
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
ret = poll(fds, 1, 5000);
if (ret == 0)
{
printf("time out\n");
}
else
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
信号的方式:
如果熟悉RTOS操作系统,对信号量肯定不陌生,信号量是对多个任务之间通信使用的。而Linux里面内核与应用层除了read,writer,ioctl,来进行数据交互,还有信号这个东西,可以让内核用信号来告知应用层,只是没有数据罢了。这样应用层就有了中断了,对,可以理解为应用层的中断,有信号了,应用层就会执行相应的函数。这个信号的分析,需要先从应用层分析
应用层的实现:
内核提供了signal()函数接口,有两个参数,第一个参数是SIGIO,其值大小为29,第二参数就像内核申请中断函数一样,就是自己的中断函数,需要自己写,自己想,而信号来了会自动的跳转到这个函数中去。
signal(SIGIO, button_signal);
void button_signal(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
应用层要是写好了,就可以gcc 编译一下,测试一下,运行应用程序,再打开一个终端,ps找到这个应用程序的进程号,比如 2376
kill -29 2376
应该就会有反应,没有反应就好好检查程序。
关于信号的种类及处理,可以看下面这个网页
http://blog.chinaunix.net/uid-25002135-id-3300821.html
这样的情况是手动的操作,后续机器是自动操作的,不可能处处需要人为,那就需要一个方法让应用程序可以告诉对应的驱动程序我的进程号并且信号的大小。
- fcntl(fd, F_SETOWN, getpid());
-
- Oflags = fcntl(fd, F_GETFL);
-
- fcntl(fd, F_SETFL, Oflags | FASYNC);
这三句话都牵扯到了一个函数fcntl(),这个是应用层给内核命令的,太多的解释,自己也会含糊不清,第一句,告诉内核我的进程号,第二句选择好信号大小,第三句让驱动程序在fasync()函数里做好更改。
驱动程序的实现:
这个在应用层提到的fasync()函数,就在驱动程序file_operations结构里,所以添加即可
- static int button_fasync (int fd, struct file *filp, int on)
- {
- printk("driver: sixth_drv_fasync\n");
- return fasync_helper (fd, filp, on, &button_async);
- }
配置好了,内核只需要通过
static struct fasync_struct *button_async; //作为全局使用
kill_fasync (&button_async, SIGIO, POLL_IN);
什么时候用,那就是自己的事了,这样应用层与驱动程序就做好了联系
应用程序的改进:
这种信号的方式,应用程序就不需要采用while(1){}死循环的方式了,如果用C写应用程序,需要做的就是不能程序return,所以还是需要while(1){sleep(100);} 只是不做什么事而已。
如果要用QT写一个应用程序,怎么办?而我的QT是用C++写的,而signal()函数是对应C的函数,如果对应到了类的成员函数,要是你用类button,不断的去创造其他子类,就有button ss,button ss1.......那么就会有ss1.signal().ss.signel(),那内核发过来的信号到底要跳到哪个signal()函数中去,这是一个问题!假如抛开类的操作,就在QT里面使用纯C函数,那么要是在这个函数里面使用到GUI也是不行的,这个函数只能处理C函数,C++函数是处理不了的,而GUI是用C++写的,怎么解决?
这个时候只能感概C++与C的不容,仔细琢磨,不应该呀,肯定是自己C++能力不够,于是查资料,果然皇天不负有心人!
静态成员函数
在《C++程序设计教程-钱能》第八章第6节
就是共有的 这个词打破了这个局面,我可以在类中建一个静态成员函数,这样不管你用这个类继续创了多少个子类,而这个成员函数有且只有一个,而且还是这些个子类共有的,那么
signal(SIGIO, button_signal); 这个函数就可以在QT中使用了,只要button_signal函数是作为静态成员函数的存在就行。
接下来又有一个问题了,要是现在这个函数里使用其他成员函数从而使用GUI控件,怎么办?C++默认静态成员是不能访问别的成员函数的,只有被访问。一找资料,网上就是一大批的涌现,网络是个好东西,都有详细的解决方法,解决静态成员函数访问其他成员函数的方法有一些,这里不细诉。
看看效果
这里QT采用弹窗处理,按下按键,就弹一个窗口,在这个窗口并且显示是那个按键按下的
本帖最后由 ywlzh 于 2016-7-30 11:19 编辑