人若无名 便可潜心练剑.|

hazy1k

园龄:7个月粉丝:14关注:0

2024-09-11 16:24阅读: 23评论: 0推荐: 0

第26章 电容按键检测实验

第二十六章 电容按键检测实验

1. 硬件设计

标示TPAD1在电路板上就是电容按键实体,默认通过一个调帽连接到PA1,即通用定时器TIM5的通道2。 充电电容的阻值为5.1M,电阻的大小决定了电容按键充电的时间。

2. 软件设计

2.1 编程大纲

  1. TIM相关参数宏定义

  2. TIM GPIO和Mode配置

  3. 电容按键基本操作函数

  4. 主函数测试

2.2 代码分析

2.2.1 TIM相关参数宏定义

/* TIM相关参数宏定义 */
#define TPAD_TIM               TIM5                   
#define TPAD_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd 
#define TPAD_TIM_CLK           RCC_APB1Periph_TIM5    
#define TPAD_TIM_Period        0XFFFF                
#define TPAD_TIM_Prescaler     71                     
/* TIM_CHxia相关参数宏定义 */
#define TPAD_TIM_CH_GPIO_CLK   RCC_APB2Periph_GPIOA  
#define TPAD_TIM_CH_PORT       GPIOA                 
#define TPAD_TIM_CH_PIN        GPIO_Pin_1            
#define TPAD_TIM_CHANNEL_x     TIM_Channel_2 // 输入捕获通道
/* TIM中断相关参数宏定义 */
#define TPAD_TIM_IT_CCx        TIM_IT_CC2           
#define TPAD_TIM_IRQ           TIM5_IRQn            
#define TPAD_TIM_INT_FUN       TIM5_IRQHandler   
// 获取捕获寄存器值函数宏定义
#define TPAD_TIM_GetCapturex_FUN       TIM_GetCapture2
// 捕获信号极性函数宏定义
#define TPAD_TIM_OCxPolarityConfig_FUN TIM_OC2PolarityConfig
// 电容按键被按下的时候门限值,需要根据不同的硬件实际测试,减小这个门限值可以提供响应速度
#define TPAD_GATE_VAL         70
// 电容按键空载的时候的最大和最小的充电时间,不同的硬件不一样,指南者稳定在76
#define TPAD_DEFAULT_VAL_MIN  70
#define TPAD_DEFAULT_VAL_MAX  80

#define TPAD_ON   1
#define TPAD_OFF  0

有关宏的具体含义配套注释阅读即可。定时器计数器的时钟分频因子,我们通过宏TPAD_TIM_Prescaler默认配置为71, 则计数器的计数时间为(71+1)/72M =1us,自动重装载寄存器ARR的值通过TPAD_TIM_Period默认配置为0XFFFF,即65535。 所以,计数器在不发生溢出的情况下,能计数的最长时间为65.535ms。而一般电容按键的充电时间都是us级别,所以定时器的这个配置足以。

2.2.2 电容按键GPIO和Mode配置

// 电容按键GPIO初始化
void TPAD_TIM_GPIO_Config(void) 
{
      GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(TPAD_TIM_CH_GPIO_CLK, ENABLE); 
    GPIO_InitStructure.GPIO_Pin =  TPAD_TIM_CH_PIN;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
      GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
}
// 前置知识
/*
    1.ARR:自动重载寄存器的值,本工程设置为0xFFFF,即计数器的最大值
    2.CNTxCLK:计数器的时钟频率,APB1的时钟频率为72M,所以CNTxCLK=72/(psc+1) = 1MHz
    3.PWN信号的周期T:T = ARR*(psc+1)/72 = ARR*1
    4.占空比P:P = CCR/(ARR+1),其中CCR为比较寄存器的值,代表高电平在周期的时间
*/
// 定时器模式配置
static void TPAD_TIM_Mode_Config(void)
{
      TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TPAD_TIM_APBxClock_FUN(TPAD_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
    TIM_TimeBaseStructure.TIM_Period = TPAD_TIM_Period;    
    TIM_TimeBaseStructure.TIM_Prescaler = TPAD_TIM_Prescaler;    
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;    
    TIM_TimeBaseInit(TPAD_TIM, &TIM_TimeBaseStructure);
/*--------------------输入捕获结构体初始化----------------------*/        
    TIM_ICInitStructure.TIM_Channel = TPAD_TIM_CHANNEL_x;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0;
    TIM_ICInit(TPAD_TIM, &TIM_ICInitStructure);
    TIM_Cmd(TPAD_TIM, ENABLE);
}

TPAD_TIM_Mode_Config()函数中初始化了两个结构体,有关这两个结构体成员的具体含义可参考“定时器初始化结构体详解”小节, 剩下的程序参考注释阅读即可。有个地方要注意的是捕获信号的极性配置,需要配置为上升沿。因为电容按键在放电之后再充电的时候是一个电平由低到高的过程。


关于输入捕获结构体几个成员解释:

TIM_ICInitStructure.TIM_Channel = TPAD_TIM_CHANNEL_x;
  • TIM_Channel:设置定时器的输入捕获通道。TPAD_TIM_CHANNEL_x 应替换为实际的通道,如 TIM_Channel_1TIM_Channel_2 等,具体取决于所使用的定时器和引脚配置。
// 输入捕获信号的极性配置
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  • TIM_ICPolarity:设置捕获信号的极性。TIM_ICPolarity_Rising 表示在上升沿时进行捕获。另一种选项是 TIM_ICPolarity_Falling,表示在下降沿进行捕获。
// 输入通道和捕获通道的映射关系,有直连和非直连两种
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  • TIM_ICSelection:选择输入捕获通道的映射方式。
    • TIM_ICSelection_DirectTI:直接选择输入信号。
    • TIM_ICSelection_IndirectTI:选择输入信号经过预分频器处理后再捕获(通常用于更复杂的信号处理)。
// 输入的需要被捕获的信号的分频系数
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  • TIM_ICPrescaler:设置输入捕获的分频系数。TIM_ICPSC_DIV1 表示不进行分频。可以选择 TIM_ICPSC_DIV2TIM_ICPSC_DIV4 或 TIM_ICPSC_DIV8,根据实际需要来决定分频级别。
// 输入的需要被捕获的信号的滤波系数
TIM_ICInitStructure.TIM_ICFilter = 0;
  • TIM_ICFilter:设置输入信号的滤波系数,滤波系数决定了在捕获前对输入信号的去噪程度。0 表示没有滤波。可以选择更高的滤波系数来减少噪声的影响,但也会增加延迟。
// 定时器输入捕获初始化
TIM_ICInit(TPAD_TIM, &TIM_ICInitStructure);
  • TIM_ICInit:将配置应用到指定的定时器(TPAD_TIM)。TPAD_TIM 应该替换为实际的定时器实例,如 TIM1TIM2 等。

2.2.3 电容按键基本操作函数

2.2.3.1 复位按键
void TPAD_Reset(void) // 电容按键复位
{    
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(TPAD_TIM_CH_GPIO_CLK, ENABLE);
    GPIO_InitStructure.GPIO_Pin =  TPAD_TIM_CH_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
    GPIO_ResetBits(TPAD_TIM_CH_PORT, TPAD_TIM_CH_PIN);
    Delay_ms(5);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
      GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
}

开发板上电之后,电容按键默认已经充满了电,要想测得电容按键的充电时间 就必须先把电容按键的电放掉, 方法为让接电容按键的IO输出低电平即可,这个放电的时间一般都是us级别,我们可以稍微延时以下即可。放电完毕之后, 再把连接电容按键的IO配置为输入,然后通过输入捕获的方法测量电容按键的充电时间,这个充电时间T1是没有手指触摸的情况下的充电时间, 而且这个空载的充电时间非常稳定,因为电路板的硬件已经确定了。当有手指触摸的情况下,相当于电容变大,充电时间T2会变长, 我们只需要对比这两个时间就可以 知道电容按键是否有手指触摸。

简单来说就是:由于我们开发板接电后,电容会充满电,所以我们需要放电-即给IO输出低电平,然后再对这个IO配置为输入捕获,用来检测有无触摸电容按键的情况下各自充电时间

2.2.3.2 电容按键初始化
// 初始化触摸按键,获得空载的时候触摸按键的充电时间
uint8_t TPAD_Init(void)
{
    uint16_t temp;
    TPAD_TIM_Init();
    temp = TPAD_Get_Val();
    // 调试的时候可以把捕获的值打印出来,看看默认的充电时间是多少
    printf("电容按键默认充电时间为: %d us\n",temp);
    if((TPAD_DEFAULT_VAL_MIN<temp) && (temp<TPAD_DEFAULT_VAL_MAX)) // 确保默认的充电时间在合理范围内
    {
        tpad_default_val = temp;
        return 0;  // 成功
    }
    else
    {
        return 1; // 失败
    }
}

TPAD_Init() 函数用来获取电容按键空载的充电时间,当获取到之后,把值存在tpad_default_val这个全局变量当中。 这个空载的充电时间不同的硬件是不一样的,需要实际测试,在调试的过程中,可把捕获到的值打印出来看看。霸道开发板上这个值稳定在218,指南者则稳定在76。

在TPAD_Init() 函数中,我们是通过调用TPAD_Get_Val()函数来获取电容按键的充电时间的。 当电容按键从0开始充电到STM32能够识别的高电平时, 定时器则发生捕获,此时计数器的值会被锁存到输入捕获寄存器,我们只需要读取输入捕获寄存器的值,就可以算出这个充电的时间。 通过TPAD_Get_Val()这个函数,我们可以测出电容按键的空载充电时间T1和有手触摸的情况下的充电时间T2。

2.2.3.3 获取TIM输入捕获值取最大
uint16_t TPAD_Get_Val(void) // 获取定时器捕获值
{        
    TPAD_Reset();
    // 当电容按键复位放电之后,计数器清0开始计数
      TIM_SetCounter (TPAD_TIM, 0);
    TIM_ClearITPendingBit(TPAD_TIM, TPAD_TIM_IT_CCx | TIM_IT_Update);
    // 等待捕获上升沿,当电容按键充电到1.8V左右的时候,就会被认为是上升沿
    while(TIM_GetFlagStatus (TPAD_TIM, TPAD_TIM_IT_CCx) == RESET)
    {
        if (TIM_GetCounter(TPAD_TIM) > TPAD_TIM_Period-100) // 超时处理
        {
            return TIM_GetCounter (TPAD_TIM);
        }             
    }
    return TPAD_TIM_GetCapturex_FUN(TPAD_TIM); // 获取捕获值
}

// 获得多个触摸按键的最大值
uint16_t TPAD_Get_MaxVal( uint8_t num )
{
    uint16_t temp=0, res=0;
    while(num--)
    {
        temp = TPAD_Get_Val();
        if( temp > res ) 
            res = temp;
    }    
    return res;    
} 
2.2.3.4 电容按键扫描
// 按键扫描函数
uint8_t TPAD_Scan(void)
{
    static uint8_t keyen=0;    
    uint8_t res=0,sample=3; 
    uint16_t scan_val;        
    // 根据sample值采样多次,并取最大值,小的一般是干扰或者是误触摸
    scan_val = TPAD_Get_MaxVal(sample); 
    // 当扫描的值大于空载值加上默认的门限值之后,表示按键按下
    if(scan_val > (tpad_default_val+TPAD_GATE_VAL))
    {                        
        // 再次检测,类似于机械按键的去抖
        scan_val = TPAD_Get_MaxVal(sample);        
        if((keyen == 0 )&&(scan_val > (tpad_default_val+TPAD_GATE_VAL)))
                res = 1;    // 有效的按键
        keyen = 2;
    }
    if(keyen > 0)
        keyen--;
    return res;
}

按键扫描函数不断的检测充电时间,当大于tpad_default_val+TPAD_GATE_VAL时,表示按键被按下,其中TPAD_GATE_VAL是一个宏, 具体多大需要实际测试。具体的我们可以通过调用TPAD_Get_Val()函数来测试按键有手触摸的情况下的充电值, 然后再减去tpad_default_val的值就可以得到TPAD_GATE_VAL,当减小这个门限值的时候可以提高按键的灵敏度。

在按键扫描函数中,我们引入了一个按键检测标志keyen,其由关键字static修饰,相当于一个全局变量, 每次修改这个变量的时候其保留的都是上一次的值。引入一个按键检测标志是为了消除按键是否一直按下的情况, 如果按键一直被按下keyen的值会一直在keyen的初始值和keyen-1之间循环,永远不会等于0,则永远都不会被认为按键按下, 需要等待释放。有关函数更加详细的说明看程序的注释即可。

2.2.4 主函数测试

// TIM—通用-捕获-电容按键检测 应用
#include "stm32f10x.h"
#include "usart.h" 
#include "SysTick.h"
#include "beep.h"
#include "tpad.h"

int main(void)
{
    SysTick_Init();
    USART_Config();
    BEEP_GPIO_Config();
    TPAD_Init(); 
    while(1)
    {
        if(TPAD_Scan() == TPAD_ON)
        {
            BEEP(ON);
            printf("按下按键\r\n");
            Delay_ms(100);
            BEEP(OFF);
        }
    }
}

主函数初始化了蜂鸣器和串口,然后等待电容按键初始化成功,如果不成功则会一直等待。初始化成功之后, 在一个while无限循环中不断的扫描按键,当按键按下之后蜂鸣器响100ms,然后关掉。

3. 小结

下面我们来回顾一下基本流程:

  1. 硬件连接
  • 将电容按键连接到 STM32F103 的输入引脚。
  • 将定时器的输入通道(通常是一个捕获输入引脚)连接到电容按键的输出。
  1. 初始化 STM32F103 的定时器
  • 配置定时器的计数器和输入捕获功能。
  • 设置定时器的捕获通道以响应电容按键的状态变化。
  1. 初始化 GPIO 引脚
  • 配置用于电容按键的 GPIO 引脚为输入模式。
  • 配置用于定时器输入捕获的 GPIO 引脚为复用功能模式。
  1. 编写定时器和捕获功能的初始化代码
  • 设置定时器的预分频器、计数器周期等参数。
  • 配置捕获模式(例如上升沿、下降沿或双边沿)。
  1. 编写中断服务程序(ISR)或轮询代码
  • 处理定时器中断以获取捕获值。
  • 根据捕获的值来分析电容按键的状态。

下面是一个使用 STM32F103 定时器输入捕获功能的示例代码,展示了如何配置定时器来检测电容按键的状态。

1. 初始化代码

#include "stm32f10x.h"

// 初始化 GPIO 引脚
void GPIO_Init(void) {
    // 启用 GPIOB 时钟(假设 TIM1 的通道连接到 GPIOB)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    // 配置 TIM1_CH1 引脚(假设为 GPIOB.6)为复用功能
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // TIM1_CH1
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

// 初始化定时器
void TIM_Init(void) {
    // 启用 TIM1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;

    // 定时器基础配置
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重装载寄存器的值
    TIM_TimeBaseStructure.TIM_Prescaler = 0; // 时钟分频因子
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    // 配置输入捕获
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0;
    TIM_ICInit(TIM1, &TIM_ICInitStructure);

    // 使能定时器
    TIM_Cmd(TIM1, ENABLE);

    // 使能输入捕获中断
    TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);

    // 配置 NVIC 中断
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

2. 定时器中断服务程序

void TIM1_CC_IRQHandler(void) {
    if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET) {
        // 读取捕获值
        uint16_t capture = TIM_GetCapture1(TIM1);

        // 处理捕获值,例如计算电容按键的状态
        // ...

        // 清除中断标志位
        TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
    }
}

2024.9.11 第一次修订,后期不再维护

2025.1.24 修补内容

本文作者:hazy1k

本文链接:https://www.cnblogs.com/hazy1k/p/18408435

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hazy1k  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起