OpenEdv-开源电子网

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

《STM32F407 探索者开发指南》第二十三章 电容触摸按键实验

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4667
金钱
4667
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-7-20 10:40:12 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-7-19 15:30 编辑

第二十三章 电容触摸按键实验

1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

上一章,我们介绍了STM32F407的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用TIM2的通道1(PA5)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制DS1的亮灭。从本章分为如下几个部分:
21.1 电容触摸按键简介
21.2 硬件设计
21.3 软件设计
21.4 下载验证

23.1 电容触摸按键简介
前面我们学习过了机械按键,这节我们将介绍另一种人机交互设备:电容触摸按键。电容式触摸按键已经广泛应用在家用电器、消费电子市场,其主要优势有:无机械装置,使用寿命长;非接触式感应,面板不需要开孔;产品更加美观简洁;防水可以做到很好。
探索者STM32F407开发板上的触摸按键TPAD其实就是一小块覆铜区域,其形状为正点原子的LOGO,如图23.1.1所示。                              
image001.png
图23.1.1 电容按键TPAD外观

与机械按键不同,这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图23.1.2中的A、B分别表示有无人体按下时电容的充放电曲线。其中R是外接的电容充电电阻,Cs是没有触摸按下时TPAD与PCB之间的杂散电容。而Cx则是有手指按下的时候,手指与TPAD之间形成的电容。图中的开关是电容放电开关(实际使用时,由STM32F407的IO代替)。     
image003.png
图23.1.2 电容按键TPAD外

先用开关将Cs(或Cs+Cx)上的电放尽,然后断开开关,让R给Cs(或Cs+Cx)充电,当没有手指触摸的时候,Cs的充电曲线如图中的A曲线。而当有手指触摸的时候,手指和TPAD之间引入了新的电容Cx,此时Cs+Cx的充电曲线如图中的B曲线。从上图可以看出,A、B两种情况下,Vc达到Vth的时间分别为Tcs和Tcs+Tcx。

其中,除了Cs和Cx我们需要计算,其他都是已知的,根据电容充放电公式:
image005.png

其中Vc为电容电压,V0为充电电压,R为充电电阻,C为电容容值,e为自然底数,t为充电时间。根据这个公式,我们就可以计算出Cs和Cx。利用这个公式,我们还可以把探索者开发板作为一个简单的电容计,直接可以测电容容量了,有兴趣的朋友可以捣鼓下。
在本章中,其实我们只要能够区分Tcs和Tcs+Tcx,就已经可以实现触摸检测了,当充电时间在Tcs附近,就可以认为没有触摸,而当充电时间大于Tcs+Tx时,就认为有触摸按下(Tx为检测阀值)。

本章,我们使用PA5(TIM2_CH1)来检测TPAD是否有触摸,在每次检测之前,我们先配置PA5为推挽输出,将电容Cs(或Cs+Cx)放电,然后配置PA5为浮空输入,利用外部上拉电阻给电容Cs(Cs+Cx)充电,同时开启TIM2_CH1的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。

在MCU每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与tpad_default_val的对比,来判断是不是有触摸发生。

关于输入捕获的配置,在上一章我们已经有详细介绍了,这里我们就不再介绍。至此,电容触摸按键的原理介绍完毕。

23.2 硬件设计
1. 例程功能
LED0用来指示程序运行,150ms变换一次状态,即约300ms一次闪烁。不断扫描按键的状态,如果判定了电容触摸按键按下,我们就把LED1的状态翻转一次。

2. 硬件资源
1)LED灯:
     LED0 – PF9
     LED1 – PF10
2) 定时器 TIM2
3) GPIO:PA5,用于控制触摸按键TPAD。

3. 原理图
触摸按键的原理图TPAD设计如图23.2.1所示     
image007.png
图23.2.1 电容按键TPAD连接原理图

由于设计时PA5不直接连接到电容触摸按键,而是引到了插针上,我们需要通过跳线帽把P11上标为ADC的引脚与标号为“TPAD”的标号连接到一起,连接图23.2.2所示。     
image009.png
图23.2.2 用跳线帽连接电容按键TPAD和PA5

23.3 程序设计
我们在基本定时器一节已经学习过定时器的输入捕获功能,这里我们可以类似地,用定时器2来实现对TPAD引脚上的电平状态进行捕获的功能。本实验用到的HAL库驱动请回顾基本定时器实验的介绍。下面直接从程序流程图开始介绍。

23.3.1 程序流程图
QQ截图20230719153015.png
图23.2.3.1.1 电容触摸按键实验程序流程图

23.3.2 程序解析
TPAD可以看作《实验9-3 通用定时器输入捕获实验》的一个应用案例,相关的HAL库函数函数在定时器章节我们已经介绍过了,所以这里就不重复介绍了,大家回过头复习一下即可。

1. TPAD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,TPAD的驱动主要包括两个文件:tpad.c和tpad.h。

首先看tpad.h头文件的几个宏定义:
  1. /* TPAD 引脚 及 定时器 定义 */
  2. #define TPAD_GPIO_PORT              GPIOA
  3. #define TPAD_GPIO_PIN                GPIO_PIN_5
  4. /* PA口时钟使能 */
  5. #define TPAD_GPIO_CLK_ENABLE()     do{__HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
  6. #define TPAD_TIMX_CAP                       TIM2
  7. #define TPAD_TIMX_CAP_CHY                  TIM_CHANNEL_1  /* 通道Y,  1<= Y <=4 */
  8. #define TPAD_TIMX_CAP_CHY_CCRX            TIM2->CCR1     /* 通道Y的捕获/比较寄存器 */
  9. #define TPAD_TIMX_CAP_CHY_CLK_ENABLE()  \
  10. do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0)     /* TIM5 时钟使能 */
复制代码
PA5是定时器2的PWM通道1,如果我们使用其它定时器和它们对应的捕获通道的其它IO,我们只需要修改上面的宏即可。

利用前面描述的触摸按键的原理,上电时检测TPAD上的电容的充放电时间,并以此为基准,每次需要重新检测TPAD时,通过比较充放电的时长来检测当前是否有按下,所以我们需要用定时器的输入捕获来监测低TPAD上低电平的时间。编写tpad_timx_cap_init()函数如下:
  1. /**
  2. * @brief       触摸按键输入捕获设置
  3. * @param       arr    :自动重装值
  4. * @param       psc    :时钟预分频数
  5. * @retval      无
  6. */
  7. static void tpad_cap_init(uint16_t arr, uint16_t psc)
  8. {
  9.    GPIO_InitTypeDef gpio_init_struct;
  10.    TPAD_GPIO_CLK_ENABLE();                                /* TPAD引脚 时钟使能 */
  11.    TPAD_TIMX_CAP_CHY_CLK_ENABLE();                       /* 定时器 时钟使能 */
  12.    gpio_init_struct.Pin = TPAD_GPIO_PIN;               /* 输入捕获的GPIO口 */
  13.    gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 复用推挽输出 */
  14.    gpio_init_struct.Pull = GPIO_NOPULL;                  /* 不带上下拉 */
  15.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
  16.     gpio_init_struct.Alternate= TPAD_GPIO_AF;           /* PA5复用为TIM2_CH1 */
  17.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);  /* TPAD引脚浮空输入 */
  18.    g_timx_cap_chy_handler.Instance = TPAD_TIMX_CAP;   /* 定时器5 */
  19.    g_timx_cap_chy_handler.Init.Prescaler = psc;        /* 定时器分频 */
  20.    g_timx_cap_chy_handler.Init.CounterMode = TIM_COUNTERMODE_UP;/*向上计数模式*/
  21.    g_timx_cap_chy_handler.Init.Period = arr;            /* 自动重装载值 */
  22.    g_timx_cap_chy_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/*不分频*/
  23.     HAL_TIM_IC_Init(&g_timx_cap_chy_handler);
  24.    g_timx_ic_cap_chy_handler.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
  25.    g_timx_ic_cap_chy_handler.ICSelection = TIM_ICSELECTION_DIRECTTI;/*映射TI1*/
  26.    g_timx_ic_cap_chy_handler.ICPrescaler = TIM_ICPSC_DIV1;/* 配置输入不分频 */
  27.    g_timx_ic_cap_chy_handler.ICFilter = 0;               /* 配置输入滤波器,不滤波 */
  28. HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handler,
  29. &g_timx_ic_cap_chy_handler, TPAD_TIMX_CAP_CHY);/* 配置TIM5通道2 */
  30.    HAL_TIM_IC_Start(&g_timx_cap_chy_handler, TPAD_TIMX_CAP_CHY);/*使能输入捕获*/
  31. }
复制代码
这和我们《实验9-3 通用定时器输入捕获实验》的代码基本一样,原理就不重复解释了,接下我们通过控制变量法,每次先给TPAD放电(STM32输出低电平)相同时间,然后释放,监测VCC每次给TPAD的充电时间,由此可以得到一个充电时间,操作的代码如下:
  1. /**
  2. * @brief       复位TPAD
  3. *   @note     我们将TPAD按键看做是一个电容, 当手指按下/不按下时容值有变化
  4. *              该函数将GPIO设置成推挽输出, 然后输出0, 进行放电, 然后再设置
  5. *              GPIO为浮空输入, 等待外部大电阻慢慢充电
  6. * @param       无
  7. * @retval      无
  8. */
  9. static void tpad_reset(void)
  10. {
  11.    GPIO_InitTypeDef gpio_init_struct;
  12.    gpio_init_struct.Pin = TPAD_GPIO_PIN;            /* 输入捕获的GPIO口 */
  13.    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;    /* 复用推挽输出 */
  14.    gpio_init_struct.Pull = GPIO_PULLDOWN;                    /* 下拉 */
  15.    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
  16.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
  17. /* TPAD引脚输出0, 放电 */
  18.    HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET);   
  19.     delay_ms(5);
  20.    g_timx_cap_chy_handler.Instance->SR = 0;             /* 清除标记 */
  21.    g_timx_cap_chy_handler.Instance->CNT = 0;            /* 归零 */
  22.    gpio_init_struct.Pin = TPAD_GPIO_PIN;                /* 输入捕获的GPIO口 */
  23.    gpio_init_struct.Mode = GPIO_MODE_AF_PP;             /* 复用推挽输出 */
  24.    gpio_init_struct.Pull = GPIO_NOPULL;                  /* 浮空 */
  25.    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
  26.    gpio_init_struct.Alternate = TPAD_GPIO_AF;           /* PA5复用为TIM2_CH1 */
  27.     HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);  /* TPAD引脚浮空输入 */
  28. }
  29. /**
  30. * @brief       得到定时器捕获值
  31. *   @note     如果超时, 则直接返回定时器的计数值
  32. *               我们定义超时时间为: TPAD_ARR_MAX_VAL - 500
  33. * @param       无
  34. * @retval      捕获值/计数值(超时的情况下返回)
  35. */
  36. static uint16_ttpad_get_val(void)
  37. {
  38.     uint32_t flag = (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_1)?TIM_FLAG_CC1:\
  39.                       (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_2)?TIM_FLAG_CC2:\
  40.                       (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_3)?TIM_FLAG_CC3:\
  41.                                                                  TIM_FLAG_CC4;
  42.    
  43.     tpad_reset();
  44.     while (__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handler,flag) == RESET)
  45.     {   /* 等待通道CHY捕获上升沿 */
  46.         if (g_timx_cap_chy_handler.Instance->CNT > TPAD_ARR_MAX_VAL - 500)
  47.         {
  48.            returng_timx_cap_chy_handler.Instance->CNT; /* 超时了,直接返回CNT的值 */
  49.         }
  50.     }
  51.     returnTPAD_TIMX_CAP_CHY_CCRX;      /* 返回捕获/比较值 */
  52. }
  53. /**
  54. * @brief       读取n次, 取最大值
  55. * @param       n      :连续获取的次数
  56. * @retval      n次读数里面读到的最大读数值
  57. */
  58. static uint16_ttpad_get_maxval(uint8_t n)
  59. {
  60.     uint16_t temp = 0;
  61.     uint16_t maxval= 0;
  62.     while (n--)
  63.     {
  64.         temp = tpad_get_val();  /* 得到一次值 */
  65.         if (temp > maxval)maxval = temp;
  66.     }
  67.     return maxval;
  68. }
复制代码
得到充电时间后,接下来我们要做的就是获取没有按下TPAD时的充电时间,并把它作为基准来确认后续有无按下操作,我们定义全局变量g_tpad_default_val来保存这个值,通过多次平均的滤波算法来减小误差,编写的初始化函数tpad_init代码如下。
  1. /**
  2. * @brief       初始化触摸按键
  3. * @param       psc    : 分频系数(值越小, 越灵敏, 最小值为: 1)
  4. * @retval      0, 初始化成功; 1, 初始化失败;
  5. */
  6. uint8_t tpad_init(uint16_t psc)
  7. {
  8.     uint16_t buf[10];
  9.     uint16_t temp;
  10.     uint8_t j, i;
  11.     /* 以 Ft/ (psc - 1)Mhz的频率计数 @Ft = 定时器工作频率*/
  12.    tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc - 1);
  13.     for (i = 0; i < 10; i++)     /* 连续读取10次 */
  14.     {
  15.         buf = tpad_get_val();
  16.         delay_ms(10);
  17.     }
  18.     for (i = 0; i < 9; i++)      /* 排序 */
  19.     {
  20.         for (j = i + 1; j < 10; j++)
  21.         {
  22.             if (buf > buf[j]) /* 升序排列 */
  23.             {
  24.                temp = buf;
  25.                 buf = buf[j];
  26.                 buf[j] = temp;
  27.             }
  28.         }
  29.     }
  30.     temp = 0;
  31.     for (i = 2; i < 8; i++)     /* 取中间的6个数据进行平均 */
  32.     {
  33.         temp += buf;
  34.     }
  35.    
  36.    g_tpad_default_val = temp / 6;
  37.     printf("g_tpad_default_val:%d\r\n", g_tpad_default_val);
  38.     if (g_tpad_default_val > TPAD_ARR_MAX_VAL / 2)
  39.     {
  40.         return 1;   /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */
  41.     }
  42.     return 0;
  43. }
复制代码
得到初始值后,我们需编写一个按键扫描函数,以方便在需要监控TPAD的地方调用,代码如下:
  1. /**
  2. * @brief       扫描触摸按键
  3. * @param       mode   :扫描模式
  4. *   @arg       0, 不支持连续触发(按下一次必须松开才能按下一次);
  5. *   @arg       1, 支持连续触发(可以一直按下)
  6. * @retval      0, 没有按下; 1, 有按下;
  7. */
  8. uint8_t tpad_scan(uint8_t mode)
  9. {
  10.     static uint8_t keyen = 0;   /* 0, 可以开始检测;  >0, 还不能开始检测; */
  11.     uint8_t res = 0;
  12.     uint8_t sample = 3;          /* 默认采样次数为3次 */
  13.     uint16_t rval;
  14.     if (mode)
  15.     {
  16.         sample = 6;               /* 支持连按的时候,设置采样次数为6次 */
  17.         keyen = 0;                /* 支持连按, 每次调用该函数都可以检测 */
  18.     }
  19.     rval = tpad_get_maxval(sample);
  20.     if (rval > (g_tpad_default_val + TPAD_GATE_VAL))
  21.     {/* 大于tpad_default_val+TPAD_GATE_VAL,有效 */
  22.         if (keyen == 0)
  23.         {
  24.             res = 1;              /* keyen==0, 有效 */
  25.         }
  26.         keyen = 3;                /* 至少要再过3次之后才能按键有效 */
  27.     }
  28.     if (keyen)keyen--;
  29.     return res;
  30. }
复制代码
TPAD函数到此就编写完了,接下来我们通过main函数编写测试代码来验证一下TPAD的逻辑是否正确。

2. main.c代码
在main.c里面编写如下代码:
  1. int main(void)
  2. {
  3. uint8_t t = 0;
  4.     HAL_Init();                            /* 初始化HAL库 */
  5.    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
  6.     delay_init(168);                       /* 延时初始化 */
  7.     usart_init(115200);                   /* 串口初始化为115200 */
  8.     led_init();                             /* 初始化LED */
  9.     tpad_init(8);                           /* 初始化触摸按键 */
  10.     while (1)
  11.     {
  12.         if (tpad_scan(0))    /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */
  13.         {
  14.            LED1_TOGGLE();   /* LED1取反 */
  15.         }
  16.         t++;
  17.         if (t == 15)
  18.         {
  19.             t = 0;
  20.            LED0_TOGGLE();   /* LED0取反 */
  21.         }
  22.         delay_ms(10);
  23.     }
  24. }
复制代码

初始化了必要的外设后,我们通过循环来实现我们的代码操作。我们在扫描函数中定义了电容按触摸发生后的状态,通过判断返回值来判断是否符合按下的条件,如果按下我们就翻转一次LED1。LED0通过累计延时次数的方法,既能保证扫描的频率,又能达到定时翻转的目的。

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

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-11-23 02:25

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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