第33章 TIM—電容按鍵檢測
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
前面章節我們講解了基本定時器和高級控制定時器功能,這一章我們將介紹定時器輸入捕獲一個應用實例,幫助我們更加深入理解定時器。
33.1 電容按鍵原理
電容器(簡稱為電容)就是可以容納電荷的器件,兩個金屬塊中間隔一層絕緣體就可以構成一個最簡單的電容。如圖 331(俯視圖),有兩個金屬片,之間有一個絕緣介質,這樣就構成了一個電容。這樣一個電容在電路板上非常容易實現,一般設計四周的銅片與電路板地信號連通,這樣一種結構就是電容按鍵的模型。當電路板形狀固定之后,該電容的容量也是相對穩定的。
圖 331 片狀電容器
電路板制作時都會在表面上覆蓋一層絕緣層,用於防腐蝕和絕緣,所以實際電路板設計時情況如圖 332。電路板最上層是絕緣材料,下面一層是導電銅箔,我們根據電路走線情況設計決定銅箔的形狀,再下面一層一般是FR-4板材。金屬感應片與地信號之間有絕緣材料隔着,整個可以等效為一個電容Cx。一般在設計時候,把金屬感應片設計成方便手指觸摸大小。
圖 332 無手指觸摸情況
在電路板未上電時,可以認為電容Cx是沒有電荷的,在上電時,在電阻作用下,電容Cx就會有一個充電過程,直到電容充滿,即Vc電壓值為3.3V,這個充電過程的時間長短受到電阻R阻值和電容Cx容值的直接影響。但是在我們選擇合適電阻R並焊接固定到電路板上后,這個充電時間就基本上不會變了,因為此時電阻R已經是固定的,電容Cx在無外界明顯干擾情況下基本上也是保持不變的。
現在,我們來看看當我們用手指觸摸時會是怎樣一個情況?如圖 333,當我們用手指觸摸時,金屬感應片除了與地信號形成一個等效電容Cx外,還會與手指形成一個Cs等效電容。
圖 333 有手指觸摸情況
此時整個電容按鍵可以容納的電荷數量就比沒有手指觸摸時要多了,可以看成是Cx和Cs疊加的效果。在相同的電阻R情況下,因為電容容值增大了,導致需要更長的充電時間。也就是這個充電時間變長使得我們區分有無手指觸摸,也就是電容按鍵是否被按下。
現在最主要的任務就是測量充電時間。充電過程可以看出是一個信號從低電平變成高電平的過程,現在就是要求出這個變化過程的時間,這樣的一個命題與上一章講解高級控制定時器的輸入捕獲功能非常吻合。我們可以利用定時器輸入捕獲功能計算充電時間,即設置TIMx_CH為定時器輸入捕獲模式通道。這樣先測量得到無觸摸時的充電時間作為比較基准,然后再定時循環測量充電時間與無觸摸時的充電時間作比較,如果超過一定的閾值就認為是有手指觸摸。
圖 334為Vc跟隨時間變化情況,可以看出在無觸摸情況下,電壓變化較快;而在有觸摸時,總的電容量增大了,電壓變化緩慢一些。
圖 334 Vc電壓與充電時間關系
為測量充電時間,我們需要設置定時器輸入捕獲功能為上升沿觸發,圖 334中VH就是被觸發上升沿的電壓值,也是STM32認為是高電平的最低電壓值,大約為1.8V。t1和t2可以通過定時器捕獲/比較寄存器獲取得到。
不過,在測量充電時間之前,我們必須想辦法制作這個充電過程。之前的分析是在電路板上電時會有充電過程,現在我們要求在程序運行中循環檢測按鍵,所以必須可以控制充電過程的生成。我們可以控制TIMx_CH引腳作為普通的GPIO使用,使其輸出一小段時間的低電平,為電容Cx放電,即Vc為0V。當我們重新配置TIMx_CH為輸入捕獲時電容Cx在電阻R的作用下就可以產生充電過程。
33.2 電容按鍵檢測實驗
電容按鍵不需要任何外部機械部件,使用方便,成本低,很容易制成與周圍環境相密封的鍵盤,以起到防潮防濕的作用。電容按鍵優勢突出使得越來越多電子產品使用它代替傳統的機械按鍵。
本實驗實現電容按鍵狀態檢測方法,提供一個編程實例。
33.2.1 硬件設計
開發板板載一個電容按鍵,原理圖設計參考圖 335。
圖 335 電容按鍵電路設計
標示TPAD1在電路板上就是電容按鍵實體,它通過一根導線連接至定時器通道引腳,這里選用的電阻阻值為1M。
實驗還用到調試串口和蜂鳴器功能,用來打印輸入捕獲信息和指示按鍵狀態,這兩個模塊電路可參考之前相關章節。
33.2.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_touchpad.c和bsp_touchpad.h文件用來存放電容按鍵檢測相關函數和宏定義。
1. 編程要點
(1) 初始化蜂鳴器、調試串口以及系統滴答定時器;
(2) 配置定時器基本初始化結構體並完成定時器基本初始化;
(3) 配置定時器輸入捕獲功能;
(4) 使能電容按鍵引腳輸出低電平為電容按鍵放電;
(5) 待放電完整后,配置為輸入捕獲模式,並獲取輸入捕獲值,該值即為無觸摸時輸入捕獲值;
(6) 循環執行電容按鍵放電、讀取輸入捕獲值檢過程,將捕獲值與無觸摸時捕獲值對比,以確定電容按鍵狀態。
2. 軟件分析
宏定義
代碼清單 331 宏定義
1 #define TPAD_TIMx TIM2
2 #define TPAD_TIM_CLK RCC_APB1Periph_TIM2
3
4 #define TPAD_TIM_Channel_X TIM_Channel_1
5 #define TPAD_TIM_IT_CCx TIM_IT_CC1
6 #define TPAD_TIM_GetCaptureX TIM_GetCapture1
7
8 #define TPAD_TIM_GPIO_CLK RCC_AHB1Periph_GPIOA
9 #define TPAD_TIM_CH_PORT GPIOA
10 #define TPAD_TIM_CH_PIN GPIO_Pin_5
11 #define TPAD_TIM_AF GPIO_AF_TIM2
12 #define TPAD_TIM_SOURCE GPIO_PinSource5
使用宏定義非常方便程序升級、移植。
開發板選擇使用通用定時器2的通道1連接到電容按鍵,對應的引腳為PA5。
定時器初始化配置
代碼清單 332 定時器初始化配置
1 static void TIMx_CHx_Cap_Init(uint32_t arr,uint16_t psc)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
5 TIM_ICInitTypeDef TIM_ICInitStructure;
6
7 //使能TIM時鍾
8 RCC_APB1PeriphClockCmd(TPAD_TIM_CLK,ENABLE);
9 //使能通道引腳時鍾
10 RCC_AHB1PeriphClockCmd(TPAD_TIM_GPIO_CLK, ENABLE);
11 //指定引腳復用
12 GPIO_PinAFConfig(TPAD_TIM_CH_PORT,TPAD_TIM_SOURCE,TPAD_TIM_AF);
13
14 //端口配置
15 GPIO_InitStructure.GPIO_Pin = TPAD_TIM_CH_PIN;
16 //復用功能
17 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
19 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
20 //不帶上下拉
21 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
22 GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
23
24 //初始化TIM
25 //設定計數器自動重裝值
26 TIM_TimeBaseStructure.TIM_Period = arr;
27 //預分頻器
28 TIM_TimeBaseStructure.TIM_Prescaler =psc;
29 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
30 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
31 TIM_TimeBaseInit(TPAD_TIMx, &TIM_TimeBaseStructure);
32 //初始化通道
33 //選擇定時器輸入通道
34 TIM_ICInitStructure.TIM_Channel = TPAD_TIM_Channel_X;
35 //上升沿觸發
36 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
37 // 輸入捕獲選擇
38 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
39 //配置輸入分頻,不分頻
40 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
41 //配置輸入濾波器不濾波
42 TIM_ICInitStructure.TIM_ICFilter = 0x00;
43 TIM_ICInit(TPAD_TIMx, &TIM_ICInitStructure);
44 //使能TIM
45 TIM_Cmd ( TPAD_TIMx, ENABLE );
46 }
首先定義三個初始化結構體變量,這三個結構體之前都做了詳細的介紹,可以參考相關章節理解。
使用外設之前都必須開啟相關時鍾,這里開啟定時器時鍾和定時器通道引腳對應端口時鍾,並指定定時器通道引腳復用功能。
接下來初始化配置定時器通道引腳為復用功能,無需上下拉。
然后,配置定時器功能。定時器周期和預分頻器值由函數形參決定,采用向上計數方式。指定輸入捕獲通道,電容按鍵檢測需要采用上升沿觸發方式。
最后,使能定時器。
電容按鍵復位
代碼清單 333 電容按鍵復位
1 static void TPAD_Reset(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 //配置引腳為普通推挽輸出
6 GPIO_InitStructure.GPIO_Pin = TPAD_TIM_CH_PIN;
7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
8 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
9 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
10 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
11 GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
12
13 //輸出低電平,放電
14 GPIO_ResetBits ( TPAD_TIM_CH_PORT, TPAD_TIM_CH_PIN );
15 //保持一小段時間低電平,保證放電完全
16 Delay_ms(5);
17
18 //清除中斷標志
19 TIM_ClearITPendingBit(TPAD_TIMx, TPAD_TIM_IT_CCx|TIM_IT_Update);
20 //計數器歸0
21 TIM_SetCounter(TPAD_TIMx,0);
22
23 //引腳配置為復用功能,不上、下拉
24 GPIO_InitStructure.GPIO_Pin = TPAD_TIM_CH_PIN;
25 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
26 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
27 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
28 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
29 GPIO_Init(TPAD_TIM_CH_PORT,&GPIO_InitStructure);
30 }
該函數實現兩個主要功能:控制電容按鍵放電和復位計數器。
首先,配置定時器通道引腳作為普通GPIO,使其為下拉的推挽輸出模式。然后調用GPIO_ResetBits函數輸出低電平,為保證放電完整,需要延時一小會時間,這里調用Delay_ms函數完成5毫秒的延時。Delay_ms函數是定義在bsp_SysTick.h文件的一個延時函數,它利用系統滴答定時器功能實現毫秒級的精准延時。也因此要求在調用電容按鍵檢測相關函數之前必須先初始化系統滴答定時器。
這里還需要一個注意的地方,在控制電容按鍵放電的整個過程定時器是沒有停止的,計數器還是在不斷向上計數的,只是現階段計數值對我們來說沒有意義而已。
然后,清除定時器捕獲/比較標志位和更新中斷標志位以及將定時器計數值賦值為0,使其重新從0開始計數。
最后,配置定時器通道引腳為定時器復用功能,不上下拉。在執行完該GPIO初始化函數后,電容按鍵就馬上開始充電,定時器通道引腳電壓就上升,當達到1.8V時定時器就輸入捕獲成功。所以在執行完TPAD_Reset函數后應用程序需要不斷查詢定時器輸入捕獲標志,在發送輸入捕獲時馬上讀取TIMx_CCRx寄存器的值,作為該次電容按鍵捕獲值。
獲取輸入捕獲值
代碼清單 334 獲取輸入捕獲值
1 //定時器最大計數值
2 #define TPAD_ARR_MAX_VAL 0XFFFF
3
4 static uint16_t TPAD_Get_Val(void)
5 {
6 /* 先放電完全,並復位計數器 */
7 TPAD_Reset();
8 //等待捕獲上升沿
9 while (TIM_GetFlagStatus ( TPAD_TIMx, TPAD_TIM_IT_CCx ) == RESET) {
10 //超時了,直接返回CNT的值
11 if (TIM_GetCounter(TPAD_TIMx)>TPAD_ARR_MAX_VAL-500)
12 return TIM_GetCounter(TPAD_TIMx);
13 };
14 /* 捕獲到上升沿后輸出TIMx_CCRx寄存器值 */
15 return TPAD_TIM_GetCaptureX(TPAD_TIMx );
16 }
開始是TPAD_ARR_MAX_VAL的宏定義,它指定定時器自動重載寄存器(TIMx_ARR)的值。
TPAD_Get_Val函數用來獲取一次電容按鍵捕獲值,包括電容按鍵放電和輸入捕獲過程。
先調用TPAD_Reset函數完成電容按鍵放電過程,並復位計數器。
接下來,使用TIM_GetFlagStatus函數獲取當前計數器的輸入捕獲狀態,如果成功輸入捕獲就使用TPAD_TIM_GetCaptureX函數獲取此刻定時器捕獲/比較寄存器的值並返回該值。如果還沒有發生輸入捕獲,說明還處於充電過程,就進入等待狀態。
為防止無限等待情況,加上超時處理函數,如果發生超時則直接返回計數器值。實際上,如果發生超時情況,很大可能是硬件出現問題。
獲取最大輸入捕獲值
代碼清單 335 獲取最大輸入捕獲值
1 static uint16_t TPAD_Get_MaxVal(uint8_t n)
2 {
3 uint16_t temp=0;
4 uint16_t res=0;
5 while (n--) {
6 temp=TPAD_Get_Val();//得到一次值
7 if (temp>res)res=temp;
8 };
9 return res;
10 }
該函數接收一個參數,用來指定獲取電容按鍵捕獲值的循環次數,函數的返回值則為n次發生捕獲中最大的捕獲值。
電容按鍵捕獲初始化
代碼清單 336 電容按鍵捕獲初始化
1 uint8_t TPAD_Init(void)
2 {
3 uint16_t buf[10];
4 uint16_t temp;
5 uint8_t j,i;
6
7 //設定定時器預分頻器目標時鍾為:9MHz(180Mhz/20)
8 TIMx_CHx_Cap_Init(TPAD_ARR_MAX_VAL,20-1);
9 for (i=0; i<10; i++) { //連續讀取10次
10 buf[i]=TPAD_Get_Val();
11 Delay_ms(10);
12 }
13 for (i=0; i<9; i++) { //排序
14 for (j=i+1; j<10; j++) {
15 if (buf[i]>buf[j]) { //升序排列
16 temp=buf[i];
17 buf[i]=buf[j];
18 buf[j]=temp;
19 }
20 }
21 }
22 temp=0;
23 //取中間的6個數據進行平均
24 for (i=2; i<8; i++) {
25 temp+=buf[i];
26 }
27
28 tpad_default_val=temp/6;
29 /* printf打印函數調試使用,用來確定閾值TPAD_GATE_VAL,在應用工程中應注釋掉 */
30 printf("tpad_default_val:%d\r\n",tpad_default_val);
31
32 //初始化遇到超過TPAD_ARR_MAX_VAL/2的數值,不正常!
33 if (tpad_default_val>TPAD_ARR_MAX_VAL/2) {
34 return 1;
35 }
36
37 return 0;
38 }
39
該函數實現定時器初始化配置和無觸摸時電容按鍵捕獲值確定功能。它一般在main函數靠前位置調用完成電容按鍵初始化功能。
程序先調用TIMx_CHx_Cap_Init函數完成定時器基本初始化和輸入捕獲功能配置,兩個參數用於設置定時器的自動重載計數和定時器時鍾頻率,這里自動重載計數被賦值為TPAD_ARR_MAX_VAL,這里對該值沒有具體要求,不要設置過低即可。定時器時鍾配置設置為9MHz為合適,實驗中用到TIM2,默認使用內部時鍾為180MHz,經過參數設置預分頻器為20分頻,使定時器時鍾為9MHz。
接下來,循環10次讀取電容按鍵捕獲值,並保存在數組內。TPAD_Init函數一般在開機時被調用,所以認為10次讀取到的捕獲值都是無觸摸狀態下的捕獲值。
然后,對10個捕獲值從小到大排序,取中間6個的平均數作為無觸摸狀態下的參考捕獲值,並保存在tpad_default_val變量中,該值對應圖 334中的時間t1。
程序最后會檢測tpad_default_val變量的合法性。
電容按鍵狀態掃描
代碼清單 337 電容按鍵狀態掃描
1 //閾值:捕獲時間必須大於(tpad_default_val + TPAD_GATE_VAL),才認為是有效觸摸.
2 #define TPAD_GATE_VAL 100
3
4 uint8_t TPAD_Scan(uint8_t mode)
5 {
6 //0,可以開始檢測;>0,還不能開始檢測
7 static uint8_t keyen=0;
8 //掃描結果
9 uint8_t res=0;
10 //默認采樣次數為3次
11 uint8_t sample=3;
12 //捕獲值
13 uint16_t rval;
14
15 if (mode) {
16 //支持連按的時候,設置采樣次數為6次
17 sample=6;
18 //支持連按
19 keyen=0;
20 }
21 /* 獲取當前捕獲值(返回 sample 次掃描的最大值) */
22 rval=TPAD_Get_MaxVal(sample);
23 /* printf打印函數調試使用,用來確定閾值TPAD_GATE_VAL,在應用工程中應注釋掉 */
24 // printf("scan_rval=%d\n",rval);
25
26 //大於tpad_default_val+TPAD_GATE_VAL,且小於10倍tpad_default_val,則有效
27 if (rval>(tpad_default_val+TPAD_GATE_VAL)&&rval<(10*tpad_default_val)) {
28 //keyen==0,有效
29 if (keyen==0) {
30 res=1;
31 }
32 keyen=3; //至少要再過3次之后才能按鍵有效
33 }
34
35 if (keyen) {
36 keyen--;
37 }
38 return res;
39 }
TPAD_GATE_VAL用於指定電容按鍵觸摸閾值,當實時捕獲值大於該閾值和無觸摸捕獲參考值tpad_default_val之和時就認為電容按鍵有觸摸,否則認為沒有觸摸。閾值大小一般需要通過測試得到,一般做法是通過串口在TPAD_Init函數中把tpad_default_val值打印到串口調試助手並記錄下來,在TPAD_Scan函數中也把實時捕獲值打印出來,在運行時觸摸電容按鍵,獲取有觸摸時的捕獲值,這樣兩個值對比就可以大概確定TPAD_GATE_VAL。
TPAD_Scan函數用來掃描電容按鍵狀態,需要被循環調用,類似獨立按鍵的狀態掃描函數。它有一個形參,用於指定電容按鍵的工作模式,當為賦值為1時,電容按鍵支持連續觸發,即當一直觸摸不松開時,每次運行TPAD_Scan函數都會返回電容按鍵被觸摸狀態,直到松開手指,才返回無觸摸狀態。當參數賦值為0時,每次觸摸函數只返回一次被觸摸狀態,之后就總是返回無觸摸狀態,除非松開手指再觸摸。TPAD_Scan函數有一個返回值,用於指示電容按鍵狀態,返回值為0表示無觸摸,為1表示有觸摸。
TPAD_Scan函數主要是調用TPAD_Get_MaxVal函數獲取當前電容按鍵捕獲值,該值這里指定在連續觸發模式下取6次掃描的最大值為當前捕獲值,如果是不連續觸發只取三次掃描的最大值。正常情況下,如果無觸摸,當前捕獲值與捕獲參考值相差很小;如果有觸摸,當前捕獲值比捕獲參考值相差較大,此時捕獲值對應圖 334的時間t2。
接下來比較當前捕獲值與無觸摸捕獲參考值和閾值之和的關系,以確定電容按鍵狀態。這里為增強可靠性,還加了當前捕獲值不能超過參考值的10倍的限制條件,因為超過10倍關系幾乎可以認定為出錯情況。
主函數
代碼清單 338 main函數
1 int main(void)
2 {
3 /* 初始化蜂鳴器 */
4 Beep_GPIO_Config();
5
6 /* 初始化調試串口,一般為串口1 */
7 Debug_USART_Config();
8
9 /* 初始化系統滴答定時器 */
10 SysTick_Init();
11
12 /* 初始化電容按鍵 */
13 TPAD_Init();
14
15 while (1) {
16 if (TPAD_Scan(0)) {
17 BEEP_ON;
18 Delay_ms(100);
19 BEEP_OFF;
20 }
21 Delay_ms(50);
22 }
23 }
24
主函數分別調用Beep_GPIO_Config()、Debug_USART_Config()、和SysTick_Init()完成蜂鳴器、調試串口和系統滴答定時器參數。
TPAD_Init函數初始化配置定時器,並獲取無觸摸時的捕獲參考值。
無限循環中調用TPAD_Scan函數完成電容按鍵狀態掃描,指定為不連續觸發方式。如果檢測到有觸摸就讓蜂鳴器響100ms。
33.2.3 下載驗證
使用USB線連接開發板上的"USB TO UART"接口到電腦,電腦端配置好串口調試助手參數。編譯實驗程序並下載到開發板上,程序運行后在串口調試助手可接收到開發板發過來有關定時器捕獲值的參數信息。用手冊觸摸開發板上電容按鍵時可以聽到蜂鳴器響一聲,移開手指后再觸摸,又可以聽到響聲。
33.3 每課一問
1. 談談定時器時鍾頻率對閾值大小的影響。