índice
Experiência de botão de toque capacitivo
O princípio básico dos botões de toque capacitivos (nível esquemático)
A produção e função de novos capacitores
Mudanças no desempenho de carga e descarga
Fluxo geral de configuração de hardware
Princípios básicos de botões de toque capacitivos (nível de configuração de software)
Experiência de botão de toque capacitivo
O princípio básico dos botões de toque capacitivos (nível esquemático)
A produção e função de novos capacitores
Nossos colegas que estudaram eletrônica analógica sabem que nossa mão é na verdade equivalente a uma placa de metal que pode armazenar cargas induzidas. Quando nossa mão está perto da tela, nossa mão e a placa de metal Cx sob a tela formam uma placa capacitiva paralela. A capacitância é conectada em paralelo com a capacitância parasita Cs, "capacitância paralela C = C1 + C2".
Mudanças no desempenho de carga e descarga
Vemos que "VCC é constante; quanto maior a capacitância C, maior a quantidade de carga armazenada sob uma determinada tensão (Q = CU). Pelo contrário, o tempo que leva para carregar em U também é maior; o valor da resistência R deve representar o O efeito obstrutivo da carga elétrica é fixo. "
Carregando para V = Vth, o tempo TA é significativamente menor do que TB; nosso pulso retangular real é mostrado na figura a seguir:
Como o pulso é capturado
Quando o capacitor é carregado de 0 a Vth, é equivalente a um pulso de borda ascendente. Na verdade, o processo de carga e descarga é muito rápido. O circuito RC de carga e descarga que geralmente usamos é para observar o fenômeno e, portanto, tornar a constante de tempo muito grande. No entanto, na realidade, devemos identificar rapidamente a constante de tempo para estar dentro de um determinado intervalo.
Conexão de nível esquemático
STM_ADC é na verdade apenas uma das funções deste pino. Não estamos usando a função ADC do pino aqui. O seguinte é a multiplexação deste pino:
O que usamos aqui é a função TIM5_CH2 de PA1, que é usada para capturar rapidamente a mudança de borda de pulso efetiva de TPAD.
Fluxo geral de configuração de hardware
Primeiro passo |
A tensão do pino TPAD é ajustada para zero primeiro, e o capacitor Cs é descarregado |
Segundo passo |
O pino TPAD é configurado para entrada flutuante (porque TPAD e PA1 estão conectados juntos, então PA1 é configurado para modo de entrada flutuante, a fim de identificar bordas de pulso válidas) |
terceiro passo |
O valor inicial do contador TIM5 é definido como 0 e o modo de contagem de borda ascendente é ativado |
o quarto passo |
Ligue o modo de captura de entrada de TIM5_CH2 |
o quinto passo |
Aguarde até que o carregamento seja concluído, leia o valor capturado neste momento, "duração da borda do pulso = (valor de captura-valor inicial) * tempo de incremento da unidade" |
Nota: Quando não é pressionado, o tempo de carregamento é T1 (padrão). Pressione TPAD, a capacitância aumenta, então o tempo de carga é T2. Podemos determinar se devemos pressionar ou não detectando a carga e o tempo de descarga. Se T2-T1 for maior que um certo valor, você pode julgar
Um botão é pressionado.
Princípios básicos de botões de toque capacitivos (nível de configuração de software)
Nome da função |
Tipo de valor de retorno |
Função função |
TPAD_Init () |
vazio |
Usado para capturar o tempo de carga do capacitor sob condições sem contato e configurar as propriedades iniciais do pino TIM5_CH2 |
TPAD_Get_Val () |
int |
Obtenha um tempo de carga do capacitor |
TPAD_Get_MaxVal () |
int |
Obtenha o valor máximo de 4 leituras de "tempo de carga do capacitor" |
TPAD_Scan () |
char (verdadeiro ou falso) |
Usado para fazer a varredura para determinar se o TPAD está operando |
TPAD_Reset () |
vazio |
Redefinir o contador e capturar o valor (redefinir o resultado da leitura anterior) |
Descrição da função
Função TPAD_InitConfig ()
void TPAD_InitConfig() // 读取默认电容充电时间
{
u16 RecordChargeTime[10] = {0};
u8 i = 0, temp = 0;
uart_init(115200); // 初始化USART1
TIM_CAP_InitConfig(0xFFFF, 72-1); // 初始化TIM5_CH2的输入配置
for(; i<10; i++)
{
RecordChargeTime[i] = TPAD_GetValue(); // 连续计算10次默认充电时间
}
for(i=0; i<9; i++)
{
if(RecordChargeTime[i] < RecordChargeTime[i+1]) // 冒泡排序(大->小)
{
temp = RecordChargeTime[i];
RecordChargeTime[i] = RecordChargeTime[i+1];
RecordChargeTime[i+1] = temp;
}
}
for(i=1, temp=0; i<9; i++)
{
temp += RecordChargeTime[i]; // 去掉MAX与MIN求平均充电时间
}
TPAD_DefaultValue = temp/8; // 求出TPAD无动作时的默认充电时间
printf("Default:%dus\r\n",TPAD_DefaultValue); // 向串口打印未触摸时的电容充电时长
}
Função TPAD_Reset ()
Vale a pena notar aqui: a mudança do modo de entrada e saída da porta PA1 e o uso da função TIM_ClearFlag () .
Mudanças nos modos de entrada e saída da porta PA1:
Perguntaremos “Por que a porta PA1 muda o modo de entrada e saída?” Conforme mencionado em nosso processo acima, devemos primeiro definir a porta TPAD (PA1) para 0 para recontar totalmente a borda de subida causada pelo carregamento do capacitor. Descarga. Como o descarregamos?
Na figura, PA1 e TPAD estão conectados por jumper caps, e então para ler a borda ascendente gerada em TPAD, devemos definir PA1 para a função de TIM5_CH2, neste momento PA1 está no modo de entrada flutuante, mas terminamos a leitura Depois de uma borda de pulso válida, se você continuar a ler, deve primeiro descarregá-la. Neste ponto, configuramos PA1 para estar no modo de saída push-pull e, em seguida, mudamos para o modo de entrada flutuante após a descarga.
O significado da função de biblioteca TIM_ClearFlag ():
Nossos alunos que usam a função de biblioteca podem ter algumas perguntas: Qual é a diferença entre a função TIM_ClearFlag () e a função TIM_ClearITPendingBit ()? Aqui vou explicar.
A função TIM_ClearFlag () é usada para "limpar o bit de sinalizador", por exemplo: quando o pulso de borda ascendente (borda de pulso válida) é detectado, o sinalizador TIM_IT_CC2 correspondente aparecerá. Na verdade, esses bits de sinalizador e o bit de sinalizador de interrupção na função TIM_ClearITPendingBit () É exatamente igual, mas como a interrupção não está habilitada, uma vez que o evento ocorre, apenas o bit de flag será definido como 1, e a interrupção não será disparada. Na verdade, no programa a seguir, queremos apenas limpar o bit de sinalizador, de modo que TIM_ClearITPendingBit (TIM5, TIM_IT_CC2) também pode ser usado. A função dessas duas funções é remover o bit de sinalizador correspondente. A mesma função pode ser convertida para uso.
Comparação do corpo da função TIM_ClearFlag () e TIM_ClearITPendingBit ()
Corpo da função TIM_ClearFlag ()
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
{
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_IT(TIM_IT));
/* Clear the IT pending Bit */
TIMx->SR = (uint16_t)~TIM_IT; // 操作SR寄存器
}
Corpo da função de TIM_ClearITPendingBit ()
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG)
{
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_CLEAR_FLAG(TIM_FLAG));
/* Clear the flags */
TIMx->SR = (uint16_t)~TIM_FLAG; // 操作SR寄存器
}
A partir da comparação dos corpos de função de TIM_ClearFlag () e TIM_ClearITPendingBit (), descobrimos que eles são exatamente os mesmos e podem ser usados alternadamente.
//复位一次
void TPAD_Reset(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
//设置GPIOA.1为推挽使出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA.1输出0,放电
delay_ms(5); // 给予充足的时间进行放电
TIM_SetCounter(TIM5,0); //计数器的初始计数值置零
TIM_ClearFlag(TIM5, TIM_IT_CC2); // 清除TIM5_CH2的上升沿标志位
//重新设置GPIOA.1为浮空输入
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
Função TPAD_GetValue ()
u16 TPAD_GetValue() // 获取电容充电时间
{
TPAD_Reset(); // 复位一次
while(TIM_GetFlagStatus(TIM5, TIM_IT_CC2) == RESET)//不断轮询等待捕获上升沿
{
if(TIM_GetCounter(TIM5)>MAX_ARR-500)
{
return TIM_GetCounter(TIM5);//超时了,直接返回CNT的值
}
};
return TIM_GetCapture2(TIM5);
}
Aqui estão algumas linhas de código que merecem atenção especial:
if(TIM_GetCounter(TIM5)>MAX_ARR-500)
{
return TIM_GetCounter(TIM5);//超时了,直接返回CNT的值
}
Aqui está um conceito de "verificação de erros". Podemos verificar se o tempo de carga do capacitor é muito longo enquanto verificamos se há um disparo de pulso de borda ascendente. Se o tempo de carga for muito longo, retorne o valor diretamente para encerrar a pesquisa (enquanto (1) ), neste momento consideramos a duração efetiva da borda do pulso como sendo o valor do contador neste momento.
Função TPAD_GetMaxValue ()
u16 TPAD_GetMaxValue(u8 SampleNumber) // 捕获4次取MAX
{
u8 temp = TPAD_GetValue();
while(1)
{
temp = temp>TPAD_GetValue()?temp:TPAD_GetValue();
if(--SampleNumber) break;
}
return temp; // 求出TPAD无动作时的默认充电时间
}
TPAD_Scan ()
u8 TPAD_Scan() // 扫描TPAD状态且不支持连续按
{
u16 TPAD_MaxValue = TPAD_GetMaxValue(3); // 连续采集3次
static u8 PressedFlag = 1;
uart_init(115200); // 初始化USART1
printf("Moment:%dus\r\n",TPAD_MaxValue); // 向串口打印当前充电时长
if((TPAD_MaxValue >= TPAD_Threshold + TPAD_DefaultValue)&&PressedFlag)
{
PressedFlag = 0;
return 1;
}
else if(TPAD_MaxValue < TPAD_Threshold + TPAD_DefaultValue)
{
PressedFlag = 1;
return 0;
}
else
{
return 0;
}
}
O código essencial que não suporta prensagem contínua:
if((TPAD_MaxValue >= TPAD_Threshold + TPAD_DefaultValue)&&PressedFlag)
{
PressedFlag = 0;
return 1;
}
else if(TPAD_MaxValue < TPAD_Threshold + TPAD_DefaultValue)
{
PressedFlag = 1;
return 0;
}
else
{
return 0;
}
// 希望大家理解代码执行的逻辑,我在这里就不仔细讲解了。
Uma coisa que devemos deixar claro é: todas as funções que escrevemos antes estão preparadas para escrever TPAD_Scan (), nós apenas usamos a função TPAD_Scan () na função principal. Nosso bom hábito ao escrever funções é: preste atenção em quais são as funções auxiliares e quais são as funções a serem utilizadas na função principal.A função da função não deve ser muito concentrada ou muito dispersa.
Exemplo de programa
Main.c
#include "tpad.h"
#include "led.h"
#include "timer.h"
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
int main()
{
extern u16 TPAD_DefaultValue; // TPAD的默认充电时间
u8 TPAD_Status = 0, temp = 0;
delay_init(); // systick时钟初始化
LED_InitConfig(); // 初始化LED1&LED0
TPAD_InitConfig(); // 计算TPAD_DefaultValue
uart_init(115200); // 初始化USART1
while(1)
{
TPAD_Status = TPAD_Scan(); // 实时捕获TPAD的动作
if(TPAD_Status == 1)
{
LED1 = !LED1; // 触摸按键动作触发LED1状态翻转
}
temp++;
if(temp == 15)
{
LED0 = !LED0; // LED0作为状态灯使用
}
}
}
Led.c
#include "led.h"
#include "stm32f10x.h"
void LED_InitConfig()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); // 使能LED1,LED0的时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); // LED0配置为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure); // LED1配置为推挽输出模式
GPIO_SetBits(GPIOB, GPIO_Pin_5); // LED0初始化为高电平
GPIO_SetBits(GPIOE, GPIO_Pin_5); // LED1初始化为高电平
}
Led.h.
#ifndef _LED_H
#define _LED_H
#include "sys.h"
void LED_InitConfig();
#define LED1 PEout(5)
#define LED0 PBout(5)
#endif
Tpad.c
#include "tpad.h"
#include "stm32f10x.h"
#include "timer.h"
#include "delay.h"
#include "usart.h"
u16 TPAD_DefaultValue = 0;
u16 MAX_ARR = 0xFFFF; // 计数器最计数值为0xFFFF
u16 TPAD_Threshold = 30; // 有无触摸TPAD的电容充电时间差值为30
void TPAD_InitConfig() // 读取默认电容充电时间
{
u16 RecordChargeTime[10] = {0};
u8 i = 0, temp = 0;
uart_init(115200); // 初始化USART1
TIM_CAP_InitConfig(0xFFFF, 72-1); // 初始化TIM5_CH2的输入配置
for(; i<10; i++)
{
RecordChargeTime[i] = TPAD_GetValue(); // 连续计算10次默认充电时间
}
for(i=0; i<10; i++)
{
if(RecordChargeTime[i] < RecordChargeTime[0]) // 冒泡排序(大->小)
{
temp = RecordChargeTime[i];
RecordChargeTime[i] = RecordChargeTime[0];
RecordChargeTime[0] = temp;
}
}
for(i=1, temp=0; i<9; i++)
{
temp += RecordChargeTime[i]; // 去掉MAX与MIN求平均充电时间
}
TPAD_DefaultValue = temp/8; // 求出TPAD无动作时的默认充电时间
printf("Default:%dus\r\n",TPAD_DefaultValue); // 向串口打印未触摸时的电容充电时长
}
//复位一次
void TPAD_Reset(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
//设置GPIOA.1为推挽使出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA.1输出0,放电
delay_ms(5);
TIM_SetCounter(TIM5,0); // 计数器初始计数值置零
TIM_ClearFlag(TIM5, TIM_IT_CC2);
//设置GPIOA.1为浮空输入
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u16 TPAD_GetValue() // 获取电容充电时间
{
TPAD_Reset(); // 复位一次
while(TIM_GetFlagStatus(TIM5, TIM_IT_CC2) == RESET)//等待捕获上升沿
{
if(TIM_GetCounter(TIM5)>MAX_ARR-500)
{
return TIM_GetCounter(TIM5);//超时了,直接返回CNT的值
}
};
return TIM_GetCapture2(TIM5);
}
u16 TPAD_GetMaxValue(u8 SampleNumber) // 捕获4次取MAX
{
u8 temp = TPAD_GetValue();
while(1)
{
temp = temp>TPAD_GetValue()?temp:TPAD_GetValue();
if(--SampleNumber) break;
}
return temp; // 求出TPAD无动作时的默认充电时间
}
u8 TPAD_Scan() // 扫描TPAD状态
{
u16 TPAD_MaxValue = TPAD_GetMaxValue(3); // 连续采集3次
static u8 PressedFlag = 1;
uart_init(115200); // 初始化USART1
printf("Moment:%dus\r\n",TPAD_MaxValue); // 向串口打印当前充电时长
if((TPAD_MaxValue >= TPAD_Threshold + TPAD_DefaultValue)&&PressedFlag)
{
PressedFlag = 0;
return 1;
}
else if(TPAD_MaxValue < TPAD_Threshold + TPAD_DefaultValue)
{
PressedFlag = 1;
return 0;
}
else
{
return 0;
}
}
Tpad.h
#ifndef _TPAD_H
#define _TPAD_H
#include "sys.h"
void TPAD_InitConfig(); // 用于初始化TPAD
u16 TPAD_GetValue(); // 捕获一次电容充电时间
u16 TPAD_GetMaxValue(u8 SampleNumber); // 捕获10次电容电容充电时间的MAX
u8 TPAD_Scan(); // 用于扫描TPAD是否动作
#endif
Timer.c
#include "timer.h"
#include "stm32f10x.h"
#include "sys.h"
void TIM_CAP_InitConfig(u16 ARR, u16 PR)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能TIM5的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA的时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA1为浮空输入
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = ARR;
TIM_TimeBaseInitStructure.TIM_Prescaler = PR;
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStructure); // 配置TIM5计数器的属性
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置TIM5_CH2的输入属性
TIM_Cmd(TIM5, ENABLE); // 使能TIM5
}
// 我们不开启捕获中断,但是我们用“识别脉冲沿标志位”来判断事件是否成功发生
Timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
void TIM_CAP_InitConfig(u16 ARR, u16 PR);
#endif
Exibição de resultado