1电容触摸按键
无手指触摸:上电时,电阻作用下,电容Cs进行充电,直到电容充满,这时候会有一个充电时间Tcs。
有手指触摸:上电时,电阻作用下,电容Cs和Cx进行充电,电容充满时间会变长,得到充电时间Tcx。
注意:充电过程可以看成是一个信号从低电平变为高电平的过程。STM32认为高电平的最低电压值(1.8V)、
2检测电容触摸按键实验过程
没有按下的时候,充电时间为T1(default)。按下TPAD,电容变大,所以充电时间为T2。
我们可以通过检测充电时间来判断是否按下。如果T2-T1大于某个值,就可以判断触摸按键按下。
3.重点函数及调用过程
tpad_reset函数:复位TPAD(放电且开始充电,并清空定时器计数器CNT值)
tpad_get_val函数:获取一次捕获值(复位TPAD,等待捕获上升沿得到充电时间)
tpad_get_maxval函数:多次调用tpad_get_val函数获取充电时间,获取最大的值
tpad_init函数:初始化TPAD,调用tpad_get_val获取电容触摸按键没有按下的默认充电时间值
tpad_scan函数:扫描TPAD,调用tpad_get_maxval获取多次充电中最大充电时间进行判断
tpad_timx_cap_init函数:输入捕获通道初始化
4.电容触摸按键充电时间测量实验实战
4.1 tpad.c
#include "./BSP/TPAD/tpad.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
/* 空载的时候(没有手按下),计数器需要的时间
* 这个值应该在每次开机的时候被初始化一次
*/
//1.空载时计数器时间
volatile uint16_t g_tpad_default_val = 0; /* 空载的时候(没有手按下),计数器需要的时间 */
/*定时器输入边沿捕获*/
static TIM_HandleTypeDef g_timx_cap_chy_handler; /* 定时器x句柄 */
static TIM_IC_InitTypeDef g_timx_ic_cap_chy_handler;
/**
* @brief 初始化触摸按键
* @param psc : 分频系数(值越小, 越灵敏, 最小值为: 1)
* @retval 0, 初始化成功; 1, 初始化失败;
*/
uint8_t tpad_init(uint16_t psc)
{
//1.定义变量,包括存放10个空载充电时间值数值、6个值累加和,以及i,j
uint16_t buf[10];
uint16_t temp;
uint8_t i,j;
//2.调用定时器输入捕获初始化函数
tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc-1); /* 以Ft / (psc - 1) Mhz的频率计数 @Ft = 定时器工作频率 */
//3.连续获取十次值
for( i = 0; i<10;i++)
{
buf[i]= tpad_get_val();
delay_ms(10);
}
//4.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;
}
}
}
//5.去中间六个值的平均值
temp = 0;
for(i=2;i<8;i++)
{
temp += buf[i];
}
g_tpad_default_val = temp / 6;
printf("g_tpad_default_val:%d\r\n", g_tpad_default_val);
if (g_tpad_default_val > (uint16_t)TPAD_ARR_MAX_VAL / 2)
{
return 1; /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */
}
return 0;
}
/**
* @brief 得到定时器捕获值
* @note 如果超时, 则直接返回定时器的计数值
* 我们定义超时时间为: TPAD_ARR_MAX_VAL - 500
* @param 无
* @retval 捕获值/计数值(超时的情况下返回)
*/
static uint16_t tpad_get_val(void) //得到定时器捕获值
{
//1.判断为哪个通道
uint32_t flag =(TPAD_TIMX_CAP_CHY == TIM_CHANNEL_1)? TIM_FLAG_CC1:\
(TPAD_TIMX_CAP_CHY == TIM_CHANNEL_2)? TIM_FLAG_CC2:\
(TPAD_TIMX_CAP_CHY == TIM_CHANNEL_3)? TIM_FLAG_CC3:TIM_FLAG_CC4;
//2.调用复位函数,初始化
tpad_reset();
//3.通道CHY捕获上升沿
while(__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handler,flag)== RESET)
{
if (g_timx_cap_chy_handler.Instance->CNT > TPAD_ARR_MAX_VAL - 500)
{
return g_timx_cap_chy_handler.Instance->CNT; /* 超时了,直接返回CNT的值 */
}
}
//4.返回捕获比较值寄存器值
return TPAD_TIMX_CAP_CHY_CCRX;
}
/**
* @brief 复位TPAD
* @note 我们将TPAD按键看做是一个电容, 当手指按下/不按下时容值有变化
* 该函数将GPIO设置成推挽输出, 然后输出0, 进行放电, 然后再设置
* GPIO为浮空输入, 等待外部大电阻慢慢充电
* @param 无
* @retval 无
*/
static void tpad_reset(void) //复位
{
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET); /* TPAD引脚输出0, 放电 */
delay_ms(5);
g_timx_cap_chy_handler.Instance->SR = 0; /* 清除标记 */
g_timx_cap_chy_handler.Instance->CNT = 0; /* 归零 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = TPAD_GPIO_AF; /* PA5复用为TIM2通道1 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚浮空输入 */
}
//定时器输入捕获初始化设置
static void tpad_timx_cap_init(uint32_t arr, uint16_t psc) //定时器输入捕获初始化
{
GPIO_InitTypeDef gpio_init_struct;
TPAD_GPIO_CLK_ENABLE(); // TPAD引脚 时钟使能
TPAD_TIMX_CAP_CHY_CLK_ENABLE(); // 定时器 时钟使能
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 不带上下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 下拉 */
gpio_init_struct.Alternate = TPAD_GPIO_AF; /* PA5复用为TIM2_CH1 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚复用 */
g_timx_cap_chy_handler.Instance = TPAD_TIMX_CAP; /* 定时器2 */
g_timx_cap_chy_handler.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cap_chy_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_timx_cap_chy_handler.Init.Period = arr; /* 自动重装载值 */
g_timx_cap_chy_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
HAL_TIM_IC_Init(&g_timx_cap_chy_handler);
g_timx_ic_cap_chy_handler.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
g_timx_ic_cap_chy_handler.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 映射到TI1上 */
g_timx_ic_cap_chy_handler.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入分频,不分频 */
g_timx_ic_cap_chy_handler.ICFilter = 0; /* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handler, &g_timx_ic_cap_chy_handler, TPAD_TIMX_CAP_CHY); /* 配置TIM2通道1 */
HAL_TIM_IC_Start(&g_timx_cap_chy_handler,TPAD_TIMX_CAP_CHY); /* 使能输入捕获和定时器 */
}
/**
* @brief 扫描触摸按键
* @param mode :扫描模式
* @arg 0, 不支持连续触发(按下一次必须松开才能按下一次);
* @arg 1, 支持连续触发(可以一直按下)
* @retval 0, 没有按下; 1, 有按下;
*/
uint8_t tpad_scan(uint8_t mode) //TPAD 扫描 函数
{
static uint8_t keyen = 0; /* 0, 可以开始检测; > 0, 还不能开始检测; */
uint8_t res = 0;
uint8_t sample = 3; /* 默认采样次数为3次 */
uint16_t rval;
if (mode)
{
sample = 6; /* 支持连按的时候,设置采样次数为6次 */
keyen = 0; /* 支持连按, 每次调用该函数都可以检测 */
}
rval = tpad_get_maxval(sample);
if (rval > (g_tpad_default_val + TPAD_GATE_VAL)) /* 大于tpad_default_val + TPAD_GATE_VAL,有效 */
{
if (keyen == 0)
{
res = 1; /* keyen==0, 有效 */
}
// printf("r:%d\r\n", rval); /* 输出计数值, 调试的时候才用到 */
keyen = 3; /* 至少要再过3次之后才能按键有效 */
}
if (keyen)keyen--;
return res;
}
/**
* @brief 读取n次, 取最大值
* @param n :连续获取的次数
* @retval n次读数里面读到的最大读数值
*/
static uint16_t tpad_get_maxval(uint8_t n)
{
uint16_t temp = 0;
uint16_t maxval = 0;
while (n--)
{
temp = tpad_get_val(); /* 得到一次值 */
if (temp > maxval){
maxval = temp;
}
}
return maxval;
}
4.2 tpad.h
#ifndef __TPAD_H
#define __TPAD_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
//TPAD引脚 TIM定时器定义
/* 我们使用定时器的输入捕获功能, 对TPAD进行检测
* 这里的输入捕获使用定时器TIM2_CH1, 捕获TPAD按键的输入
*/
//TPAD引脚
#define TPAD_GPIO_PORT GPIOA
#define TPAD_GPIO_PIN GPIO_PIN_5
#define TPAD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define TPAD_GPIO_AF GPIO_AF1_TIM2 /*端口复用为TIM2通道1*/
//TIM2_CH1定义
#define TPAD_TIMX_CAP TIM2
#define TPAD_TIMX_CAP_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define TPAD_TIMX_CAP_CHY_CCRX TIM2->CCR1 /* 通道Y的捕获/比较寄存器 */
#define TPAD_TIMX_CAP_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 */
/* 触摸的门限值, 也就是必须大于 g_tpad_default_val + TPAD_GATE_VAL
* 才认为是有效触摸, 改大 TPAD_GATE_VAL, 可以降低灵敏度, 反之, 则可以提高灵敏度
* 根据实际需求, 选择合适的 TPAD_GATE_VAL 即可
*/
#define TPAD_GATE_VAL 100 //触摸的门限值, 也就是必须大于 g_tpad_default_val + TPAD_GATE_VAL, 才认为是有效触摸
#define TPAD_ARR_MAX_VAL 0xFFFF /* 最大的ARR值, 一般设置为定时器的ARR最大值 */
extern volatile uint16_t g_tpad_default_val; //空载时需要的时间,即手指没按下时
//接口函数,可在其他.c中调用
uint8_t tpad_init(uint16_t psc); //TPAD 初始化 函数
uint8_t tpad_scan(uint8_t mode); //TPAD 扫描 函数
//静态函数,只在tpad.c中调用
static void tpad_reset(void); //复位
static uint16_t tpad_get_val(void); //得到定时器捕获值
static uint16_t tpad_get_maxval(uint8_t n); //读取n次 获取最大值
static void tpad_timx_cap_init(uint32_t arr, uint16_t psc); //定时器输入捕获初始化
#endif
4.3 main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TPAD/tpad.h"
int main(void)
{
uint8_t t = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
tpad_init(8); /* 初始化触摸按键 */
while (1)
{
if (tpad_scan(0)) /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */
{
LED1_TOGGLE(); /* LED1翻转 */
}
t++;
if (t == 15)
{
t = 0;
LED0_TOGGLE(); /* LED0翻转 */
}
delay_ms(10);
}
}