|
第二十二章 电容触摸按键实验
1)实验平台:正点原子STM32H7R7开发板
2)章节摘自【正点原子】STM32H7R7开发指南 V1.1
3)购买链接: https://detail.tmall.com/item.htm?id=820823382459
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:756580169
上一章,我们介绍了 STM32H7R7 的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用 TIM3的通道 3( PB0)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制 DS1 的亮灭。从本章分为如 下几个部分:
22.1 电容触摸按键简介
22.2 硬件设计
22.3 程序设计
22.4 下载验证
22.1 电容触摸按键简介
触摸按键相对于传统的机械按键有寿命长、占用空间少、易于操作等诸多优点。大家看看如今的手机,触摸屏、触摸按键大行其道,而传统的机械按键,正在逐步从手机上面消失。本章,我们将给大家介绍一种简单的触摸按键:电容式触摸按键。我们将利用 STM32 开发板上的触摸按键( TPAD),来实现对 DS1 的亮灭控制。这里 TPAD 其实就是 STM32 开发板上的一小块覆铜区域,实现原理如图 22.1.1 所示:
图22.1.1 电容触摸按键原理
这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图中 R 是外接的电容充电电阻, Cs 是没有触摸按下时 TPAD 与 PCB 之间的杂散电容。而 Cx 则是有手指按下的时候,手指与 TPAD 之间形成的电容。图中的开关是电容放电开关(由实际使用时,由 STM32H7R7的 IO 代替)。
先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R 给 Cs(或 Cs+Cx)充电,当没有手指触摸的时候, Cs 的充电曲线如图中的 A 曲线。而当有手指触摸的时候,手指和 TPAD之间引入了新的电容 Cx,此时 Cs+Cx 的充电曲线如图中的 B 曲线。从上图可以看出, A、 B两种情况下, Vc 达到 Vth 的时间分别为 Tcs 和 Tcs+Tcx。其中,除了 Cs 和 Cx 我们需要计算,其他都是已知的,根据电容充放电公式:
V_c=V_0×(1-e^(-t/RC)) 其中 Vc 为电容电压, V0 为充电电压, R 为充电电阻, C 为电容容值, e 为自然底数, t 为充电时间。根据这个公式,我们就可以计算出 Cs 和 Cx。利用这个公式,我们还可以把开发板作为一个简单的电容计,直接可以测电容容量了,有兴趣的朋友可以捣鼓下。
在本章中,其实我们只要能够区分 Tcs 和 Tcs+Tcx,就已经可以实现触摸检测了,当充电时间在 Tcs 附近,就可以认为没有触摸,而当充电时间大于 Tcs+Tx 时,就认为有触摸按下( Tx为检测阀值)。
本章,我们使用 PB0(TIM3_CH3)来检测 TPAD 是否有触摸,在每次检测之前,我们先配置PB0 为推挽输出,将电容 Cs(或 Cs+Cx)放电,然后配置 PB0 为复用功能浮空输入,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启 TIM3_CH3 的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。
在 MCU 每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为 g_tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与 g_tpad_default_val的对比,来判断是不是有触摸发生。
关于输入捕获的配置,在上一章我们已经有详细介绍了,这里我们就不再介绍。至此,电容触摸按键的原理介绍完毕。
22.2 硬件设计
1. 例程功能
1,利用开发板板载的电容触摸按键(右下角白色LOGO,即TPAD),通过TIM3_CH3(PB0)对电容触摸按键的检测,实现对DS1的控制, 下载本代码后,我们通过按压开发板右下角的TPAD按钮,就可以控制DS1的亮灭了。
2,LED0闪烁 ,提示程序运行。
2. 硬件资源
1)LED灯
LED0:LED0 – PD14
LED1:LED1 – PC0
2)TPAD电容触摸按键(右下角LOGO,即TPAD,连接在PB0)
3)定时器3(TIM3),TIM3的通道3(TIM3_CH3,连接在PB0上面)
3. 原理图
我们需要通过TIM3_CH3(PB0)采集TPAD的信号,所以本实验需要用跳线帽短接多功能端口(P14)的TPAD和ADC,以实现TPAD连接到PB0,连接如图22.2.1所示:
图22.2.1 TPAD与STM32H7R7连接原理图
硬件设置(用跳线帽短接多功能端口的ADC和TPAD即可)好之后,下面我们开始软件设计。
22.3 程序设计
我们在基本定时器一节已经学习过定时器的输入捕获功能,这里我们可以类似地,用定时器3来实现对TPAD引脚上的电平状态的捕获功能。本实验用到的HAL库驱动请回顾基本定时器实验的介绍。下面直接从程序流程图开始介绍。
22.3.1 程序解析
TPAD 可以看作《实验 9-3 通用定时器输入捕获实验》 的一个应用案例,相关的 HAL 库函数函数在定时器章节我们已经介绍过了,所以这里就不重复介绍了,大家回过头复习一下即可。
1. TPAD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,TPAD的驱动主要包括两个文件:tpad.c和tpad.h。
首先看tpad.h头文件的几个宏定义:
- /* 电容触摸按键定义 */
- #define TPAD_GPIO_PORT GPIOB
- #define TPAD_GPIO_PIN GPIO_PIN_0
- #define TPAD_GPIO_AF GPIO_AF2_TIM3
- #define TPAD_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOB_CLK_ENABLE();}while (0)
- #define TPAD_TIMX_CAP TIM3
- #define TPAD_TIMX_CAP_CHY TIM_CHANNEL_3
- #define TPAD_TIMX_CAP_CLK_ENABLE() do{__HAL_RCC_TIM3_CLK_ENABLE();}while (0)
- #define TPAD_TIMX_ARR_MAX_VAL 0xFFFFFFFF
- #define TPAD_GATE_VAL 100
复制代码 PB0是定时器3的PWM通道1,如果我们使用其他定时器和它们对应的捕获通道的其它IO,我们只需要修改上面的宏即可。
利用前面描述的触摸按键的原理上电时检测 TPAD 上的电容的充放电时间,并以此为基准,每次需要重新检测 TPAD 时,通过比较充放电的时长来检测当前是否有按下,所以我们需要用定时器的输入捕获来监测TPAD 上低电平的时间编写tpad_timx_cap_init()函数如下:
- /**
- * @brief 初始化定时器输入捕获
- * [url=home.php?mod=space&uid=271674]@param[/url] arr: 自动重装载值
- * @param psc: 预分频系数
- * @retval 无
- */
- static void tpad_timx_cap_init(uint32_t arr, uint16_t psc)
- {
- GPIO_InitTypeDef gpio_init_struct = {0};
- TIM_IC_InitTypeDef timx_ic_cap_struct = {0};
-
- /* 使能相关时钟 */
- TPAD_GPIO_CLK_ENABLE();
- TPAD_TIMX_CAP_CLK_ENABLE();
-
- /* 初始化输入捕获引脚 */
- gpio_init_struct.Pin = TPAD_GPIO_PIN;
- 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;
- HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
-
- /* 初始化定时器输入捕获 */
- g_tpad_timx_cap_handle.Instance = TPAD_TIMX_CAP;
- g_tpad_timx_cap_handle.Init.Prescaler = psc;
- g_tpad_timx_cap_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
- g_tpad_timx_cap_handle.Init.Period = arr;
- HAL_TIM_IC_Init(&g_tpad_timx_cap_handle);
-
- /* 配置定时器输入捕获通道 */
- timx_ic_cap_struct.ICPolarity = TIM_ICPOLARITY_RISING;
- timx_ic_cap_struct.ICSelection = TIM_ICSELECTION_DIRECTTI;
- timx_ic_cap_struct.ICPrescaler = TIM_ICPSC_DIV1;
- timx_ic_cap_struct.ICFilter = 0;
- HAL_TIM_IC_ConfigChannel(&g_tpad_timx_cap_handle, &timx_ic_cap_struct,
- TPAD_TIMX_CAP_CHY);
-
- /* 开启定时器输入捕获 */
- HAL_TIM_IC_Start(&g_tpad_timx_cap_handle, TPAD_TIMX_CAP_CHY);
- }
复制代码 这和我们《实验9-3 通用定时器输入捕获实验》的代码基本一样,原理就不重复解释了,接下我们通过控制变量法,每次先给TPAD放电(STM32 输出低电平)相同时间,然后释放, 监测 VCC 每次给TPAD 的充电时间,由此可以得到一个充电时间,操作的代码如下:
- /**
- * @brief 复位TPAD
- * @param 无
- * @retval 无
- */
- static void tpad_reset(void)
- {
- GPIO_InitTypeDef gpio_init_struct = {0};
-
- /* 配置TPAD引脚为输出引脚 */
- gpio_init_struct.Pin = TPAD_GPIO_PIN;
- 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);
- delay_ms(5);
-
- __HAL_TIM_CLEAR_FLAG(&g_tpad_timx_cap_handle,
- (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 :
-
- (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_4) ? TIM_FLAG_CC4 :
-
- (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_5) ? TIM_FLAG_CC5 : TIM_FLAG_CC6);
- __HAL_TIM_SET_COUNTER(&g_tpad_timx_cap_handle, 0);
-
- /* 配置TPAD引脚为定时器输入捕获引脚 */
- gpio_init_struct.Pin = TPAD_GPIO_PIN;
- 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;
- HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
- }
- /**
- * @brief 获取定时器捕获值
- * @param 无
- * @retval 捕获值或计数值
- * @arg 等待捕获未超时: 捕获值
- * @arg 等待捕获超时: 计数值
- */
- static uint32_t tpad_get_val(void)
- {
- uint32_t flag;
- uint32_t count;
-
- 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 :
- (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_4) ? TIM_FLAG_CC4 :
- (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_5) ? TIM_FLAG_CC5 :
- TIM_FLAG_CC6;
-
- /* 复位TPAD */
- tpad_reset();
-
- /* 等待捕获到上升沿 */
- while (__HAL_TIM_CLEAR_FLAG(&g_tpad_timx_cap_handle, flag) == RESET)
- {
- /* 等待超时,则直接返回计数值 */
- count = __HAL_TIM_GET_COUNTER(&g_tpad_timx_cap_handle);
- if (count > (TPAD_TIMX_ARR_MAX_VAL - 500))
- {
- return count;
- }
- }
-
- /* 返回捕获比较值 */
- return __HAL_TIM_GET_COMPARE(&g_tpad_timx_cap_handle, TPAD_TIMX_CAP_CHY);
- }
- /**
- * @brief 获取定时器连续指定次数捕获值的最大值
- * @param times: 指定定时器连续捕获的次数
- * @retval 定时器连续指定次数捕获值的最大值
- */
- static uint32_t tpad_get_maxval(uint8_t times)
- {
- uint32_t value;
- uint32_t value_max = 0;
-
- while (times--)
- {
- value = tpad_get_val();
- if (value > value_max)
- {
- value_max = value;
- }
- }
-
- return value_max;
- }
复制代码 得到充电时间后,接下来我们要做的就是获取没有按下 TPAD 的充电时间,并把它作为基准来确认后续有无按下操作,我们定义全局变量g_tpad_default_val 来保存这个值,通过多次平均的滤波算法来减小误差,编写的初始化函数tpad_init代码如下。
- /**
- * @brief 初始化电容触摸按键
- * @param psc: 捕获定时器分频系数(范围1~65535,越小灵敏度越高)
- * @retval 初始化结果
- * @arg 0: 初始化成功
- * @arg 1: 初始化失败
- */
- uint8_t tpad_init(uint16_t psc)
- {
- uint32_t values[10];
- uint32_t value;
- uint8_t i;
- uint8_t j;
-
- /* 初始化定时器输入捕获 */
- tpad_timx_cap_init(TPAD_TIMX_ARR_MAX_VAL, psc - 1);
-
- /* 连续获取10次捕获值 */
- for (i=0; i<10; i++)
- {
- values[i] = tpad_get_val();
- delay_ms(10);
- }
-
- /* 升序排序 */
- for (i=0; i<9; i++)
- {
- for (j=i+1; j<10; j++)
- {
- if (values[i] > values[j])
- {
- values[i] = values[i] ^ values[j];
- values[j] = values[i] ^ values[j];
- values[i] = values[i] ^ values[j];
- }
- }
- }
-
- /* 对中间的6个数据进行均值滤波得到默认捕获值 */
- for (i=2; i<8; i++)
- {
- value += values[i];
- }
- g_tpad_default_val = value / 6;
-
- /* 检验默认捕获值的合法性 */
- if (g_tpad_default_val > (TPAD_TIMX_ARR_MAX_VAL >> 1))
- {
- return 1;
- }
-
- return 0;
- }
复制代码 得到初始值后,我们需编写一个按键扫描函数,以方便在需要监控TPAD的地方调用,代码如下:
- /**
- * @brief 扫描TPAD
- * @param mode: 扫描模式
- * @arg 0: 不支持连续按
- * @arg 1: 支持连续按
- * @retval 扫描结果
- * @arg 0: 没有按下
- * @arg 1: 按下有效
- */
- uint8_t tpad_scan(uint8_t mode)
- {
- static uint8_t keyen = 0;
- uint8_t res = 0;
- uint8_t sample = 3;
- uint32_t value;
-
- /* 需要支持连续按时,每次进行6次采样 */
- if (mode != 0)
- {
- sample = 6;
- keyen = 0;
- }
-
- /* 获取捕获值 */
- value = tpad_get_maxval(sample);
-
- /* 检验捕获值是否有效 */
- if (value > (g_tpad_default_val + TPAD_GATE_VAL))
- {
- /* 连续调用时才返回有效 */
- if (keyen == 0)
- {
- res = 1;
- }
-
- /* 重新调用次数计数 */
- keyen = 3;
- }
-
- /* 调用次数计数 */
- if (keyen != 0)
- {
- keyen--;
- }
-
- return res;
- }
复制代码 TPAD函数到此就编写完了,接下来我们通过main函数编写测试代码来验证一下TPAD的逻辑是否正确。
2. main.c代码
在main.c里面编写如下代码:
- int main(void)
- {
- uint8_t t = 0;
- uint8_t ret;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- tpad_init(4); /* 初始化TPAD */
-
- while (1)
- {
- ret = tpad_scan(0); /* 扫描TPAD */
- if (ret != 0)
- {
- LED1_TOGGLE(); /* LED1状态翻转 */
- }
-
- if (++t == 20)
- {
- t = 0;
- LED0_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码 初始化了必要的外设后,我们通过循环来实现我们的代码操作。我们在扫描函数中定义了电容按触摸发生后的状态,通过判断返回值来判断是否符合按下的条件,如果按下我们就翻转一次LED1。LED0 通过累计延时次数的方法,既能保证扫描的频率,又能达到定时翻转的目的。
22.4 下载验证
下载代码后,可以看到 LED0 不停闪烁(每400ms 闪烁一次),用手指按下电容按键时,LED1 的状态发生改变(亮灭交替一次)。这里记得 TPAD 引脚和PB0都是连接到开发板上的排针上的,开始测试前需要连接好,否则测试就不准了,如果下载代码前没有连接好,请连接后复位重新测试即可。 |