天天看點

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是位址,是以不能忽略間接通路符  *

測試時必須手動建立裝置節點。

繼續閱讀