天天看点

Linux中断方式按键驱动

0.0上一个按键驱动使用查询方式,占用cpu为99%,根本不实用,因此使用中断方式按键驱动。

0.1驱动功能:记录按键按下次数并发往用户端。读取按键状态时,如果按键未按下则休眠进程,按键按下则进入中断服务函数,在isr中唤醒进程并将对应按键按下的次数加1.

一、

宏定义设备名称和主设备号,定义中断描述结构体及初始化结构体参数,按键次数静态全局数组,按键状态变量(0表示未按下,1表示按下),注册等待队列。

#define DEVICE_NAME	"buttons"		/* 设备名称 */
#define BUTTON_MAJOR	232				/* 主设备号 */

static volatile unsigned int ev_press = 0;	/* 按键状态量 */

struct button_irq_des {
		unsigned int irq;
		unsigned long flags;
		char *name;
};
static volatile int  press_cnt[4];
static struct button_irq_des button_irqs[4]=
{
	{IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"},/* K1 */
	{IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"},/* K2 */
	{IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"},/* K3 */
	{IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"},/* K4 */
};
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);/* 注册等待队列 */
           

二、

定义并初始化file_operations,其中的tird_dvr_open,third_drv_read函数在测试程序中的open,read函数会用的到。

open中主要是对按键中断注册,一共四个按键中断,逐个注册,若有一个注册失败,则释放之前所有已经注册的中断。

read负责传输按键次数数组的值到用户层。

close释放所有已经注册的中断。

static struct file_operations third_drv_fops = {
		.owner = THIS_MODULE,
		.open   = third_drv_open,
		.read	=	third_drv_read,
		.release = third_drv_close,
};
           
static int third_drv_open(struct inode *inode, struct file *file)
{
	int i;
	int err;
	printk("enter open fun\n");
	/* 逐个注册中断 */
	for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
	{
		err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
							  button_irqs[i].name, (void*)&press_cnt[i]);
		if(err)
			break;
	}
	/* 如果最后一个注册失败,释放全部已经注册的中断 */
	if(err)
	{
		i--;
		while(i>=0)
		{
			free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
			i--;
		}
	return -EBUSY;
	}
	return 0; 
}
           
static int third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long err;
	printk("enter read fun\n");
	/* 等待创建的队列,当条件为1时,唤醒进程,否则进程一直休眠 */
	wait_event_interruptible(buttons_waitq, ev_press);
	
	/* 执行到此处说明ev_press已经为1,已经执行完中断处理程序,将ev_press清零 */
	ev_press = 0;

	/* 复制按键状态到用户*/
	printk("copy to usr\n");
	err = copy_to_user(buf, (const void *)press_cnt, min(sizeof(press_cnt), size));
	/** 此处是关键找了好久bug,若想增加按键按下的次数,此处不能对数组清零 **/
	//memset((void *)press_cnt, 0, sizeof(press_cnt));
	//printk("%d\n",err);
	return (!err ? 0 : -EFAULT);

}
           
static int third_drv_close(struct inode * inode, struct file * file)
{
	int i;

	for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
	{
			free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
	}
	return 0;
}

           

三、

在中断服务程序中对按键计数数组对应值计数,dev_id是在注册中断时传入的press_cnt数组元素的地址,因此直接强制类型转换为press_cnt所指向的类型后自加。接着唤醒休眠的进程,返回上一次进入休眠的下一步操作,即清零按键状态和向用户传输数据。

static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
	/* 进入中断,按键按下 */
	ev_press = 1;

  *(volatile int *)dev_id += 1;

	/* 唤醒休眠的进程 */
	wake_up_interruptible(&buttons_waitq);
  printk("wake up\n");  
	
  return IRQ_RETVAL(IRQ_HANDLED);
}
           

四、

注册file_operations和卸载file_operations,在注册时由于没有使用mdev自动创建设备节点的方法(即创建class和device_class的方法),因此之后需要手动创建设备节点(mknod buttons  c  232  0).

static int __init third_drv_init(void)
{
	int err;
	
  err	= register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &third_drv_fops); // 注册, 告诉内核
	//thirddrv_class = class_create(THIS_MODULE,DEVICE_NAME);
	//thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(BUTTON_MAJOR, 0), NULL,DEVICE_NAME);
	if(err < 0)
	{
		printk(DEVICE_NAME"can't register!\n");
		return err;
	}
	printk(DEVICE_NAME "initialized\n");
	return 0;
}

static void __exit third_drv_exit(void)
{		
	unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME); // 卸载
	//class_device_unregister(thirddrv_class_dev);
	//class_destroy(thirddrv_class);
}

module_init(third_drv_init);
module_exit(third_drv_exit);

MODULE_LICENSE("GPL");
           

五、

在按键程序中打开设备节点,读取传入user的数据放入数组,并打印数组值。

int main(int argc, char *argv[])
{
	int fd;
	int i;
	int ret;
	int  val[4];

	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
		return -1;
	}
	while(1)
	{
		ret = read(fd, val, sizeof(val));
   	if (ret < 0) {
        printf("read err!\n");
        continue;
    }
		for(i = 0; i < 4; i++)
		{
			if(val[i])
			{
				printf("K%d has been pressed %d times!\n", i+1, val[i]);
			}
		}
	}
	
	return 0;
}
           

测试信息:成功显示按键次数,并且按键未按下时cpu只占用0%

Linux中断方式按键驱动
Linux中断方式按键驱动

使用函数printf()时,记得加上换行,否则数据在输出缓冲区中不立即输出至终端。

isr中对按键次数操作时由于dev_id是地址,所以不能忽略间接访问符  *

测试时必须手动创建设备节点。

继续阅读