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%
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4gjNzITOwETM0EjMxgTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
使用函數printf()時,記得加上換行,否則資料在輸出緩沖區中不立即輸出至終端。
isr中對按鍵次數操作時由于dev_id是位址,是以不能忽略間接通路符 *
測試時必須手動建立裝置節點。