电容按键 其实只需要理解,手指按上去后充电时间变长,我们可以利用定时器输入捕获功能计算充电时间,超过无触摸时的充电时间一定的阈值就认为是有手指触摸。
基本原理就是这样,我们开始写代码:
其实,看过了上一章内容,就知道,我们只需要把TIM环境配置好,就相当于把HAL库搬了过来,直接使用HAL库的例程就可以了。
这是一个放电的过程,我们需要它一开始时是没有电的
static void TPAD_Reset(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//配置引脚为普通推挽输出
GPIO_InitStructure.Pin = TPAD_TIM_CH_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
//输出低电平,放电
HAL_GPIO_WritePin ( TPAD_TIM_CH_PORT, TPAD_TIM_CH_PIN ,GPIO_PIN_RESET);
//保持一小段时间低电平,保证放电完全
HAL_Delay(5);
//清除更新标志
__HAL_TIM_CLEAR_FLAG(&TIM_Handle,TIM_FLAG_CC1);
__HAL_TIM_CLEAR_FLAG(&TIM_Handle,TIM_FLAG_UPDATE);
//计数器归0
__HAL_TIM_SET_COUNTER(&TIM_Handle,0);
//引脚配置为复用功能,不上、下拉
GPIO_InitStructure.Pin = TPAD_TIM_CH_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Alternate = TPAD_TIM_AF;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TPAD_TIM_CH_PORT,&GPIO_InitStructure);
}
然后是tpad.c的完整代码:
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-06-14 c the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <tpad.h>
#define TPAD_ARR_MAX_VAL 0XFFFF
//保存没按下时定时器计数值
__IO uint16_t tpad_default_val=0;
/***********************************
*
* 定时器输入捕获配置
*
***********************************/
TIM_HandleTypeDef TIM_Handle;
static void TIMx_CHx_Cap_Init(uint32_t arr,uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_IC_InitTypeDef sConfigIC;
//使能TIM时钟
TPAD_TIM_CLK_ENABLE();
//使能通道引脚时钟
TPAD_TIM_GPIO_CLK_ENABLE();
//端口配置
GPIO_InitStructure.Pin = TPAD_TIM_CH_PIN;
//复用功能
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Alternate = TPAD_TIM_AF;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
//不带上下拉
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
//初始化TIM
//设定计数器自动重装值
TIM_Handle.Instance = TPAD_TIMx;
TIM_Handle.Init.Prescaler = psc;
TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Handle.Init.RepetitionCounter = 0;
TIM_Handle.Init.Period = arr;
TIM_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&TIM_Handle);
//上升沿触发
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
// 输入捕获选择
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
//配置输入分频,不分频
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
//配置输入滤波器 不滤波
sConfigIC.ICFilter = 0;
//初始化捕获通道
HAL_TIM_IC_ConfigChannel(&TIM_Handle, &sConfigIC, TPAD_TIM_Channel_X);
//启动TIM
HAL_TIM_IC_Start(&TIM_Handle, TPAD_TIM_Channel_X);
}
/****************************************
*
* 为电容按键放电
* 清除定时器标志及计数
*
*****************************************/
static void TPAD_Reset(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//配置引脚为普通推挽输出
GPIO_InitStructure.Pin = TPAD_TIM_CH_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(TPAD_TIM_CH_PORT, &GPIO_InitStructure);
//输出低电平,放电
HAL_GPIO_WritePin ( TPAD_TIM_CH_PORT, TPAD_TIM_CH_PIN ,GPIO_PIN_RESET);
//保持一小段时间低电平,保证放电完全
HAL_Delay(5);
//清除更新标志
__HAL_TIM_CLEAR_FLAG(&TIM_Handle,TIM_FLAG_CC1);
__HAL_TIM_CLEAR_FLAG(&TIM_Handle,TIM_FLAG_UPDATE);
//计数器归0
__HAL_TIM_SET_COUNTER(&TIM_Handle,0);
//引脚配置为复用功能,不上、下拉
GPIO_InitStructure.Pin = TPAD_TIM_CH_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Alternate = TPAD_TIM_AF;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TPAD_TIM_CH_PORT,&GPIO_InitStructure);
}
/****************************************************
*
* 得到定时器捕获值
* 如果超时,则直接返回定时器的计数值.
*
*****************************************************/
static uint16_t TPAD_Get_Val(void)
{
/* 先放电完全,并复位计数器 */
TPAD_Reset();
//等待捕获上升沿
while(__HAL_TIM_GET_FLAG(&TIM_Handle,TIM_FLAG_CC1) == RESET )
{
//超时了,直接返回CNT的值
if(__HAL_TIM_GET_COUNTER( &TIM_Handle)>TPAD_ARR_MAX_VAL-500)
return __HAL_TIM_GET_COUNTER( &TIM_Handle);
};
/* 捕获到上升沿后输出TIMx_CCRx寄存器值 */
return HAL_TIM_ReadCapturedValue(&TIM_Handle, TIM_CHANNEL_1);
}
/****************************************************
*
* 读取n次,取最大值
* n:连续获取的次数
* 返回值:n次读数里面读到的最大读数值
*
*****************************************************/
static uint16_t TPAD_Get_MaxVal(uint8_t n)
{
uint16_t temp=0;
uint16_t res=0;
while(n--)
{
temp=TPAD_Get_Val();//得到一次值
if(temp>res)res=temp;
};
return res;
}
/********************************************************
*
* 初始化触摸按键
* 获得空载的时候触摸按键的取值.
* 返回值:0,初始化成功;1,初始化失败
*
*********************************************************/
uint8_t TPAD_Init(void)
{
uint16_t buf[10];
uint32_t temp=0;
uint8_t j,i;
//设定定时器预分频器目标时钟为:9MHz(216Mhz/24)
TIMx_CHx_Cap_Init(TPAD_ARR_MAX_VAL,24-1);
for(i=0;i<10;i++)//连续读取10次
{
buf[i]=TPAD_Get_Val();
HAL_Delay(10);
}
for(i=0;i<9;i++)//排序
{
for(j=i+1;j<10;j++)
{
if(buf[i]>buf[j])//升序排列
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
temp=0;
//取中间的6个数据进行平均
for(i=2;i<8;i++)
{
temp+=buf[i];
}
tpad_default_val=temp/6;
/* printf打印函数调试使用,用来确定阈值TPAD_GATE_VAL,在应用工程中应注释掉 */
//printf("tpad_default_val:%d\r\n",tpad_default_val);
//初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常!
if(tpad_default_val>TPAD_ARR_MAX_VAL/2)
{
return 1;
}
return 0;
}
uint8_t TPAD_Scan(uint8_t mode)
{
//0,可以开始检测;>0,还不能开始检测
static uint8_t keyen=0;
//扫描结果
uint8_t res=0;
//默认采样次数为3次
uint8_t sample=3;
//捕获值
uint16_t rval;
if(mode)
{
//支持连按的时候,设置采样次数为6次
sample=6;
//支持连按
keyen=0;
}
/* 获取当前捕获值(返回 sample 次扫描的最大值) */
rval=TPAD_Get_MaxVal(sample);
/* printf打印函数调试使用,用来确定阈值TPAD_GATE_VAL,在应用工程中应注释掉 */
//printf("scan_rval=%d\n",rval);
//大于tpad_default_val+TPAD_GATE_VAL,且小于10倍tpad_default_val,则有效
if(rval>(tpad_default_val+ 100)&&rval<(10*tpad_default_val))
{
//keyen==0,有效
if(keyen==0)
{
res=1;
}
keyen=3; //至少要再过3次之后才能按键有效
}
if(keyen)
{
keyen--;
}
return res;
}
接下来是tpad.h
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-06-14 c the first version
*/
#ifndef APPLICATIONS_TPAD_H_
#define APPLICATIONS_TPAD_H_
#define TPAD_TIMx TIM2
#define TPAD_TIM_CLK_ENABLE() __TIM2_CLK_ENABLE()
#define TPAD_TIM_Channel_X TIM_CHANNEL_1
#define TPAD_TIM_GetCaptureX TIM_GetCapture1
#define TPAD_TIM_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define TPAD_TIM_CH_PORT GPIOA
#define TPAD_TIM_CH_PIN GPIO_PIN_5
#define TPAD_TIM_AF GPIO_AF1_TIM2
/************************** TPAD 函数声明********************************/
uint8_t TPAD_Init(void);
uint8_t TPAD_Scan(uint8_t mode);
#endif /* APPLICATIONS_TPAD_H_ */
最后是main.c
/*
* Copyright (c) 2006-2025, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-06-14 RT-Thread first version
*/
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include <rtdevice.h>
#include <board.h>
#include <tpad.h>
#define LED_R_PIN GET_PIN(H, 10)
static rt_base_t led_r_stat = PIN_LOW;
int main(void)
{
TPAD_Init();
rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT);
while (1)
{
if (TPAD_Scan(0)) {
led_r_stat = (led_r_stat == PIN_LOW) ? PIN_HIGH : PIN_LOW;
rt_pin_write(LED_R_PIN, led_r_stat);
}
}
return RT_EOK;
}
感觉代码太不RTthread了。直接从HAL库例程平移过来。不过不管怎么说,电容按键可以使用了。