数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 26|回复: 0

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

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
第二十二章 电容触摸按键实验



上一章,我们介绍了 STM32H7R7 的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用 TIM3的通道 3PB0)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制 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 之间形成的电容。图中的开关是电容放电开关(由实际使用时,由 STM32H7R7IO 代替)。
先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R Cs(或 Cs+Cx)充电,当没有手指触摸的时候, Cs 的充电曲线如图中的 A 曲线。而当有手指触摸的时候,手指和 TPAD之间引入了新的电容 Cx,此时 Cs+Cx 的充电曲线如图中的 B 曲线。从上图可以看出, AB两种情况下, 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_CH3PB0)对电容触摸按键的检测,实现对DS1的控制, 下载本代码后,我们通过按压开发板右下角的TPAD按钮,就可以控制DS1的亮灭了。
2LED0闪烁 ,提示程序运行。
2. 硬件资源
1LED灯
     LED0:LED0 PD14
LED1:LED1 PC0
2TPAD电容触摸按键(右下角LOGO,即TPAD,连接在PB0
3)定时器3TIM3),TIM3的通道3TIM3_CH3,连接在PB0上面)
3. 原理图
我们需要通过TIM3_CH3PB0)采集TPAD的信号,所以本实验需要用跳线帽短接多功能端口(P14)的TPADADC,以实现TPAD连接到PB0,连接如图22.2.1所示:
图22.2.1 TPADSTM32H7R7连接原理图
硬件设置(用跳线帽短接多功能端口的ADCTPAD即可)好之后,下面我们开始软件设计。
22.3 程序设计
我们在基本定时器一节已经学习过定时器的输入捕获功能,这里我们可以类似地,用定时器3来实现对TPAD引脚上的电平状态的捕获功能。本实验用到的HAL库驱动请回顾基本定时器实验的介绍。下面直接从程序流程图开始介绍。
22.3.1 程序解析
TPAD 可以看作《实验 9-3 通用定时器输入捕获实验》 的一个应用案例,相关的 HAL 库函数函数在定时器章节我们已经介绍过了,所以这里就不重复介绍了,大家回过头复习一下即可
1. TPAD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,TPAD的驱动主要包括两个文件:tpad.ctpad.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是定时器3PWM通道1,如果我们使用其他定时器和它们对应的捕获通道的其它IO,我们只需要修改上面的宏即可。
利用前面描述的触摸按键的原理上电时检测 TPAD 上的电容的充放电时间,并以此为基准,每次需要重新检测 TPAD 时,通过比较充放电的时长来检测当前是否有按下,所以我们需要用定时器的输入捕获来监测TPAD 上低电平的时间编写tpad_timx_cap_init()函数如下:
/**
* @brief   初始化定时器输入捕获
* @param   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 = tpad_get_val();
        delay_ms(10);
    }

    /* 升序排序 */
    for (i=0; i<9; i++)
    {
        for (j=i+1; j<10; j++)
        {
            if (values > values[j])
            {
                values = values ^ values[j];
                values[j] = values ^ values[j];
                values = values ^ values[j];
            }
        }
    }
    /* 对中间的6个数据进行均值滤波得到默认捕获值 */
    for (i=2; i<8; i++)
    {
        value += values;
    }
    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都是连接到开发板上的排针上的,开始测试前需要连接好,否则测试就不准了,如果下载代码前没有连接好,请连接后复位重新测试即可。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

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

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|数码之家-技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2026-8-20 13:04 , Processed in 0.093600 second(s), 6 queries , Gzip On, Redis On.

Powered by Discuz!

© MyDigit.Net Since 2006

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