中斷方式按鍵驅動程序


學習目的:

  • 使用中斷方式改寫查詢方式按鍵驅動程序

上一篇實現了查詢方式的按鍵驅動程序,編寫測試程序測試后發現,雖然應用程序可以通過系統調用使用驅動程序獲取按鍵狀態,但應用程序占CPU的資源極高。這一篇在編寫按鍵驅動程序中引入中斷方式,優化查詢方式實現的按鍵驅動程序。

核心思想:驅動程序中的button_drv_read函數內部加入休眠操作,當應用程序調用read函數去讀取按鍵值時,此時如果按鍵無按下或松開動作,該進程被加以休眠隊列。當按鍵按下觸發相應中斷服務程序,此時喚醒休眠的應用程序,在休眠地方繼續運行讀取按鍵值,並返回給應用程序。

程序編寫步驟(以查詢方式按鍵驅動程序為模板進行修改):

1)修改button_drv_open函數,在其中加入注冊按鍵的中斷服務函數

2)編寫中斷服務函數,在中斷服務函數中喚醒加入休眠隊列的用戶進程

3)修改button_drv_read函數,判斷按鍵是否有有效動作,若無有效動作將用戶進程加入休眠隊列中

4)修改button_drc_exit函數,卸載驅動程序時,卸載按鍵中斷服務函數設置


1、硬件連接

按鍵名稱 Altium Designer繪制電路圖表示網絡 連接芯片引腳 對應芯片外部中斷號
S2 EINT0 GPF0 EINT0
S3 EINT2 GPF2 EINT2
S4 EINT11 GPG3 EINT11
S5 EINT19 GPG11 EINT19

如上表所示,S2、S3、S4、S5按鍵分別連接到2440的GPF0、GPF2、GPG3、GPG11引腳,對應控制器的外部中斷0、外部中斷2、外部中斷11、外部中斷19。當使能控制器外部中斷0、2、11、19,設置外部中斷的觸發方式,滿足觸發條件時,CPU將進入中斷模式。得益於這樣硬件設計機制,才能保證可以通過中斷方式編寫驅動程序。

2、中斷注冊和卸載函數

2.1 中斷注冊函數

int request_irq(unsigned int irq,
        irq_handler_t handler,
        unsigned long irqflags, const char * devname, void *dev_id)

request_irq函數是內核中提供的中斷注冊函數,函數的各參數解釋如下:

irq:中斷號,控制器相關聯的,內核支持的硬件平台的中斷號一般在include/asm-xxx/arch-xxx目錄的irq.h文件中定義。以s3c2410為例,存放路徑內核源碼樹include\asm-arm\arch-s3c2410\irq.h文件

handler:中斷服務函數,irq對應中斷發生時,調用中斷服務函數

irqflags:中斷處理的屬性

devname:設置中斷名稱,通常是設備驅動程序的名稱  在cat /proc/interrupts中可以看到此名稱

dev_id: 一般設置為NULL,也可以指定指向特殊數據類型,該參數會傳遞給中斷服務函數

2.2 中斷卸載函數

void free_irq(unsigned int irq, void *dev_id)

free_irq函數作用與request_irq功能相反,該函數是內核中提供的卸載中斷服務的函數,各參數解釋如下:

irq:要卸載中斷號

dev_id:同request中的dev_id相同

3、驅動程序實現

3.1 button_drv_open函數

static int button_drv_open(struct inode *inode, struct file *file)
{
    int i;
    
    *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
    *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
    
    /* 注冊中斷處理函數 */
    for(i = 0; i < BUTTON_NUMS; i++)
        request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
    
    return 0;
}

button_drv_open函數,配置2440連接按鍵的引腳為輸入模式,調用request_irq注冊了按鍵引腳的外部中斷,設置以雙邊沿觸發方式觸發中斷

btn_desc為struct button_desc類型結構體數組,struct button_desc結構體數據類型描述如下:

struct button_desc
{
    int pin;
    int irq_type;
    unsigned long flags;
    char *name;
    int key_val;
};

pin:連接按鍵相關2440引腳

irq_type:中斷編號

flags:中斷處理的屬性

name:中斷名稱

key_val:用以描述此按鍵的值

3.2 button_irq_handle中斷服務函數

button_irq_handle為中斷服務程序,其源碼如下:

static irqreturn_t button_irq_handle(int irq, void *dev_id)
{    
    struct button_desc *pdesc = NULL;
    unsigned char pin_val;
    
    pdesc = (struct button_desc *)dev_id;
    
    pin_val = gpio_get_value(pdesc->pin);    

    if(pin_val == 1)
    {
        key_status = pdesc->key_val | 0x80;
    }
    else
    {
        key_status = pdesc->key_val;
    }
    
    event_trig = 1;
    wake_up_interruptible(&button_waitq); 
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

按鍵按下時,觸發外部中斷,此時相應的中斷服務函數會被調用。中斷服務函數中根據傳入參數獲取當前觸發中斷的外部引腳信息,並讀取該引腳狀態。通過調用wake_up_interruptible函數喚醒button_waitq隊列中休眠等待有效數據讀取的用戶進程,並將event_trig變量設置為1。

gpio_get_val函數根據傳入引腳,返回當前引腳的狀態,1:按鍵松開,0:按鍵按下

key_status:              S2              S3             S3               S4

  鍵值:按下     0x01           0x02          0x03            0x04

  鍵值:松開     0x81           0x82          0x83            0x84

3.3 button_drv_read函數

static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    if(count != 1)
        return EINVAL;
    
    wait_event_interruptible(button_waitq, event_trig);
    
    if(copy_to_user(buf, &key_status, count))
        return EFAULT;
    
    event_trig = 0;
    return 0;
}

應用程序調用read函數時被調用,event_trig為1,表示按鍵有有效值可讀取,此時直接拷貝描述按鍵狀態變量key_status值給用戶程序;event_trig為0,表示按鍵無有效值可讀,此時調用read函數讀取按鍵信息的進程被加入到button_waitq為頭部隊列中進行休眠。當按鍵按下觸發中斷,中斷服務函數中將該休眠的進程喚醒,並將event_trig置為1,完成后續讀操作。

3.3 button_drv_close函數

static int button_drv_close(struct inode *inode, struct file *file)
{
    int i;
    
    for(i = 0; i < BUTTON_NUMS; i++)
        free_irq(btn_desc[i].irq_type, &btn_desc[i]);
    
    return 0;
}

在不使用時,調用free_irq注銷中斷服務程序,釋放相關資源

4、編寫驅動測試程序

int main(int argc, char **argv)
{
    int fd, ret;
    unsigned char key_buf;
    
    fd = open("/dev/button", O_RDWR);
    if(fd == -1)
    {
        printf("can't open...\n");
        exit(EXIT_FAILURE);
    }
    
    while(1)
    {
        ret = read(fd, &key_buf, 1);
        if(ret < 0)
        {
            printf("read err...\n");
            continue;
        }
        
        /* 判斷有按鍵按下,打印按鍵信息 */
        printf("key_val=0x%x\n", key_buf);
    }

    exit(EXIT_SUCCESS);
}

讀取按鍵狀態信息,並在串口終端打印讀取的狀態

5、測試結果

驅動中設置外部中斷觸發方式為雙邊沿觸發,按鍵按下松開時都能夠觸發中斷服務程序

第一次按下S3按鍵,讀取值時0x3,松開時讀取值0x83

第一次按下S2按鍵,讀取值時0x2,松開時讀取值0x82

第一次按下S1按鍵,讀取值時0x1,松開時讀取值0x81                   

 

讀取按鍵,當無有效按鍵值可讀時,測試進程一般處於休眠狀態,占用CPU資源率基本為0,改善了上一篇中查詢方式占CPU資源多的缺點

 

完整驅動程序代碼

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <plat/gpio-fns.h>
#include <mach/gpio-nrs.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/gpio.h>


#define BUTTON_NUMS    4
#define IRQT_BOTHEDGE IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING

static int major;
static int event_trig = 0;

static unsigned char key_status;

static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpgcon = NULL;
static volatile unsigned long *gpfdat = NULL;
static volatile unsigned long *gpgdat = NULL;

static struct class *button_drv_class;
static struct class_device    *button_drv_class_dev;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

struct button_desc
{
    int pin;
    int irq_type;
    unsigned long flags;
    char *name;
    int key_val;
};

static struct button_desc btn_desc[BUTTON_NUMS] = {
    {S3C2410_GPF(0),  IRQ_EINT0,  IRQT_BOTHEDGE, "S2", 1},
    {S3C2410_GPF(2),  IRQ_EINT2,  IRQT_BOTHEDGE, "S3", 2},
    {S3C2410_GPG(3),  IRQ_EINT11, IRQT_BOTHEDGE, "S4", 3},
    {S3C2410_GPG(11), IRQ_EINT19, IRQT_BOTHEDGE, "S5", 4},
};

static int button_drv_open(struct inode *inode, struct file *file);
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static int button_drv_close(struct inode *inode, struct file *file);

struct file_operations button_drv_fileop = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   button_drv_open,
    .read   =   button_drv_read,
    .write  =   button_drv_write,
    .release =  button_drv_close,
};

static irqreturn_t button_irq_handle(int irq, void *dev_id)
{    
    struct button_desc *pdesc = NULL;
    unsigned char pin_val;
    
    pdesc = (struct button_desc *)dev_id;
    
    pin_val = gpio_get_value(pdesc->pin);    

    if(pin_val == 1)
    {
        key_status = pdesc->key_val | 0x80;
    }
    else
    {
        key_status = pdesc->key_val;
    }
    
    event_trig = 1;
    wake_up_interruptible(&button_waitq); 
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int button_drv_open(struct inode *inode, struct file *file)
{
    int i;
    
    *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
    *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
    
    /* 注冊中斷處理函數 */
    for(i = 0; i < BUTTON_NUMS; i++)
        request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
    
    return 0;
}

static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    if(count != 1)
        return EINVAL;
    
    wait_event_interruptible(button_waitq, event_trig);
    
    if(copy_to_user(buf, &key_status, count))
        return EFAULT;
    
    event_trig = 0;
    return 0;
}

static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("button_drv_write\n");
    
    return 0;
}

static int button_drv_close(struct inode *inode, struct file *file)
{
    int i;
    
    for(i = 0; i < BUTTON_NUMS; i++)
        free_irq(btn_desc[i].irq_type, &btn_desc[i]);
    
    return 0;
}
        
static int button_drv_init(void)
{
    major = register_chrdev(0, "button_light", &button_drv_fileop);
    
    button_drv_class = class_create(THIS_MODULE, "button_drv");
    //button_drv_class_dev = class_device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    button_drv_class_dev = device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpfdat = gpfcon + 1;
    gpgdat = gpgcon + 1;
    
    return 0;
}

static void button_drv_exit(void)
{
    unregister_chrdev(major, "button_drv");
    
    //class_device_unregister(button_drv_class_dev);
    device_unregister(button_drv_class_dev);
    class_destroy(button_drv_class);
    
    iounmap(gpfcon);
    iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");
irq_button_drv.c

完整測試程序代碼

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <stdio.h>


int main(int argc, char **argv)
{
    int fd, ret;
    unsigned char key_buf;
    
    fd = open("/dev/button", O_RDWR);
    if(fd == -1)
    {
        printf("can't open...\n");
        exit(EXIT_FAILURE);
    }
    
    while(1)
    {
        ret = read(fd, &key_buf, 1);
        if(ret < 0)
        {
            printf("read err...\n");
            continue;
        }
        
        /* 判斷有按鍵按下,打印按鍵信息 */
        printf("key_val=0x%x\n", key_buf);
    }

    exit(EXIT_SUCCESS);
}
irq_button_test.c


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM