赞
踩
一、电容触摸按键原理介绍
二、检测电容触摸按键过程
三、编程实战
四、总结
电容触摸按键是一种常见的电子开关,它通过检测人体的电容变化来实现按键操作。其原理基于电容的变化,具体介绍如下:
电容感应原理:电容触摸按键利用人体和地面之间的电容来检测触摸。当人体靠近电容触摸区域时,人体与地面之间的电容会发生变化,因为人体是导电的,会对电场产生影响。这种电容变化可以被电路感知到。
电容传感器:电容触摸按键通常使用的是电容传感器来检测电容的变化。传感器通常由一对电极构成,一个是发送电极,负责发送电场,另一个是接收电极,负责接收电场。当有人触摸时,人体作为第三电极会改变电场,从而改变接收电极的电容值。
检测电路:检测电路通常由电容传感器、信号处理电路和控制器组成。电容传感器用来检测电容变化,信号处理电路用来放大和处理传感器输出的信号,控制器用来解读处理后的信号并执行相应的操作,比如开关灯、调节音量等。
工作原理:当有人触摸电容触摸按键时,电容传感器感知到电容变化,并将信号传送给信号处理电路。信号处理电路处理后的信号会传送给控制器,控制器根据接收到的信号执行相应的操作。例如,如果是控制灯光的开关,触摸按键后控制器会接收到信号并执行打开或关闭灯光的指令。
优点:电容触摸按键相比传统的机械按键具有一些优点,例如无接触、易清洁、寿命长、外观美观等,因此在许多应用中得到了广泛应用。
总的来说,电容触摸按键利用人体电容变化来实现按键操作,通过电容传感器和相应的电路进行信号检测和处理,从而实现各种应用场景下的控制操作。
两种情况下的充电过程:
无手指触摸情况:
有手指触摸情况:
在有手指触摸的情况下,因为额外的电容Cx也在充电,所以相比无手指触摸的情况,充电时间会相对延长,即Tcx > Tcs。这是由于手指的导电性使得整个电容系统的等效电容增大,需要更长的时间来充满。
RC电路充放电的情况,其中包含了两种情况的表达方式,具体如下:
一般情况下的公式:
V
t
=
V
0
+
(
V
1
−
V
0
)
×
(
e
−
t
R
C
)
V_t = V_0 + (V_1 - V_0) \times \left( e^{-\frac{t}{RC}} \right)
Vt=V0+(V1−V0)×(e−RCt)
这个公式描述了电容从初始电压
V
0
V_0
V0 到最终电压
V
1
V_1
V1 的充电或放电过程中,在任意时间
t
t
t 时刻的电压
V
t
V_t
Vt。其中
R
C
RC
RC 是电路的时间常数。
简化情况下的公式:
当初始电压
V
0
V_0
V0 为0时,即从0V开始充电,可以简化为:
V
t
=
V
1
×
(
e
−
t
R
C
)
V_t = V_1 \times \left( e^{-\frac{t}{RC}} \right)
Vt=V1×(e−RCt)
这个公式描述了电容从0V开始充电到最终电压
V
1
V_1
V1 的过程中,在任意时间
t
t
t 时刻的电压
V
t
V_t
Vt。
在这种情况下,假设电容值 C C C 与时间 t t t 成正比关系,即电容 C C C 越大,充电到达某个临界值的时间 t t t 越长。那么在给定的条件下,可以利用充放电公式来描述电容的充电过程。
考虑到您提到了两种电容 C s Cs Cs 和 C x Cx Cx,以及外接的充放电电阻 R R R,我们可以采用以下步骤:
计算等效电容值:将 C s Cs Cs 和 C x Cx Cx 并联得到一个等效电容 C e q C_{eq} Ceq,即 C e q = C s + C x C_{eq} = Cs + Cx Ceq=Cs+Cx。
确定临界电压值 V 1 V_1 V1:在STM32的输入端口为高电平时,电容充电到的临界电压值应该大于或等于STM32认为为高电平的最低电压值 V t h V_{th} Vth。
确定时间常数 R C RC RC:根据给定的外接电容充放电电阻 R R R 和等效电容 C e q C_{eq} Ceq,计算得到时间常数 R C RC RC。
使用充放电公式计算充电时间
t
t
t:根据简化情况下的公式,即
V
t
=
V
1
×
(
e
−
t
R
C
)
V_t = V_1 \times \left( e^{-\frac{t}{RC}} \right)
Vt=V1×(e−RCt)
根据已知的
V
1
V_1
V1,计算在给定时间
t
t
t 下电容的电压
V
t
V_t
Vt,直到
V
t
V_t
Vt 达到或超过
V
t
h
V_{th}
Vth 时停止充电,此时的时间
t
t
t 即为充电到达临界值的时间。
控制电容充电开关:利用STM32的IO口控制电容的充放电开关,当充电时间达到临界值时关闭充电开关,停止充电。
综上所述,根据您提供的条件,可以利用电容充放电公式和已知参数来计算电容充电到达临界值所需的时间,并通过STM32的IO口控制充电开关,实现对电容的精确充电。
可以利用定时器的输入捕获功能来实现充电时间的测量,进而检测是否有手指触摸。以下是大致的实现步骤:
初始化:首先,您需要初始化定时器和IO口。定时器配置为输入捕获模式,用于测量充电时间;IO口连接到电容的充电开关,用于控制电容的充放电。
放电:在开始之前,确保电容处于放电状态,即将充电开关关闭,电容开始放电,保持初始状态为0。
测量基准时间 T c s T_{cs} Tcs:启动定时器,开始测量充电时间 T c s T_{cs} Tcs。当电容充满并达到阈值 V t h V_{th} Vth 时,停止定时器并记录时间 T c s T_{cs} Tcs,作为无触摸时的基准时间。
循环测量充电时间 T T T:在一个定时循环中,启动定时器,并等待电容开始充电。当电容充满并达到阈值 V t h V_{th} Vth 时,停止定时器并记录时间 T T T。
比较 T T T 与 T c s T_{cs} Tcs:将测量得到的充电时间 T T T 与基准时间 T c s T_{cs} Tcs 进行比较。如果 T T T 超过一定阈值(例如 T c s T_{cs} Tcs 的某个倍数),则判断为有手指触摸。
反复执行:将步骤 4 和 5 放入一个循环中,以持续监测电容的充电时间,并根据需要进行触摸检测。
实时响应:根据触摸检测结果,可以实时进行相应的操作,比如触发事件、改变系统状态等。
通过这种方法,您可以利用定时器输入捕获功能来实现对电容充电时间的测量,并据此检测是否有手指触摸。
常见的电容触摸按键检测流程。以下是对每个步骤的进一步解释:
电容放电:TPAD引脚被设置为推挽输出,并输出低电平,这会导致电容开始放电到地。这样做是为了确保电容处于初始放电状态,以便开始新的充电周期。
电容充电:TPAD引脚被设置为浮空输入,使得电容开始充电。此时电容会通过外部电阻开始充电,充电电流会导致电压在电容上逐渐增加。
开启输入捕获功能:在电容开始充电的同时,开启TPAD引脚的输入捕获功能。这样,当电容充电到一定电压(达到阈值 V t h V_{th} Vth )时,可以检测到TPAD引脚上升沿的触发。
等待触发上升沿:充电过程中,持续监测TPAD引脚的状态,直到检测到上升沿触发。这表示电容充电已经达到了预设的阈值 V t h V_{th} Vth ,即触摸按键被触发。
计算充电时间:通过定时器捕获或比较寄存器获取充电时间,即从TPAD引脚设置为浮空输入开始到检测到上升沿触发的时间间隔。这个时间间隔 T T T 就是电容充电所需的时间。
判断触摸按键状态:通过比较充电时间 T T T 与预设的基准时间 T 1 T1 T1 ,如果 T − T 1 T - T1 T−T1 大于某个阈值,就可以判断为触摸按键被按下。因为电容的大小会影响充电时间,所以当电容增大时,充电时间 T T T 会相应增加。
这种方法通过测量电容充电时间来判断触摸按键是否被按下,利用了电容大小对充电时间的影响,实现了简单而有效的触摸检测。
这些关键函数的功能描述:
tpad_reset函数:
tpad_get_val函数:
tpad_get_maxval函数:
tpad_init函数:
tpad_scan函数:
tpad_timx_cap_init函数:
这些函数共同实现了对电容触摸按键的扫描和检测功能,通过测量电容充电时间来判断是否有手指触摸按下。
以下是对这些函数的功能进一步解释:
tpad_init函数:
tpad_get_val函数:
tpad_reset函数:
tpad_scan函数:
这些函数共同实现了电容触摸按键的初始化、扫描和检测功能,通过测量电容充电时间来判断是否有手指触摸按下,并支持连按和不连按的检测。
#include "./BSP/TPAD/tpad.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
/******************************************************************************************/
/* 空载的时候(没有手按下),计数器需要的时间
* 这个值应该在每次开机的时候被初始化一次
*/
volatile uint16_t g_tpad_default_val = 0; /* 空载的时候(没有手按下),计数器需要的时间 */
/* 定时器输入边沿捕获 */
static TIM_HandleTypeDef g_timx_cap_chy_handle; /* 定时器x句柄 */
/**
* @brief 初始化触摸按键
* @param psc : 分频系数(值越小, 越灵敏, 最小值为: 1)
* @retval 0, 初始化成功; 1, 初始化失败;
*/
uint8_t tpad_init(uint16_t psc)
{
uint16_t buf[10]; // 用于存储多次读取的值
uint16_t temp; // 临时变量
uint8_t j, i; // 循环变量
/* 1、初始化定时器输入捕获 */
tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc - 1); /* 以72 / (psc - 1) Mhz的频率计数 */
/* 2、得到没有电容触摸按键按下时默认的充电时间g_tpad_default_val */
for (i = 0; i < 10; i++) /* 连续读取10次 */
{
buf[i] = tpad_get_val(); // 获取一次充电时间
delay_ms(10); // 延时10毫秒,等待下一次读取
}
// /* 方法1 */
// 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;
// }
// }
// }
/* 对读取的值进行排序,选取中间6次的值进行平均,作为基准时间 */
/* 方法2(经典冒泡算法) */
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9 - i; j++)
{
if (buf[j] > buf[j + 1])
{
temp = buf[j];
buf[j] = buf[j + 1];
buf[j + 1] = temp;
}
}
}
temp = 0;
for (i = 2; i < 8; i++) // 取中间的6个数据进行平均
{
temp += buf[i];
}
g_tpad_default_val = temp / 6; // 计算平均值并赋值给基准时间
printf("g_tpad_default_val:%d\r\n", g_tpad_default_val); /* 输出基准时间 */
printf("charging time:%d\r\n", g_tpad_default_val / (psc - 1)); /* 输出充电时间 */
if (g_tpad_default_val > TPAD_ARR_MAX_VAL / 2) // 如果基准时间大于计数器最大值的一半,返回错误
{
return 1; /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */
}
return 0;
}
/**
* @brief 复位TPAD
* @note 我们将TPAD按键看做是一个电容, 当手指按下/不按下时容值有变化
* 该函数将GPIO设置成推挽输出, 然后输出0, 进行放电, 然后再设置
* GPIO为浮空输入, 等待外部大电阻慢慢充电
* @param 无
* @retval 无
*/
static void tpad_reset(void)
{
GPIO_InitTypeDef gpio_init_struct;
/* 设置GPIO为推挽输出,输出低电平,实现电容放电到地 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET); /* TPAD引脚输出0, 放电 */
delay_ms(5); // 延时5毫秒
/* 清空定时器计数器CNT值 */
g_timx_cap_chy_handle.Instance->SR = 0; /* 清除标记 */
g_timx_cap_chy_handle.Instance->CNT = 0; /* 归零 */
/* 设置GPIO为浮空输入,等待电容充电 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 浮空 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚浮空输入 */
}
/**
* @brief 得到定时器捕获值
* @note 如果超时, 则直接返回定时器的计数值
* 我们定义超时时间为: TPAD_ARR_MAX_VAL - 500
* @param 无
* @retval 捕获值/计数值(超时的情况下返回)
*/
static uint16_t tpad_get_val(void)
{
uint32_t 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 : TIM_FLAG_CC4;
tpad_reset(); /* 复位TPAD */
while (__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle, flag) == RESET) /* 等待通道CHY捕获上升沿 */
{
if (g_timx_cap_chy_handle.Instance->CNT > TPAD_ARR_MAX_VAL - 500)
{
return g_timx_cap_chy_handle.Instance->CNT; /* 超时了,直接返回CNT的值 */
}
}
return TPAD_TIMX_CAP_CHY_CCRX; /* 返回捕获/比较值 */
}
/**
* @brief 读取n次, 取最大值
* @param n :连续获取的次数
* @retval n次读数里面读到的最大读数值
*/
static uint16_t tpad_get_maxval(uint8_t n)
{
uint16_t temp = 0;
uint16_t maxval = 0;
while (n--)
{
temp = tpad_get_val(); /* 得到一次值 */
if (temp > maxval)
{
maxval = temp;
}
}
return maxval;
}
/**
* @brief 扫描触摸按键
* @param mode :扫描模式
* @arg 0, 不支持连续触发(按下一次必须松开才能按下一次);
* @arg 1, 支持连续触发(可以一直按下)
* @retval 0, 没有按下; 1, 有按下;
*/
uint8_t tpad_scan(uint8_t mode)
{
static uint8_t keyen = 0; /* 0, 可以开始检测; >0, 还不能开始检测; */
uint8_t res = 0;
uint8_t sample = 3; /* 默认采样次数为3次 */
uint16_t rval;
if (mode)
{
sample = 6; /* 支持连按的时候,设置采样次数为6次 */
keyen = 0; /* 支持连按, 每次调用该函数都可以检测 */
}
rval = tpad_get_maxval(sample);
if (rval > (g_tpad_default_val + TPAD_GATE_VAL)) /* 大于tpad_default_val+TPAD_GATE_VAL,有效 */
{
if (keyen == 0)
{
res = 1; /* keyen==0, 有效 */
}
//printf("r:%d\r\n", rval); /* 输出计数值, 调试的时候才用到 */
keyen = 3; /* 至少要再过3次之后才能按键有效 */
}
if (keyen)
{
keyen--;
}
return res;
}
/**
* @brief 触摸按键输入捕获设置
* @param arr :自动重装值
* @param psc :时钟预分频数
* @retval 无
*/
static void tpad_timx_cap_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_IC_InitTypeDef timx_ic_cap_chy;
TPAD_GPIO_CLK_ENABLE(); /* TPAD引脚 时钟使能 */
TPAD_TIMX_CAP_CHY_CLK_ENABLE(); /* 定时器 时钟使能 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚浮空输入 */
g_timx_cap_chy_handle.Instance = TPAD_TIMX_CAP; /* 定时器5 */
g_timx_cap_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_timx_cap_chy_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_cap_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 映射到TI1上 */
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入分频,不分频 */
timx_ic_cap_chy.ICFilter = 0; /* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TPAD_TIMX_CAP_CHY); /* 配置TIM5通道2 */
HAL_TIM_IC_Start(&g_timx_cap_chy_handle, TPAD_TIMX_CAP_CHY); /* 使能输入捕获和定时器 */
}
#ifndef __TPAD_H
#define __TPAD_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* TPAD 引脚 及 定时器 定义 */
/* 我们使用定时器的输入捕获功能, 对TPAD进行检测
* 这里的输入捕获使用定时器TIM5_CH2, 捕获TPAD按键的输入
* 因为我们的TPAD是连接在PA1引脚上的, PA1只能是TIM2_CH2 / TIM5_CH2
* 所以定时器也只能在这两个里面选, 如果你自己设计的板卡, 则根据原理
* 图进行相应的修改即可, 包括GPIO 及 对应的定时器和通道
*/
#define TPAD_GPIO_PORT GPIOA
#define TPAD_GPIO_PIN GPIO_PIN_1
#define TPAD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define TPAD_TIMX_CAP TIM5
#define TPAD_TIMX_CAP_CHY TIM_CHANNEL_2 /* 通道Y, 1<= Y <=4 */
#define TPAD_TIMX_CAP_CHY_CCRX TIM5->CCR2 /* 通道Y的捕获/比较寄存器 */
#define TPAD_TIMX_CAP_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 */
/******************************************************************************************/
/* 触摸的门限值, 也就是必须大于 g_tpad_default_val + TPAD_GATE_VAL
* 才认为是有效触摸, 改大 TPAD_GATE_VAL, 可以降低灵敏度, 反之, 则可以提高灵敏度
* 根据实际需求, 选择合适的 TPAD_GATE_VAL 即可
*/
#define TPAD_GATE_VAL 100 /* 触摸的门限值, 也就是必须大于 g_tpad_default_val + TPAD_GATE_VAL, 才认为是有效触摸 */
#define TPAD_ARR_MAX_VAL 0XFFFF /* 最大的ARR值, 一般设置为定时器的ARR最大值 */
extern volatile uint16_t g_tpad_default_val;/* 空载的时候(没有手按下),计数器需要的时间 */
/* 静态函数, 仅限 tapd.c调用 */
static void tpad_reset(void); /* 复位 */
static uint16_t tpad_get_val(void); /* 得到定时器捕获值 */
static uint16_t tpad_get_maxval(uint8_t n); /* 读取n次, 获取最大值 */
static void tpad_timx_cap_init(uint16_t arr, uint16_t psc); /* 定时器输入捕获初始化 */
/* 接口函数, 可以在其他.c调用 */
uint8_t tpad_init(uint16_t psc); /* TPAD 初始化 函数 */
uint8_t tpad_scan(uint8_t mode); /* TPAD 扫描 函数 */
#endif
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TPAD/tpad.h"
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
tpad_init(6); /* 初始化触摸按键 */
while (1)
{
if (tpad_scan(1)) /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */
{
LED1_TOGGLE(); /* LED1翻转 */
}
t++;
if (t == 15)
{
t = 0;
LED0_TOGGLE(); /* LED0翻转 */
}
delay_ms(10);
}
}
tpad_scan
函数的工作原理以及根据不同的mode
参数如何影响连续按下的行为:
tpad_scan
函数用于扫描电容触摸按键是否被按下。mode
决定了是否支持连续按下的检测。1
表示按键被按下,返回值为0
表示未检测到按键按下。如果mode
为0
,即不支持连续按下:
keyen
不为0),则返回按键被按下的状态,并将keyen
设为3,表示需要再经过3次调用才能再次检测到按键按下。keyen
不为0,则将其递减。如果mode
为1
,即支持连续按下:
keyen
的限制,即每次调用都可以检测到按键的状态。不支持连续按下情况:
支持连续按下情况:
按一下就翻转一次
连按LED会一直翻转
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。