正点原子stm32f429 pcb_正点原子【STM32-F407探索者】第十六章 电容触摸按键实验
1)資料下載:點擊資料即可下載
2)對正點原子Linux感興趣的同學可以加群討論:935446741
3)關注正點原子公眾號,獲取最新資料更新
http://weixin.qq.com/r/hEhUTLbEdesKrfIv9x2W (二維碼自動識別)
上一章,我們介紹了 STM32F4 的輸入捕獲功能及其使用。這一章,我們將向大家介紹如
何通過輸入捕獲功能,來做一個電容觸摸按鍵。在本章中,我們將用 TIM2 的通道 1(PA5)來
做輸入捕獲,并實現一個簡單的電容觸摸按鍵,通過該按鍵控制 DS1 的亮滅。從本章分為如下
幾個部分:
16.1 電容觸摸按鍵簡介
16.2 硬件設計
16.3 軟件設計
16.4 下載驗證
16.1 電容觸摸按鍵簡介
觸摸按鍵相對于傳統的機械按鍵有壽命長、占用空間少、易于操作等諸多優點。大家看看
如今的手機,觸摸屏、觸摸按鍵大行其道,而傳統的機械按鍵,正在逐步從手機上面消失。本
章,我們將給大家介紹一種簡單的觸摸按鍵:電容式觸摸按鍵。
我們將利用探索者 STM32F4 開發板上的觸摸按鍵(TPAD),來實現對 DS1 的亮滅控制。
這里 TPAD 其實就是探索者 STM32F4 開發板上的一小塊覆銅區域,實現原理如圖 16.1.1 所示:
圖 16.1.1 電容觸摸按鍵原理這里我們使用的是檢測電容充放電時間的方法來判斷是否有觸摸,圖中 R 是外接的電容充
電電阻,Cs 是沒有觸摸按下時 TPAD 與 PCB 之間的雜散電容。而 Cx 則是有手指按下的時候,
手指與 TPAD 之間形成的電容。圖中的開關是電容放電開關(由實際使用時,由 STM32F4 的
IO 代替)。
先用開關將 Cs(或 Cs+Cx)上的電放盡,然后斷開開關,讓 R 給 Cs(或 Cs+Cx)充電,
當沒有手指觸摸的時候,Cs 的充電曲線如圖中的 A 曲線。而當有手指觸摸的時候,手指和 TPAD
之間引入了新的電容 Cx,此時 Cs+Cx 的充電曲線如圖中的 B 曲線。從上圖可以看出,A、B
兩種情況下,Vc 達到 Vth 的時間分別為 Tcs 和 Tcs+Tcx。
其中,除了 Cs 和 Cx 我們需要計算,其他都是已知的,根據電容充放電公式:
Vc=V0*(1-e^(-t/RC))
其中 Vc 為電容電壓,V0 為充電電壓,R 為充電電阻,C 為電容容值,e 為自然底數,t 為
充電時間。根據這個公式,我們就可以計算出 Cs 和 Cx。利用這個公式,我們還可以把探索者
開發板作為一個簡單的電容計,直接可以測電容容量了,有興趣的朋友可以搗鼓下。
在本章中,其實我們只要能夠區分 Tcs 和 Tcs+Tcx,就已經可以實現觸摸檢測了,當充電時間在 Tcs 附近,就可以認為沒有觸摸,而當充電時間大于 Tcs+Tx 時,就認為有觸摸按下(Tx
為檢測閥值)。
本章,我們使用 PA5(TIM2_CH1)來檢測 TPAD 是否有觸摸,在每次檢測之前,我們先配置
PA5 為推挽輸出,將電容 Cs(或 Cs+Cx)放電,然后配置 PA5 為浮空輸入,利用外部上拉電阻
給電容 Cs(Cs+Cx)充電,同時開啟 TIM2_CH1 的輸入捕獲,檢測上升沿,當檢測到上升沿的時
候,就認為電容充電完成了,完成一次捕獲檢測。
在 MCU 每次復位重啟的時候,我們執行一次捕獲檢測(可以認為沒觸摸),記錄此時的值,
記為 tpad_default_val,作為判斷的依據。在后續的捕獲檢測,我們就通過與 tpad_default_val 的
對比,來判斷是不是有觸摸發生。
關于輸入捕獲的配置,在上一章我們已經有詳細介紹了,這里我們就不再介紹。至此,電
容觸摸按鍵的原理介紹完畢。
16.2 硬件設計
本實驗用到的硬件資源有:
1) 指示燈 DS0 和 DS1
2) 定時器 TIM2
3) 觸摸按鍵 TPAD
前面兩個之前均有介紹,我們需要通過 TIM2_CH1(PA5)采集 TPAD 的信號,所以本實
驗需要用跳線帽短接多功能端口(P12)的 TPAD 和 ADC,以實現 TPAD 連接到 PA5。如圖 16.2.1
所示:
圖 16.2.1 TPAD 與 STM32F4 連接原理圖
硬件設置(用跳線帽短接多功能端口的 ADC 和 TPAD 即可)好之后,下面我們開始軟件
設計。
16.3 軟件設計
軟件設計方面,相比上一實驗,我們刪掉了timer.c和timer.h文件,同時新建了tpad.c和tpad.h
文件。因為 tpad 我們也是使用的定時器輸入捕獲來實現,所以我們相比上個實驗并沒有增加任
何庫函數相關的文件。
接下來我們看看 tpad.c 文件代碼:
#include "tpad.h"
#include "delay.h"
#include "usart.h"
TIM_HandleTypeDef TIM2_Handler; //定時器 2 句柄
#define TPAD_ARR_MAX_VAL 0XFFFFFFFF //最大的 ARR 值(TIM2 是 32 位定時器)
vu16 tpad_default_val=0;
//空載的時候(沒有手按下),計數器需要的時間
//初始化觸摸按鍵
//獲得空載的時候觸摸按鍵的取值.
//psc:分頻系數,越小,靈敏度越高.
//返回值:0,初始化成功;1,初始化失敗
u8 TPAD_Init(u8 psc)
{
u16 buf[10];
u16 temp;
u8 j,i;
TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL,psc-1);//設置分頻系數
for(i=0;i<10;i++)//連續讀取 10 次
{
buf[i]=TPAD_Get_Val();
delay_ms(10);
}
for(i=0;i<9;i++)//排序
{
for(j=i+1;j<10;j++)
{
if(buf[i]>buf[j])//升序排列
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
temp=0;
for(i=2;i<8;i++)temp+=buf[i];//取中間的 8 個數據進行平均
tpad_default_val=temp/6;
printf("tpad_default_val:%drn",tpad_default_val);
if(tpad_default_val>(vu16)TPAD_ARR_MAX_VAL/2)return 1;
//初始化遇到超過 TPAD_ARR_MAX_VAL/2 的數值,不正常!
return 0;
}
//復位一次
//釋放電容電量,并清除定時器的計數值
void TPAD_Reset(void)
{
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_5; //PA5
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽輸出
GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET); //PA5 輸出 0,放電
delay_ms(5);
__HAL_TIM_CLEAR_FLAG(&TIM2_Handler,TIM_FLAG_CC1|
TIM_FLAG_UPDATE);
//清除標志位
__HAL_TIM_SET_COUNTER(&TIM2_Handler,0);
//計數器值歸 0
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
//推挽復用
GPIO_Initure.Pull=GPIO_NOPULL;
//不帶上下拉
GPIO_Initure.Alternate=GPIO_AF1_TIM2;
//PA5 復用為 TIM2 通道 1
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}
//得到定時器捕獲值
//如果超時,則直接返回定時器的計數值.
//返回值:捕獲值/計數值(超時的情況下返回)
u16 TPAD_Get_Val(void)
{
TPAD_Reset();
while(__HAL_TIM_GET_FLAG(&TIM2_Handler,TIM_FLAG_CC1)==RESET)
//等待捕獲上升沿
{
if(__HAL_TIM_GET_COUNTER(&TIM2_Handler)>TPAD_ARR_MAX_VAL-
500)
return __HAL_TIM_GET_COUNTER(&TIM2_Handler);
//超時了,直接返回 CNT 的值
};
return HAL_TIM_ReadCapturedValue(&TIM2_Handler,TIM_CHANNEL_1);
}
//讀取 n 次,取最大值
//n:連續獲取的次數
//返回值:n 次讀數里面讀到的最大讀數值
u16 TPAD_Get_MaxVal(u8 n)
{
u16 temp=0;
u16 res=0;
u8 lcntnum=n*2/3;//至少 2/3*n 的有效個觸摸,才算有效
u8 okcnt=0;
while(n--)
{
temp=TPAD_Get_Val();//得到一次值
if(temp>(tpad_default_val*5/4))okcnt++;//至少大于默認值的 5/4 才算有效
if(temp>res)res=temp;
}
if(okcnt>=lcntnum)return res;//至少 2/3 的概率,要大于默認值的 5/4 才算有效
else return 0;
}
//掃描觸摸按鍵
//mode:0,不支持連續觸發(按下一次必須松開才能按下一次);
//1,支持連續觸發(可以一直按下)
//返回值:0,沒有按下;1,有按下;
u8 TPAD_Scan(u8 mode)
{
static u8 keyen=0; //0,可以開始檢測;>0,還不能開始檢測
u8 res=0;
u8 sample=3; //默認采樣次數為 3 次
u16 rval;
if(mode)
{
sample=6;
//支持連按的時候,設置采樣次數為 6 次
keyen=0; //支持連按
}
rval=TPAD_Get_MaxVal(sample);
if(rval>(tpad_default_val*4/3)&&rval<(10*tpad_default_val))
//大于 tpad_default_val+(1/3)*tpad_default_val,且小于 10 倍 tpad_default_val,則有效
{
if(keyen==0)res=1; //keyen==0,有效
//printf("r:%drn",rval);
keyen=3;
//至少要再過 3 次之后才能按鍵有效
}
if(keyen)keyen--;
return res;
}
//定時器 2 通道 1 輸入捕獲配置
//arr:自動重裝值(TIM2 是 32 位的!!)
//psc:時鐘預分頻數
void TIM2_CH1_Cap_Init(u32 arr,u16 psc)
{
TIM_IC_InitTypeDef TIM2_CH1Config;
TIM2_Handler.Instance=TIM2;
//通用定時器 3
TIM2_Handler.Init.Prescaler=psc;
//分頻
TIM2_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;
//向上計數器
TIM2_Handler.Init.Period=arr;
//自動裝載值
TIM2_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&TIM2_Handler);
TIM2_CH1Config.ICPolarity=TIM_ICPOLARITY_RISING;
//上升沿捕獲
TIM2_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到 TI1 上
TIM2_CH1Config.ICPrescaler=TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM2_CH1Config.ICFilter=0; //配置輸入濾波器,不濾波
HAL_TIM_IC_ConfigChannel(&TIM2_Handler,&TIM2_CH1Config,
TIM_CHANNEL_1);//配置 TIM2 通道 1
HAL_TIM_IC_Start(&TIM2_Handler,TIM_CHANNEL_1); //開始捕獲 TIM2 的通道 1
}
//定時器 2 底層驅動,時鐘使能,引腳配置
//此函數會被 HAL_TIM_IC_Init()調用
//htim:定時器 2 句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM2_CLK_ENABLE();
//使能 TIM2 時鐘
__HAL_RCC_GPIOA_CLK_ENABLE();
//開啟 GPIOA 時鐘
GPIO_Initure.Pin=GPIO_PIN_5;
//PA5
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
//推挽復用
GPIO_Initure.Pull=GPIO_NOPULL; //不帶上下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
GPIO_Initure.Alternate=GPIO_AF1_TIM2;
//PA5 復用為 TIM2 通道 1
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}
函數TIM2_CH1_Cap_Init和上一章輸入捕獲實驗中函數TIM5_CH1_Cap_Init的配置過程幾
乎是一模一樣的,不同的是上一章實驗開 TIM5_CH1_Cap_Init 函數最后調用的是函數
HAL_TIM_IC_Start_IT,使能輸入捕獲通道的同事開啟了輸入捕獲中斷,而該函數最后調用的
函數是 HAL_TIM_IC_Start,只是開啟了輸入捕獲通道,并沒有開啟輸入捕獲中斷。
函數 HAL_TIM_IC_MspInit 是輸入捕獲通用 MSP 回調函數,該函數的作用是使能定時器
和 GPIO 時鐘,配置 GPIO 復用映射關系。該函數功能和輸入捕獲實驗中該函數作用基本類似。
函數 TPAD_Get_Val 用于得到定時器的一次捕獲值。該函數先調用 TPAD_Reset,將電容放
電,同時設置通過程序__HAL_TIM_SET_COUNTER(&TIM2_Handler,0)將計數值 TIM2_CNT 設
置為 0,然后死循環等待發生上升沿捕獲(或計數溢出),將捕獲到的值(或溢出值)作為返回
值返回。
函數 TPAD_Init 用于初始化輸入捕獲,并獲取默認的 TPAD 值。該函數有一個參數,用來
傳遞分頻系數,其實是為了配置 TIM2_CH1_Cap_Init 的計數周期。在該函數中連續 10 次讀取
TPAD 值,將這些值升序排列后取中間 6 個值再做平均(這樣做的目的是盡量減少誤差),并賦
值給 tpad_default_val,用于后續觸摸判斷的標準。
函數 TPAD_Scan 用于掃描 TPAD 是否有觸摸,該函數的參數 mode,用于設置是否支持連
續觸發。返回值如果是 0,說明沒有觸摸,如果是 1,則說明有觸摸。該函數同樣包含了一個靜
態變量,用于檢測控制,類似第七章的 KEY_Scan 函數。所以該函數同樣是不可重入的。在函
數中,我們通過連續讀取 3 次(不支持連續按的時候)TPAD 的值,取最大值和 tpad_default_val*4/3
比較,如果大于則說明有觸摸,如果小于,則說明無觸摸。其中 tpad_default_val 是我們在調用
TPAD_Init 函數的時候得到的值,然后取其 4/3 為門限值。該函數,我們還做了一些其他的條件
限制,讓觸摸按鍵有更好的效果,這個就請大家看代碼自行參悟了。
函數 TPAD_Reset 顧名思義,是進行一次復位操作。先設置 PA5 輸出低電平,電容放電,
同時清除中斷標志位并且計數器值清零,然后配置 PA5 為復用功能浮空輸入,利用外部上拉電
阻給電容 Cs(Cs+Cx)充電,同時開啟 TIM2_CH1 的輸入捕獲。
函數 TPAD_Get_MaxVal 就非常簡單了,它通過 n 次調用函數 TPAD_Get_Val 采集捕獲值,
然后進行比較后獲取 n 次采集值中的最大值。
tpad.h 頭文件部分代碼比較簡單,這里不做介紹。
接下來我們看看主函數代碼如下:
int main(void)
{
u8 t=0;
HAL_Init(); //初始化 HAL 庫
Stm32_Clock_Init(336,8,2,7); //設置時鐘,168Mhz
delay_init(168); //初始化延時函數
uart_init(115200); //初始化 USART
LED_Init();
//初始化 LED
TPAD_Init(8); //初始化觸摸按鍵
while(1)
{
if(TPAD_Scan(0)) //成功捕獲到了一次上升沿(此函數執行時間至少 15ms)
{
LED1=!LED1;
//LED1 取反
}
t++;
if(t==15)
{
t=0; LED0=!LED0;
//LED0 取反,提示程序正在運行
}
delay_ms(10);
}
}
該 main 函數比較簡單,TPAD_Init(8)函數執行之后,就開始觸摸按鍵的掃描,當有觸摸的
時候,對 DS1 取反,而 DS0 則有規律的間隔取反,提示程序正在運行。注意在修改 main 函數
之后,還需要在 main.c 里面添加 tpad.h 頭文件,否則會報錯哦。
這里還要提醒一下大家,不要把 uart_init(115200);去掉,因為在 TPAD_Init 函數里面,我們
有用到 printf,如果你去掉了 uart_init,就會導致 printf 無法執行,從而死機。
至此,我們的軟件設計就完成了。
16.4 下載驗證
在完成軟件設計之后,將我們將編譯好的文件下載到探索者 STM32F4 開發板上,可以看
到 DS0 慢速閃爍,此時,我們用手指觸摸 ALIENTEK 探索者 STM32F4 開發板上的 TPAD(右
下角的白色頭像),就可以控制 DS1 的亮滅了。不過你要確保 TPAD 和 ADC 的跳線帽連接上了
哦!如圖 16.4.1 所示:
圖 16.4.1 觸摸區域和跳線帽短接方式示意圖同時大家可以打開串口調試助手,每次復位的時候,會收到 tpad_default_val 的值,一般為
250 左右。
總結
以上是生活随笔為你收集整理的正点原子stm32f429 pcb_正点原子【STM32-F407探索者】第十六章 电容触摸按键实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 指针变量和引用变量的区别_指针Ⅰ--变量
- 下一篇: python怎么添加列_如何将列添加到D