STM32CubeMX学习笔记(24)——通用定时器接口使用(电容按键检测)
时间:2023-05-11 15:07:00
一、电容按键简介
电容器(简称为电容)就是可以容纳电荷的器件,两个金属块中间隔一层绝缘体就可以构成一个最简单的电容。如图 32-1(俯视图),两个金属片之间有绝缘介质,形成电容。这种电容在电路板上很容易实现,一般设计周围的铜板与电路板地面信号连接,这种结构是电容按钮的模型。当电路板形状固定时,电容的容量也相对稳定。
制作电路板时,表面会覆盖一层防腐绝缘层,因此实际电路板设计如图所示 32-2。电路板最上层为绝缘材料,下层为导电铜箔。我们根据电路布线设计确定铜箔的形状,下层一般为 FR-4 板材。金属感应片与地面信号之间有绝缘材料,整个电容器可以等效 Cx。金属感应片一般设计成手指触摸大小。
当电路板未上电时,可以认为是电容 Cx 没有电荷,在电阻的作用下,电容 Cx 在电容充满之前,会有一个充电过程,即 Vc 电压值为 3.3V,充电过程的长度受到电阻 R 阻值和电容 Cx 直接影响容值。但是,我们选择合适的电阻 R 并焊接固定到电路板上后,这个充电时间就基本上不会变了,因为此时电阻 R 电容器已固定 Cx 基本保持不变,没有明显的外部干扰。
现在,让我们来看看当我们用手指触摸它时会发生什么。如图所示 当我们用手指触摸时,金属感应片除了与地面信号形成等效电容外,还与地面信号相同 Cx 此外,它还将与手指形成一个 Cs 等效电容。
此时,整个电容按钮可以容纳比没有手指触摸更多的电荷,可以看作是 Cx 和 Cs 叠加效果。相同电阻 R 情况下,因为电容容值增大了,导致需要更长的充电时间。也就是这个充电时间变长使得我们区分有无手指触摸,也就是电容按键是否被按下。
现在最重要的任务是测量充电时间。可以看出,充电过程是一个信号从低电平到高电平的过程,现在需要时间来改变过程。我们可以使用定时器输入捕获功能来计算充电时间,即设置 TIMx_CH 捕获模式通道输入定时器。这样,首先测量无触摸充电时间作为比较基准,然后定期循环测量充电时间与无触摸充电时间进行比较。如果超过一定阈值,则认为有手指触摸。
图 32-4 为 Vc 随着时间的变化,可以看出电压在没有触摸的情况下变化很快;触摸时,总电容增加,电压变化缓慢。
为了测量充电时间,我们需要设置上升沿触发的定时器输入捕获功能 32-4 中 VH 就是被触发上升沿的电压值,也是 STM32 高电平的最低电压值约为 1.8V。t1 和 t2 可通过定时器捕获/比较寄存器获得。
然而,在测量充电时间之前,我们必须找到制作这个充电过程的方法。以前的分析是,当电路板上电时,会有充电过程。现在我们需要在程序运行中循环检测按钮,因此我们必须能够控制充电过程的生成。我们可以控制它 TIMx_CH 引脚是普通的 GPIO 使用它输出一小段时间的低电平作为电容 Cx 放电,即 Vc 为 0V。当我们重新配置时 TIMx_CH 输入捕获时的电容器 Cx 在电阻 R 充电过程可以在作用下产生。
二、输入捕获
输入捕获模式可用于测量脉冲宽度或频率。STM32F103 除了定时器TIM6、TIM7.其他定时器具有输入捕获功能。
2.1 输入捕获的工作原理
①先将输入捕获设置为上升沿检测,
②当记录发生上升沿时TIMx_CNT(计数器)值
③捕获信号的配置是下降沿捕获,当下降沿到来时捕获
④记录此时的TIMx_CN(计数器)T的值
⑤前后两次TIMx_CNT(计数器)的值差为高电平脉宽。同时,根据TIM我们可以知道高电平脉宽的准确时间。
简单说:
当你设置的捕获开始时,cpu会将计数寄存器
的值复制到捕获比较寄存器
并开始计数。当电平再次发生变化时,这是计数寄存器中的值减去刚刚复制的值的持续时间。您可以设置上升、下降或上升和下降。
2.2 溢出时间计算
t1.检测到高电平并中断。中断时将计数值置0,并开始记录溢出次数N,
其中每计数0xFFFF一次溢出,直到t2时跳转回低电平,
获得最后一次溢出是时候了t二时计数值TIM5CH1_CAPTURE_VAL
则 高电平时间 = 溢出次数x65535 TIM5CH1_CAPTURE_VAL us;溢出总次数占用的时间可以根据定时器初始化时的频率计算,即高电平时间。
若计数器值为 32 bit 那么最大为0xFFFFFFFF
高电平时间:
三、确定引脚
开发板载电容,引脚 PA一、对应通用定时器 TIM5 的通道 2.充电电容的电阻值为 5.1M,电阻的大小决定了电容按钮的充电时间。
四、添加通用定时器TIM5输入捕获功能
查看 STM32CubeMX学习笔记(23)-使用通用定时器接口(输入捕获测量脉宽)
五、编程要点
1. 编时器输入捕获相关函数
2. 测量电容按键空载的充电时间 T1
3. 用手触摸测量电容按钮的充电时间 T2
4. 只需要比较 T2 与 T1 检测按钮是否有手指触摸的时间
几个重要函数:
注:对于不同的平台,主要区别在于定时器的底层IO口初始化
5.1 TPAD_Reset(void)
复位 TPAD
设置 IO 口为推挽输出 0.电容器放电。放电完成后,设置为浮空输入,开始充电。同时,计数器 CNT 设置为 0。
5.2 TPAD_Get_Val(void)
获得捕获值(充电时间)
复位 TPAD,等待捕获上升沿,捕获后,获得定时器值,计算充电时间。
5.3 TPAD_Get_MaxVal(void)
多次调用 TPAD_Get_Val 函数获得充电时间。获得最大值。
5.4 TPAD_Init(void)
初始化 TPAD
系统启动后,初始化输入捕获。 10 次调用 TPAD_Get_Val() 函数获取 10 次充电时间,然后获得中间 N(N=8或者6)次的平均值,作为在没有电容触摸按键下的时候的充电时间缺省值 tpad_default_val。
5.5 TPAD_Scan(void)
扫描 TPAD
调用 TPAD_Get_MaxVal 函数获得多次充电中最大的充电时间 tpad_default_val 如果比较大于某个阈值 tpad_default_val TPAD_GATE_VAL,认为有触摸动作。
六、添加TPAD相关函数
/******************** TPAD 引脚配置参数定义 **************************/ #define TOUCHPAD_TIMx TIM5 #define TOUCHPAD_TIM_RCC_CLK_ENABLE() __HAL_RCC_TIM5_CLK_ENABLE) #define TOUCHPAD_TIM_RCC_CLK_DISABLE() __HAL_RCC_TIM5_CLK_DISABLE() #define TOUCHPAD_GPIO_RCC_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define TOUCHPAD_GPIO_PIN GPIO_PIN_1 #define TOUCHPAD_GPIO GPIOA #define TOUCHPAD_TIM_CHANNEL TIM_CHANNEL_2 #define TOUCHPAD_TIM_FLAG_CCR TIM_FLAG_CC2 //定时器最大计数值 #define TPAD_ARR_MAX_VAL 0XFFFF //保存没按下时定时器计数值 __IO uint16_t tpad_default_val = 0; /**************************************** * * 为电容按键放电 * 清除定时器标志及计数 * *****************************************/ static void TPAD_Reset(void) {
/* 定义IO硬件初始化结构体变量 */ GPIO_InitTypeDef GPIO_InitStruct; /* 使能电容按键引脚对应IO端口时钟 */ TOUCHPAD_GPIO_RCC_CLK_ENABLE(); /* 设置引脚输出为低电平 */ HAL_GPIO_WritePin(TOUCHPAD_GPIO, TOUCHPAD_GPIO_PIN, GPIO_PIN_RESET); //PA.2输出0,放电 /* 设定电容按键对应引脚IO编号 */ GPIO_InitStruct.Pin = TOUCHPAD_GPIO_PIN; /* 设定电容按键对应引脚IO为输出模式 */ GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 设定电容按键对应引脚IO操作速度 */ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 初始化电容按键对应引脚IO */ HAL_GPIO_Init(TOUCHPAD_GPIO, &GPIO_InitStruct); HAL_Delay(5); __HAL_TIM_SET_COUNTER(&htim5,0); // 清零定时器计数 __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE|TIM_FLAG_CC2);//清除中断标志 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(TOUCHPAD_GPIO, &GPIO_InitStruct); HAL_TIM_IC_Start(&htim5,TOUCHPAD_TIM_CHANNEL); } /**************************************************** * * 得到定时器捕获值 * 如果超时,则直接返回定时器的计数值. * *****************************************************/ static uint16_t TPAD_Get_Val(void) {
TPAD_Reset(); while(__HAL_TIM_GET_FLAG(&htim5,TOUCHPAD_TIM_FLAG_CCR)==RESET) {
uint16_t count; count=__HAL_TIM_GET_COUNTER(&htim5); if(count>(TPAD_ARR_MAX_VAL-500)) return count;//超时了,直接返回CNT的值 }; return HAL_TIM_ReadCapturedValue(&htim5,TOUCHPAD_TIM_CHANNEL); } /**************************************************** * * 读取n次,取最大值 * n:连续获取的次数 * 返回值:n次读数里面读到的最大读数值 * *****************************************************/ static uint16_t TPAD_Get_MaxVal(uint8_t n) {
uint16_t temp=0; uint16_t res=0; while(n--) {
temp=TPAD_Get_Val();//得到一次值 if(temp>res)res=temp; }; return res; } /******************************************************** * * 初始化触摸按键 * 获得空载的时候触摸按键的取值. * 返回值:0,初始化成功;1,初始化失败 * *********************************************************/ uint8_t TPAD_Init(void) {
uint16_t buf[10]; uint32_t temp=0; uint8_t j,i; /* 以1MHz的频率计数 */ MX_TIM5_Init(); HAL_TIM_IC_Start(&htim5,TOUCHPAD_TIM_CHANNEL); /* 连续读取10次 */ for(i=0;i<10;i++) {
buf[i]=TPAD_Get_Val(); HAL_Delay(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; /* 取中间的6个数据进行平均 */ for(i=2;i<8;i++)temp+=buf[i]; tpad_default_val=temp/6; printf("tpad_default_val:%d\r\n",tpad_default_val); /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */ if(tpad_default_val>TPAD_ARR_MAX_VAL/2)return 1; return 0; } /******************************************************************************* * * 扫描触摸按键 * mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下) * 返回值:0,没有按下;1,有按下; * *******************************************************************************/ //阈值:捕获时间必须大于(tpad_default_val + TPAD_GATE_VAL),才认为是有效触摸. #define TPAD_GATE_VAL 100 uint8_t TPAD_Scan(uint8_t mode) {
//0,可以开始检测;>0,还不能开始检测 static uint8_t keyen=0; //扫描结果 uint8_t res=0; //默认采样次数为3次 uint8_t sample=3; //捕获值 uint16_t rval; if(mode) {
//支持连按的时候,设置采样次数为6次 sample=6; //支持连按 keyen=0; } /* 获取当前捕获值(返回 sample 次扫描的最大值) */ rval=TPAD_Get_MaxVal(sample); /* printf打印函数调试使用,用来确定阈值TPAD_GATE_VAL,在应用工程中应注释掉 */ // printf("scan_rval=%d\n",rval); //大于tpad_default_val+TPAD_GATE_VAL,且小于10倍tpad_default_val,则有效 if(rval>(tpad_default_val+TPAD_GATE_VAL)&&rval<(10*tpad_default_val)) {
//keyen==0,有效 if(keyen==0) {
res=1; } keyen=3; //至少要再过3次之后才能按键有效 } if(keyen) {
keyen--; } return res; }
七、修改main函数
/** * @brief The application entry point. * @retval int */
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_USART1_UART_Init();
TPAD_Init();
/* USER CODE BEGIN 2 */
printf("This is TPAD test...\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(TPAD_Scan(0))
{
printf("key down\r\n");
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
八、查看打印
串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用
按下电容键,串口会打印信息,可根据 printf("scan_rval=%d\n",rval);
打印的 scan_rval
调整 TPAD_GATE_VAL
的大小,来调整按键灵敏度
九、注意事项
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2021 年 3 月 29 日
• 参考:STM32的电容触摸按键实验
【常用模块】电容触摸按键模块(原理讲解、STM32实例操作)