目录
一、电容触摸按键简介
电容式感应触摸按键可以穿透绝缘材料外壳 8mm (玻璃、塑料等等)以上,准确无误地侦测到手指的有效触摸。它普遍使用在开关,各类电器上面。比如抽油烟机的按键,如下图。
1.RC充电充电原理
首先电容和电阻串联在V1上,当按键按下的瞬间,经过R电阻,给CX电容充电,等待时间足够让CX电容充满电后,充电后的CX的电压Vt等于V1的电压,之后就没有电流经过R。下面右图就是充电的时间,当CX一直充电,Vt和V1的电压差不断缩小,所经过的电流也越来越小。
2.RC电路充放电公式
V0:电容上的初始电压(一般为0);
V1:电容最终可充到或放到的电压值;
Vt:t时刻电容上的电压值
结论:同样的条件下,电容值C跟时间值t成正比关系,电容越大,充电满的时间越长。
3.检测电容触摸按键过程
①:TPAD引脚设置为推挽输出,输出0,实现电容放电0;
②:TPAD引脚设置为浮空输入(IO复位后的状态),电容开始充电;
③:同时开启TPAD引脚的输入捕获开始捕获;
④:等待充电完成(充电到满Vx,检测上升沿);
⑤:计算充电时间。
没有按下的时候,充电时间为T1。按下TPAD,电容变大,所以充电时间为T2。我们可以通过检测充放电时间,来判断是否按下。如果T2-T1大于某个值,就可以判断有按键按下。
二、程序思路
1.TPAD_Init:TIM2_CH1_Cap_Init()初始化输入捕获,10次调用TPAD_Get_Val(),取中间6次的平均值赋值给TPAD_default_val变量;
2.TPAD_Reset:初始化PA5为推挽输出,输出0,放电,并且初始化PA5为浮空输入,等待按下充电。
3.TPAD_Get_Val:刚开始就TPAD_Reset(),等待上升沿,之后捕获上升沿,返回捕获寄存器的值。
4.TPAD_Scan:调用TPAD_Get_MaxVal,获得N次的平均值
三、程序实现
1.tpad.h
#ifndef __TPAD_H
#define __TPAD_H
#include "sys.h"
extern vu16 TPAD_Default_Val;
void TPAD_Reset(void);
void TIM2_CH1_Cap_Init(uint32_t arr, uint16_t psc);
uint16_t TPAD_Get_Val(void);
uint16_t TPAD_Get_Max_Val(uint8_t n);
uint16_t TPAD_Init(uint8_t psc);
uint8_t TPAD_Scan(uint8_t mode);
#endif
2.tpad.c
#include "tpad.h"
#include "delay.h"
#define TPAD_ARR_MAX_VAL 0XFFFFFFFF // 最大的ARR的值,并且STM32429的TIM2是32位定时器
vu16 TPAD_Default_Val = 0; // 空载的时候(没有手按下),计数器需要的时间
TIM_HandleTypeDef TIM2_IC_Struct; // TIM句柄
TIM_IC_InitTypeDef TIM2_IC_CHannel1_Define; // TIM_IC句柄
GPIO_InitTypeDef GPIO_Struct;
// 1.初始化触摸按键
// 2.获得空载的时候触摸按键取值
// return:1,初始化成功;0,初始化失败
uint16_t TPAD_Init(uint8_t psc)
{
uint8_t i,j;
uint16_t arr[10];
uint16_t temp = 0;
TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL, psc - 1); // 公式里需要减1
for ( i = 0; i < 10; i++) // 取10次TPAD_Get_Val的值
{
arr[i] = TPAD_Get_Val();
delay_ms(10);
}
for ( i = 0; i < 9; i++) // 把取出来的10次捕获值按升序排序
{
for ( j = i + 1; j < 10; j++)
{
if (arr[i] > arr[j])
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
for ( i = 2; i < 8; i++) // 取中间的6个数
{
temp += arr[i];
}
TPAD_Default_Val = temp / 6; // 取平均值
if (TPAD_Default_Val > TPAD_ARR_MAX_VAL / 2) // 如果默认值大于最大值/2 就说明Default的值不正常
{
return 0;
}
return 1;
}
// 先复位一次,然后让电容放电,并清除定时器的计数值
void TPAD_Reset()
{
GPIO_Struct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_Struct.Pin = GPIO_PIN_5; // PA5
GPIO_Struct.Pull = GPIO_PULLDOWN; // 下拉
GPIO_Struct.Speed = GPIO_SPEED_FAST; // 高速
HAL_GPIO_Init(GPIOA, &GPIO_Struct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // PA5=0,给PA5放电
delay_ms(20);
__HAL_TIM_CLEAR_FLAG(&TIM2_IC_Struct, TIM_FLAG_CC1 | TIM_FLAG_UPDATE); // 清除标志位
__HAL_TIM_SET_COUNTER(&TIM2_IC_Struct, 0); // 清除计数器的值
GPIO_Struct.Mode = GPIO_MODE_AF_PP; // 推挽复用输出
GPIO_Struct.Pull = GPIO_NOPULL; // 不带上下拉
GPIO_Struct.Alternate = GPIO_AF1_TIM2; // 复用为TIM2
HAL_GPIO_Init(GPIOA, &GPIO_Struct);
}
// 1.得到定时器捕获值
// 2.如果超时,直接返回定时器的计数值
// 返回值:捕获值或计数值(超时的时候返回)
uint16_t TPAD_Get_Val(void)
{
TPAD_Reset();
while (__HAL_TIM_GET_FLAG(&TIM2_IC_Struct, TIM_FLAG_CC1) == RESET) // 等待捕获上升沿,把TIM2计数器的值赋值给TIM_FLAG_CC1标签
{
if (__HAL_TIM_GET_COUNTER(&TIM2_IC_Struct) > TPAD_ARR_MAX_VAL - 500) // 判断TIM2的值是否超过32位计数器的最大值,-500是留缓存一下
{
return __HAL_TIM_GET_COUNTER(&TIM2_IC_Struct); // 超时直接返回CNT的值
}
}
return HAL_TIM_ReadCapturedValue(&TIM2_IC_Struct, TIM_CHANNEL_1); // 不超时就返回捕捉TIM2_CH1的值
}
// 1.读取n次,取最大的值(n:连续捕获的次数)
// 返回值:n次里面读取的最大的数值
uint16_t TPAD_Get_Max_Val(uint8_t n)
{
uint16_t rest = 0; // 最大捕获值
uint16_t temp; // 记录当前捕获值或计数值(超时时返回)
uint8_t ok = 0; // 大于默认值的5/4才算有效的次数
uint8_t Cnt_Number = n * 2/3; // 至少2/3*n的有效个触摸,才算有效
while (n--)
{
temp = TPAD_Get_Val();
if (temp > TPAD_Default_Val*5/4) // 至少大于默认值的5/4才算有效
{
ok++;
}
if (temp > rest) // 判断当前值捕获值是否大于最大值
{
rest = temp;
}
}
if (ok >= Cnt_Number) // 至少2/3*n的有效个触摸,默认值的5/4才能返回最大值
{
return rest;
}
else
{
return 0; // 没有找到最大捕获值,返回0
}
}
// 扫描按键
// mode(0):不支持连续触发(按下一次后必须松开才能按下一次)
// mode(1):支持连续触发
// return 0 or 1
uint8_t TPAD_Scan(uint8_t mode)
{
static uint8_t key = 0; // 0,可以检测;>0,不可以检测
uint8_t sample = 3; // 默认采样次数是3
uint16_t TPAD_MaxValue = 0; // 获得TPAD的最大采样值
uint8_t rest = 0; // 返回值 0:没有按下,1:按下了
if (mode)
{
sample = 6; // 支持连续按键的时候,设置采样次数位6
key = 0; // 连按可以检测
}
TPAD_MaxValue = TPAD_Get_Max_Val(sample); // 在sample次中取得最大采样值
if (TPAD_MaxValue > (TPAD_Default_Val * 4/3) && TPAD_MaxValue < (TPAD_Default_Val * 10)) // 判断TPAD最大采样值是否超过TPAD_Default_Val的三分之四,并且小于TPAD_Default_Val的10倍才有效
{
if (key == 0) // 可以进行检测
{
rest = 1; // 有按下
}
printf("最大采样值位:%d\r\n", TPAD_MaxValue);
key = 3; // 至少按下三次,按键才有效
}
if (key) // 判断key是否大于0的数,是则自减1
{
key--;
}
return rest;
}
void TIM2_CH1_Cap_Init(uint32_t arr, uint16_t psc)
{
TIM2_IC_Struct.Instance = TIM2; // 通用定时器2
TIM2_IC_Struct.Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式为向上计数
TIM2_IC_Struct.Init.Period = arr; // 自动重装载值(ARR)
TIM2_IC_Struct.Init.Prescaler = psc; // 分频系数
HAL_TIM_IC_Init(&TIM2_IC_Struct);
TIM2_IC_CHannel1_Define.ICFilter = 0; // 捕获滤波器为0
TIM2_IC_CHannel1_Define.ICPolarity = TIM_ICPOLARITY_RISING; // 捕获极性为上升沿捕获
TIM2_IC_CHannel1_Define.ICPrescaler = TIM_ICPSC_DIV1; // 无分频系数
TIM2_IC_CHannel1_Define.ICSelection = TIM_ICSELECTION_DIRECTTI; // 映射TI1上
HAL_TIM_IC_ConfigChannel(&TIM2_IC_Struct, &TIM2_IC_CHannel1_Define, TIM_CHANNEL_1);
HAL_TIM_IC_Start(&TIM2_IC_Struct, TIM_CHANNEL_1); // 开启TIM_IC
}
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
__HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA使能
__HAL_RCC_TIM2_CLK_ENABLE(); // TIM2使能
GPIO_Struct.Alternate = GPIO_AF1_TIM2;
GPIO_Struct.Mode = GPIO_MODE_AF_PP; // 推挽复用输出
GPIO_Struct.Pin = GPIO_PIN_5;
GPIO_Struct.Pull = GPIO_NOPULL; // 不带上下拉
GPIO_Struct.Speed = GPIO_SPEED_FAST; // 高速
HAL_GPIO_Init(GPIOA, &GPIO_Struct);
}
}
3.main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "tpad.h"
#include "led.h"
int main(void)
{
uint8_t t = 0;
HAL_Init();
Stm32_Clock_Init(360, 25, 2, 8);
delay_init(180);
uart_init(115200);
LED_Init();
TPAD_Init(2);
while (1)
{
if (TPAD_Scan(0)) // 判断是否捕获到一次上升沿
{
LED1 = !LED1;
}
t++;
if (t == 15) // Scan扫描了15次就反转一次LED0
{
LED0 = !LED0;
}
delay_ms(10);
}
}