前言:
本文是根據嗶哩嗶哩網站上“正點原子[第二期]Linux之ARM(MX6U)裸機篇”視頻的學習筆記,在這里會記錄下正點原子 I.MX6ULL 開發板的配套視頻教程所作的實驗和學習筆記內容。本文大量引用了正點原子教學視頻和鏈接中的內容。
引用:
正點原子IMX6U倉庫 (GuangzhouXingyi) - Gitee.com
《【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.5.2.pdf》
正點原子資料下載中心 — 正點原子資料下載中心 1.0.0 文檔
正文:
本文是 “正點原子[第二期]Linux之ARM(MX6U)裸機篇--第17?講” 的讀書筆記。第17講主要是介紹I.MX6U處理器的EPIT定時器的按鍵消抖。本節將參考正點原子的視頻教程第17講和配套的正點原子開發指南文檔進行學習。
0. 概述
在第15章和第17章實驗都使用到了按鍵,用到按鍵就要處理因為機械結構帶來的抖動問題,也就是按鍵消抖。前面的時延中都是直接使用了延時函數來實現消抖,因為簡單,但是直接使用延時函數來實現消抖會浪費CPU的性能,因為在延時函數里面CPU什么都做不了。如果按鍵使用中斷的話更不能再中斷里面使用延時函數,因為中斷服務函數要快進快出!本章我們學習如何使用定時器來實現按鍵消抖,使用定時器既可以實現按鍵消抖,而且也不會浪費CPU性能,這個也是Linux驅動里面按鍵消抖的做法。
1. 定時器按鍵消抖簡介
按鍵消抖的原理已經在第十五章詳細的講解過了,起始就是在按鍵按下以后延時一段時間再去讀取按鍵值,如果此時按鍵值還有效那就表示這是一次有效的按鍵,中間的延時就是消抖的。
但是這有一個缺點,就是已按時函數會浪費CPU性能,應為延時函數就是空跑。如果按鍵是用中斷方式實現的,那就更不應該在中斷服務函數里使用延時函數,因為中斷服務函數最基本的要求就是快進快出!上一章我們學習了EPIT定時器,定時器設置好定時時間,然后CPU就可以做其他事情去了,定時時間到了以后就會觸發中斷,然后在中斷中做相應的處理即可。
因此,我們可以借助定時器來實現消抖,
- 按鍵采用中斷驅動的方式,當按下按鍵觸發按鍵中斷,在按鍵中斷中開啟一個定時器,
- 定時周期為10ms,當定時時間到了以后就會觸發定時器中斷
- 最后在定時器中斷處理函數中讀取按鍵的值,如果按鍵還是按下的狀態那就表示這是一次有效的按鍵。
定時器按鍵消抖如下圖所示:
在圖19.1.1中的t1~t3這一段時間就是按鍵抖動,是需要消除的。設置按鍵為下降沿觸發,因此會在t1,t2,和t3這三個時刻觸發那件中斷,每次進入中斷處理函數都會重開定時器中斷,所以會在t1,t2,和t3這三個時刻開定時器中斷。但是t1~t2和t2~t3這兩段時間是小于我們設置的定時器中斷周期(也就是消抖時間,比如10ms),所以雖然t1開啟了定時器,但是定時器時間沒有到呢t2時刻就重置了定時器,最終之后t3時刻開啟的定時器能完整的完成整個定時周期并觸發中斷,我們就可以在中斷處理函數里面做按鍵處理了,這就是定時器完成按鍵消抖的原理,Linux里面的按鍵驅動用的就是這個原理!
關于定時器消毒的原理就介紹到這里,接下來講解如何使用EPIT1來配置按鍵KEY來實現具體的消抖,步驟如下:
- 配置按鍵IO中斷
配置按鍵所使用的IO,因為要使用到中斷驅動按鍵,所以要配置IO的中斷模式。- 初始化消抖用的定時器
上面已經講的很清楚了,消抖要用定時器來完成,所以需要初始化一個定時器,這是使用上一章講解的EPIT1定時器,也算是對EPIT1定時器的一次鞏固。定時器的定時周期為10ms,也可以根據實際情況調整定時周期。- 編寫中斷處理函數
需要編寫兩個中斷處理器函數:按鍵對應的GPIO中斷處理函數和EPIT1定時器的中斷處理函數。在按鍵的中斷處理函數中主要用于開啟EPIT1定時器,EPIT1定時器處理函數才是重點,按鍵要做的具體任務都是在定時器EPIT1的中斷處理函數中完成的,比如控制蜂鳴器打開或關閉。
2. 定時器按鍵消抖程序編寫
更具上面分析的定時器按鍵消抖的原理和定時器按鍵消抖實驗的步驟,編寫定時器按鍵消抖程序源碼如下:
bsp/keyfilter/bsp_keyfilter.h
#ifndef __BSP_KEYFILTER_H__
#define __BSP_KEYFILTER_H__#include "imx6u.h"void keyfilter_init(void);
void keyfilter_timer_init(int value);
void keyfilter_timer_stop(void);
void keyfilter_timer_restart(int value);
void keyfilter_timer_irqhandler(IRQn_Type irq, void *userparam);
void gpio1_16_31_irqhandler(IRQn_Type irq, void *userparam);#endif
bsp/keyfilter/bsp_keyfilter.c?
#include "bsp_keyfilter.h"
#include "bsp_beep.h"
#include "bsp_led.h"
#include "bsp_int.h"
#include "bsp_gpio.h"
#include "bsp_epittimer.h"void keyfilter_init(void)
{/* GPIO1_IO18 */gpio_pin_config_t config;/* 1. 初始化IO復用,復用為GPIO1_IO18 */IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);/* 2. 設置 UART1_CTS_B IO 的電氣特性 */IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xf080);/* 3. 初始化 GPIO1_IO18 設置為輸入 */config.directioin = kGPIO_DigitalInput;config.intMode = kGPIO_FalllingEdgeInt;gpio_init(GPIO1, 18, &config);/* 啟用GIC IRQ */GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);/* 注冊IRQ處理函數 */system_irqhandler_register(GPIO1_Combined_16_31_IRQn, gpio1_16_31_irqhandler, NULL);/* EPIT1定時器初始化 */keyfilter_timer_init(66000000/100);/* 啟用gpio中斷 */gpio_int_enable(GPIO1, 18);
}void keyfilter_timer_init(int value){EPIT1->CR = 0;EPIT1->CR = (1 << 24 )| (1 << 3) | (1 << 2) | (1 << 1);EPIT1->LR = value;EPIT1->CMPR = 0;/* 使能GIC EPIT1_IRQn 中斷 */GIC_EnableIRQ(EPIT1_IRQn);/* 注冊中斷處理函數 */system_irqhandler_register(EPIT1_IRQn, keyfilter_timer_irqhandler, NULL);
}void keyfilter_timer_stop(void)
{EPIT1->CR &= ~(1 << 0);
}void keyfilter_timer_restart(int value){EPIT1->CR &= ~(1 << 0); /* 關閉EPIT1 */EPIT1->LR = value; /* EPIT1加載值寄存器 */EPIT1->CR |= (1 << 0); /* 打開EPIT1 */
}void keyfilter_timer_irqhandler(IRQn_Type irq, void *userparam){static int beep_state = 0;if(EPIT1->SR & (1 << 0)){ /* 檢查EPIT1中斷標志 */keyfilter_timer_stop(); /* 關閉EPIT1定時器 */if(gpio_pinread(GPIO1, 18) == 0) /* 檢查gpio引腳電平值 */{beep_state = !beep_state; /* 翻轉蜂鳴器 */beep_switch(beep_state);}}/* 清除中斷標志位 */EPIT1->SR |= (1<<0);
}void gpio1_16_31_irqhandler(IRQn_Type irq, void *userparam){if(GPIO1->ISR & (1 << 18)){keyfilter_timer_restart(66000000/100); /* 重啟EPIT1定時器,定時器周期10ms */}/* 清除中斷標志位 */gpio_int_cleanFlag(GPIO1, 18);
}
在如上的源碼中,初始化按鍵KEY0 對應GPIO1_IO18的 IO 復用,IO特性,啟用IO中斷,并注冊GPIO1_IO18的中斷處理函數,在按鍵中斷處理函數中重啟 EPIT1定時器設置定時周期為10ms,當定時周期完成時觸發EPIT1定時器比較事件中斷,在EPIT1定時器中斷里再次檢查gpio引腳的輸入電平如果還是有效說明此次按鍵按下是有效的,此時在EPIT1定時器中斷里翻轉蜂鳴器的鳴叫。
3. 編譯燒寫SD卡驗證實驗結果
譯修改主頻后源碼燒錄SD卡驗證本節的EPIT定時器消抖實驗是否生效。預期燒錄SD卡后正點原子I.MX6ULL ALPHA/Mini 開發板后,按下按鍵蜂鳴器鳴叫,再次按下按鍵蜂鳴器停止鳴叫,多次測試按鍵按下都能翻轉蜂鳴器開關。
我本地驗證的結果是EPIT定時器按鍵消抖實驗結果正常,多次按下按鍵都能正確的翻轉蜂鳴器鳴叫開關。
4. 總結和實驗遇到的問題記錄
4.1 問題1:EPIT定時器消抖實驗程序燒錄SD,發現有時按下并松開按鍵后蜂鳴器只鳴叫一聲就停止,預期應該一直鳴叫。
原因分析如下:
- 由于按鍵的機械結構,不僅僅在按鍵按下的瞬間有電平的多次抖動,從而在按鍵按下的瞬間多次在按鍵gpio電平的下降沿觸發GPIO中斷,進而重置EPIT1定時器最終出發EPIT1定時器中斷。
- 按鍵的機械結構決定了,在按鍵松開的瞬間也有多次的gpio引腳電平抖動,也會在按鍵松開的瞬間由于抖動在電平的下降沿觸發GPIO中斷,進而重置EPIT1定時器最終出發EPIT1定時器中斷。
- 由上分析可知,由于按鍵的機械結構,在按鍵按下的時會有電平抖動從而觸發GPIO中斷;在按鍵松開的時同樣也會有電平抖動從而觸發GPIO中斷;
所以,必須在EPIT1定時器中斷里再次檢查 GPIO 引腳的電平是否有效,對于本實驗,在按鍵按下的時候EPIT1中斷處理函數讀取到的gpio引腳為低電平證明按鍵按下有效。
(EPIT1定時中斷處理函數里讀取到gpio引腳為高電平,說明是按鍵松開。)
5. 結束
本文至此結束