嵌入式linux學習筆記 之 按鍵與中斷

1.查詢方式獲取按鍵
    1.框架
        頭文件
        file_operations結構體

            .open = 
            .read = second_drv_read,
        read函數的參數
        入口函數註冊結構體 second_drv_init
            major = register_chrdev(0,”secon_drv”,&second_drv_fops);
        出口函數second_drv_exit(void)
            unregister_chrdev(major,”secon_drv”);
        修飾
            module_init(second_drv_init)
            module_exit(second_drv_exit)
        讓udev自動創建設備節點(mdev是udev的簡化版本)
            定義類和設備
            static struct class *second_drv_class;
            創建class,創建class device
            seconddrv_class = classs_create(THIS_MODULE, “second_drv”);
            gbseconddrv_class_device = class_device_create(seconddrv_class,NULL,MKDEV(major,0),NULL,”buttons”);
            卸載設備和class
            class_device_unregister(firstdrv_class_dev);
            class_destroy(seconddrv_class);
        MODULE_LINCENS(“GPL”)
    上傳驅動文件,修改Makefile,編譯,拷貝到根文件系統,加載驅動,檢查驅動是否正常加載
    2.硬件操作
        看原理圖,確定引腳
        看2440手冊,確定引腳如何操作,相關寄存器
        編寫程序,單片機編程直接用物理地址;Linux驅動中使用虛擬地址。
            vA = ioremap(pA,len);
        程序規劃:
            在open函數中配置引腳
            在read中返回IO值
            在inint中進行寄存器映射
        編程:
        init中建立映射
            gpfcon = (volatile unsigned log *)ioremap(0x56000050,16);
            gpfdat = gpfcon + 1;
            gpgcon = (volatile unsigned log *)ioremap(0x56000060,16);
            gpgdat = gpgcon + 1;
        exit中取消映射
            iounmap(gpfcon);
            iounmap(gpgcon);
        open中配置GPF0,2,GPG3,11爲輸入,對應位清零
            *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
            *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
        read中返回引腳電平,ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loft_t *ppos)
            unsigned char key_vals[4];
            int regval;
            regval = *gpfdat;
            key_vals[0] = regval & BIT0;  或者 key_vals[0] = (regval & (1<<0)) ? 1: 0
            key_vals[1] = (regval & (1<<2)) ? 1: 0
            數據返回給用戶空間
            if (size != sizeof(key_vals))
                return -EINVAL;
            copy_to_user(buf,key_vals,sizeof(key_vals));
        測試程序
            open(“/dev/buttons”,O_RDWR);
            read(fd,keyvals,sezeof(key_vals));
            if (!keyval[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
            {
                printf(“keyval: %4d %4d %4d %4d”,key_vals[0],,key_vals[1],key_vals[2],key_vals[3])              
            }
            
            
2.Linux的中斷處理框架
    Linux的中斷處理與單片機本質上是一致的,單片機中,中斷髮生後硬件根據中斷向量表跳轉到對應的地址去執行;在ARM架構Linux中,中斷產生後,CPU進入異常處理模式,即中斷異常,調用中斷總入口函數 asm_do_IRQ() ,該函數又會根據中斷號調用對應的中斷處理函數,這個處理函數就相當於我們在單片機編程中寫的中斷服務子程序。
    而Linux的中斷編程就是填充相關的數據結構和編寫中斷服務函數。
    關鍵的數據結構(這裏只列出了數據結構中比較關鍵的成員):
    struct irq_desc {
        irq_flow_handler_t  handle_irq; /*highlevel irq-events handler [if NULL, __do_IRQ()]*/
        struct irq_chip     *chip;      /*low level interrupt hardware access*/
        struct irqaction    *action;    /* IRQ action list */
        unsigned int        status;     /* IRQ status */
        unsigned int        irq_count;  /* For detecting broken IRQs */
        const char      *name;          /*flow handler name for /proc/interrupts output*/
    } ____cacheline_internodealigned_in_smp;
    其中irq_chip 結構體用於定義與硬件相關的底層操作,如啓動或關閉中斷、使能或禁止中斷、設置中斷屏蔽設置中毒觸發方式等。
    irqaction 結構體用於存放用戶定義的中斷處理函數,對於共享中斷,可以有多箇中斷處理函數,故irqaction是一個結構鏈表。
    另外,在單片機低功耗編程中,我設置好中斷後進入休眠狀態,當中斷髮生後喚醒系統進行中斷處理,中斷完成後再次進入休眠等待中斷髮生;在Linux按鍵中斷編程中,中斷處理函數喚醒read函數,read函數將數據發送給用戶應用後再次進入休眠,這樣就不會一直佔用CPU時間。則與單片機的低功耗編程很相似。
    
3.中斷方式的按鍵驅動框架
    與查詢方式的按鍵驅動不一樣的地方在於:
        1.在open函數中使用request_irq()函數註冊中斷
        2.在close函數中使用free_irq()函數釋放中斷
        3.文件操作結構體 gbthird_drv_fops 中添加 .release = gbthird_drv_close, 即關閉設備時需要調用驅動的close函數,該函數中釋放中斷。
        4.編寫中斷服務函數 buttons_irq()    
    其實對於Linux的中斷編程,我們只需要做幾件簡單的事就可以,前面說到的數據結構填充都會有Linux自動完成。當然這是站在一個比較低的層次來看,我相信中斷編程還有很多需要深入的地方。
        1. gbthird_drv_open 函數中註冊中斷:
            request_irq(IRQ_EINT8, buttons_irq,IRQT_BOTHEDGE,”S1″,&pins_desc[0]);
            參數依次爲:中斷號,中斷處理函數,觸發方式,名稱,設備號,這些參數與前面的結構體是對應的,request_irq() 函數會使用這些參數完成數據結構的填充。設備號可以是任意數,也可以是指針。
        2. gbthird_drv_close 函數中釋放中斷
            free_irq(IRQ_EINT8 , &pins_desc[0]);
            參數依次爲:中斷號,設備號,與註冊中斷時一致。
        3. 文件操作結構體 gbthird_drv_fops 中添加 .release = gbthird_drv_close
        4. 編寫中斷服務子函數,當中斷產生時將會調用該函數
            static irqreturn_t buttons_irq(int irq, void *dev_id)
            {
                struct pin_desc * pindesc = (struct pin_desc*)dev_id;
                unsigned int pinval;
                pinval = s3c2410_gpio_getpin(pindesc->pin);      //ok6410中是使用gpio_get_value(pindesc->pin); 括號中參數可以爲 S3C64XX_GPN(0)等等
                if (pinval)
                {
                    key_val = 0x80 | pindesc->key_val;  //relrese
                }
                else
                {
                    key_val = pindesc->key_val;
                }
                ev_press = 1;
                wake_up_interruptible(&button_waitq);
                return  IRQ_RETVAL(IRQ_HANDLED);
            }
        5. 修改原有的Read函數,read函數等待中斷喚醒,然後拷貝按鍵值到用戶空間。
            static ssize_t gbthird_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
            {
                unsigned char KeyVal[6];
                int RegVal = 0;
                if ( size != 1 )
                    return -EINVAL;
                /* 如果沒有按鍵動作, 休眠 */
                wait_event_interruptible(button_waitq, ev_press);
                /* 如果有按鍵動作, 返回鍵值 */
                copy_to_user(buf, &key_val, 1);
                ev_press = 0;
                return size;
            }
        這樣就完成了中斷方式的按鍵驅動。
        
PS:新接觸的linux命令
    cat /proc/interrupts    查看中斷註冊、使用情況
    ./button_test &         後臺方式運行程序
    ps 顯示運行的進程

/////////////////////////////////////////////////////////////////////////////////////////
s3c2410_gpio_getpin()函數說明

unsigned int s3c2410_gpio_getpin(unsigned int pin)

{

    void __iomem *base = S3C24XX_GPIO_BASE(pin);

    unsigned long offs = S3C2410_GPIO_OFFSET(pin);

    return __raw_readl(base + 0x04) & (1<< offs);

}

s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值與所要讀取的GPIO對應的bit mask相與以後的值,0表示該GPIO對應的bit爲0, 非0表示該bit爲1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9爲低電平則返回的是0,如果是高電平則返回的是GPxDAT中的GPG9對應位的值爲0x0100而不是0x0001,查處問題後修改也很簡單了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章