OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 85|回复: 0

《STM32H7R7开发指南 V1.1 》第二十二章 电容触摸按键实验

[复制链接]

1306

主题

1322

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5590
金钱
5590
注册时间
2019-5-8
在线时间
1488 小时
发表于 前天 09:27 | 显示全部楼层 |阅读模式
第二十二章 电容触摸按键实验

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


2.jpg

3.png

上一章,我们介绍了 STM32H7R7 的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用 TIM3的通道 3( PB0)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制 DS1 的亮灭。从本章分为如 下几个部分:
22.1 电容触摸按键简介
22.2 硬件设计
22.3 程序设计
22.4 下载验证


22.1 电容触摸按键简介
触摸按键相对于传统的机械按键有寿命长、占用空间少、易于操作等诸多优点。大家看看如今的手机,触摸屏、触摸按键大行其道,而传统的机械按键,正在逐步从手机上面消失。本章,我们将给大家介绍一种简单的触摸按键:电容式触摸按键。我们将利用 STM32 开发板上的触摸按键( TPAD),来实现对 DS1 的亮灭控制。这里 TPAD 其实就是 STM32 开发板上的一小块覆铜区域,实现原理如图 22.1.1 所示:

第二十二章 电容触摸按键实验422.png
图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所示:


第二十二章 电容触摸按键实验1812.png
图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头文件的几个宏定义:

  1. /* 电容触摸按键定义 */
  2. #define TPAD_GPIO_PORT              GPIOB
  3. #define TPAD_GPIO_PIN               GPIO_PIN_0
  4. #define TPAD_GPIO_AF                GPIO_AF2_TIM3
  5. #define TPAD_GPIO_CLK_ENABLE()      do{__HAL_RCC_GPIOB_CLK_ENABLE();}while (0)
  6. #define TPAD_TIMX_CAP               TIM3
  7. #define TPAD_TIMX_CAP_CHY           TIM_CHANNEL_3
  8. #define TPAD_TIMX_CAP_CLK_ENABLE()  do{__HAL_RCC_TIM3_CLK_ENABLE();}while (0)
  9. #define TPAD_TIMX_ARR_MAX_VAL       0xFFFFFFFF
  10. #define TPAD_GATE_VAL               100
复制代码
PB0是定时器3的PWM通道1,如果我们使用其他定时器和它们对应的捕获通道的其它IO,我们只需要修改上面的宏即可。
利用前面描述的触摸按键的原理上电时检测 TPAD 上的电容的充放电时间,并以此为基准,每次需要重新检测 TPAD 时,通过比较充放电的时长来检测当前是否有按下,所以我们需要用定时器的输入捕获来监测TPAD 上低电平的时间编写tpad_timx_cap_init()函数如下:

  1. /**
  2. * @brief   初始化定时器输入捕获
  3. * [url=home.php?mod=space&uid=271674]@param[/url]   arr: 自动重装载值
  4. * @param   psc: 预分频系数
  5. * @retval  无
  6. */
  7. static void tpad_timx_cap_init(uint32_t arr, uint16_t psc)
  8. {
  9.     GPIO_InitTypeDef gpio_init_struct = {0};
  10.     TIM_IC_InitTypeDef timx_ic_cap_struct = {0};
  11.    
  12.     /* 使能相关时钟 */
  13.     TPAD_GPIO_CLK_ENABLE();
  14.     TPAD_TIMX_CAP_CLK_ENABLE();
  15.    
  16.     /* 初始化输入捕获引脚 */
  17.     gpio_init_struct.Pin = TPAD_GPIO_PIN;
  18.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  19.     gpio_init_struct.Pull = GPIO_NOPULL;
  20.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  21.     gpio_init_struct.Alternate = TPAD_GPIO_AF;
  22.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
  23.    
  24.     /* 初始化定时器输入捕获 */
  25.     g_tpad_timx_cap_handle.Instance = TPAD_TIMX_CAP;
  26.     g_tpad_timx_cap_handle.Init.Prescaler = psc;
  27.     g_tpad_timx_cap_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  28.     g_tpad_timx_cap_handle.Init.Period = arr;
  29.     HAL_TIM_IC_Init(&g_tpad_timx_cap_handle);
  30.    
  31.     /* 配置定时器输入捕获通道 */
  32.     timx_ic_cap_struct.ICPolarity = TIM_ICPOLARITY_RISING;
  33.     timx_ic_cap_struct.ICSelection = TIM_ICSELECTION_DIRECTTI;
  34.     timx_ic_cap_struct.ICPrescaler = TIM_ICPSC_DIV1;
  35.     timx_ic_cap_struct.ICFilter = 0;
  36. HAL_TIM_IC_ConfigChannel(&g_tpad_timx_cap_handle, &timx_ic_cap_struct,
  37. TPAD_TIMX_CAP_CHY);
  38.    
  39.     /* 开启定时器输入捕获 */
  40.     HAL_TIM_IC_Start(&g_tpad_timx_cap_handle, TPAD_TIMX_CAP_CHY);
  41. }
复制代码
这和我们《实验9-3 通用定时器输入捕获实验》的代码基本一样,原理就不重复解释了,接下我们通过控制变量法,每次先给TPAD放电(STM32 输出低电平)相同时间,然后释放, 监测 VCC 每次给TPAD 的充电时间,由此可以得到一个充电时间,操作的代码如下:
  1. /**
  2. * @brief   复位TPAD
  3. * @param   无
  4. * @retval  无
  5. */
  6. static void tpad_reset(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.    
  10.     /* 配置TPAD引脚为输出引脚 */
  11.     gpio_init_struct.Pin = TPAD_GPIO_PIN;
  12.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
  13.     gpio_init_struct.Pull = GPIO_PULLDOWN;
  14.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  15.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
  16.    
  17.     /* 对电容触摸按键放电 */
  18.     HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET);
  19.     delay_ms(5);
  20.    
  21. __HAL_TIM_CLEAR_FLAG(&g_tpad_timx_cap_handle,
  22. (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_1) ? TIM_FLAG_CC1 :
  23.                                                   
  24.         (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_2) ? TIM_FLAG_CC2 :
  25.                                                   (
  26.         TPAD_TIMX_CAP_CHY == TIM_CHANNEL_3) ? TIM_FLAG_CC3 :
  27.                                                   
  28.         (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_4) ? TIM_FLAG_CC4 :
  29.                                                   
  30.         (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_5) ? TIM_FLAG_CC5 : TIM_FLAG_CC6);
  31.     __HAL_TIM_SET_COUNTER(&g_tpad_timx_cap_handle, 0);
  32.    
  33.     /* 配置TPAD引脚为定时器输入捕获引脚 */
  34.     gpio_init_struct.Pin = TPAD_GPIO_PIN;
  35.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  36.     gpio_init_struct.Pull = GPIO_NOPULL;
  37.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  38.     gpio_init_struct.Alternate = TPAD_GPIO_AF;
  39.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
  40. }

  41. /**
  42. * @brief   获取定时器捕获值
  43. * @param   无
  44. * @retval  捕获值或计数值
  45. * @arg     等待捕获未超时: 捕获值
  46. * @arg     等待捕获超时: 计数值
  47. */
  48. static uint32_t tpad_get_val(void)
  49. {
  50.     uint32_t flag;
  51.     uint32_t count;
  52.    
  53.     flag = (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_1) ? TIM_FLAG_CC1 :
  54.            (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_2) ? TIM_FLAG_CC2 :
  55.            (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_3) ? TIM_FLAG_CC3 :
  56.            (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_4) ? TIM_FLAG_CC4 :
  57.            (TPAD_TIMX_CAP_CHY == TIM_CHANNEL_5) ? TIM_FLAG_CC5 :
  58.            TIM_FLAG_CC6;
  59.    
  60.     /* 复位TPAD */
  61.     tpad_reset();
  62.    
  63.     /* 等待捕获到上升沿 */
  64.     while (__HAL_TIM_CLEAR_FLAG(&g_tpad_timx_cap_handle, flag) == RESET)
  65.     {
  66.         /* 等待超时,则直接返回计数值 */
  67.         count = __HAL_TIM_GET_COUNTER(&g_tpad_timx_cap_handle);
  68.         if (count > (TPAD_TIMX_ARR_MAX_VAL - 500))
  69.         {
  70.             return count;
  71.         }
  72.     }
  73.    
  74.     /* 返回捕获比较值 */
  75.     return __HAL_TIM_GET_COMPARE(&g_tpad_timx_cap_handle, TPAD_TIMX_CAP_CHY);
  76. }

  77. /**
  78. * @brief   获取定时器连续指定次数捕获值的最大值
  79. * @param   times: 指定定时器连续捕获的次数
  80. * @retval  定时器连续指定次数捕获值的最大值
  81. */
  82. static uint32_t tpad_get_maxval(uint8_t times)
  83. {
  84.     uint32_t value;
  85.     uint32_t value_max = 0;
  86.    
  87.     while (times--)
  88.     {
  89.         value = tpad_get_val();
  90.         if (value > value_max)
  91.         {
  92.             value_max = value;
  93.         }
  94.     }
  95.    
  96.     return value_max;
  97. }
复制代码
得到充电时间后,接下来我们要做的就是获取没有按下 TPAD 的充电时间,并把它作为基准来确认后续有无按下操作,我们定义全局变量g_tpad_default_val 来保存这个值,通过多次平均的滤波算法来减小误差,编写的初始化函数tpad_init代码如下。
  1. /**
  2. * @brief   初始化电容触摸按键
  3. * @param   psc: 捕获定时器分频系数(范围1~65535,越小灵敏度越高)
  4. * @retval  初始化结果
  5. * @arg     0: 初始化成功
  6. * @arg     1: 初始化失败
  7. */
  8. uint8_t tpad_init(uint16_t psc)
  9. {
  10.     uint32_t values[10];
  11.     uint32_t value;
  12.     uint8_t i;
  13.     uint8_t j;
  14.    
  15.     /* 初始化定时器输入捕获 */
  16.     tpad_timx_cap_init(TPAD_TIMX_ARR_MAX_VAL, psc - 1);
  17.    
  18.     /* 连续获取10次捕获值 */
  19.     for (i=0; i<10; i++)
  20.     {
  21.         values[i] = tpad_get_val();
  22.         delay_ms(10);
  23.     }
  24.    
  25.     /* 升序排序 */
  26.     for (i=0; i<9; i++)
  27.     {
  28.         for (j=i+1; j<10; j++)
  29.         {
  30.             if (values[i] > values[j])
  31.             {
  32.                 values[i] = values[i] ^ values[j];
  33.                 values[j] = values[i] ^ values[j];
  34.                 values[i] = values[i] ^ values[j];
  35.             }
  36.         }
  37.     }
  38.    
  39.     /* 对中间的6个数据进行均值滤波得到默认捕获值 */
  40.     for (i=2; i<8; i++)
  41.     {
  42.         value += values[i];
  43.     }
  44.     g_tpad_default_val = value / 6;
  45.    
  46.     /* 检验默认捕获值的合法性 */
  47.     if (g_tpad_default_val > (TPAD_TIMX_ARR_MAX_VAL >> 1))
  48.     {
  49.         return 1;
  50.     }
  51.    
  52.     return 0;
  53. }
复制代码
得到初始值后,我们需编写一个按键扫描函数,以方便在需要监控TPAD的地方调用,代码如下:
  1. /**
  2. * @brief   扫描TPAD
  3. * @param   mode: 扫描模式
  4. * @arg     0: 不支持连续按
  5. * @arg     1: 支持连续按
  6. * @retval  扫描结果
  7. * @arg     0: 没有按下
  8. * @arg     1: 按下有效
  9. */
  10. uint8_t tpad_scan(uint8_t mode)
  11. {
  12.     static uint8_t keyen = 0;
  13.     uint8_t res = 0;
  14.     uint8_t sample = 3;
  15.     uint32_t value;
  16.    
  17.     /* 需要支持连续按时,每次进行6次采样 */
  18.     if (mode != 0)
  19.     {
  20.         sample = 6;
  21.         keyen = 0;
  22.     }
  23.    
  24.     /* 获取捕获值 */
  25.     value = tpad_get_maxval(sample);
  26.    
  27.     /* 检验捕获值是否有效 */
  28.     if (value > (g_tpad_default_val + TPAD_GATE_VAL))
  29.     {
  30.         /* 连续调用时才返回有效 */
  31.         if (keyen == 0)
  32.         {
  33.             res = 1;
  34.         }
  35.         
  36.         /* 重新调用次数计数 */
  37.         keyen = 3;
  38.     }
  39.    
  40.     /* 调用次数计数 */
  41.     if (keyen != 0)
  42.     {
  43.         keyen--;
  44.     }
  45.    
  46.     return res;
  47. }
复制代码
TPAD函数到此就编写完了,接下来我们通过main函数编写测试代码来验证一下TPAD的逻辑是否正确。
2. main.c代码
在main.c里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4.     uint8_t ret;
  5.    
  6.     sys_mpu_config();                   /* 配置MPU */
  7.     sys_cache_enable();                 /* 使能Cache */
  8.     HAL_Init();                         /* 初始化HAL库 */
  9.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  10.     delay_init(600);                    /* 初始化延时 */
  11.     usart_init(115200);                 /* 初始化串口 */
  12.     led_init();                         /* 初始化LED */
  13.     tpad_init(4);                       /* 初始化TPAD */
  14.    
  15.     while (1)
  16.     {
  17.         ret = tpad_scan(0);             /* 扫描TPAD */
  18.         if (ret != 0)
  19.         {
  20.             LED1_TOGGLE();              /* LED1状态翻转 */
  21.         }
  22.         
  23.         if (++t == 20)
  24.         {
  25.             t = 0;
  26.             LED0_TOGGLE();
  27.         }
  28.         
  29.         delay_ms(10);
  30.     }
  31. }
复制代码
初始化了必要的外设后,我们通过循环来实现我们的代码操作。我们在扫描函数中定义了电容按触摸发生后的状态,通过判断返回值来判断是否符合按下的条件,如果按下我们就翻转一次LED1。LED0 通过累计延时次数的方法,既能保证扫描的频率,又能达到定时翻转的目的。

22.4 下载验证
下载代码后,可以看到 LED0 不停闪烁(每400ms 闪烁一次),用手指按下电容按键时,LED1 的状态发生改变(亮灭交替一次)。这里记得 TPAD 引脚和PB0都是连接到开发板上的排针上的,开始测试前需要连接好,否则测试就不准了,如果下载代码前没有连接好,请连接后复位重新测试即可。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-16 05:39

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表