Análise de exemplo de "experimento com botão de toque capacitivo"

í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

Como o pulso é capturado

Conexão de nível esquemático

Fluxo geral de configuração de hardware

Princípios básicos de botões de toque capacitivos (nível de configuração de software)

Descrição da função

Função TPAD_InitConfig ()

Função TPAD_Reset ()

Função TPAD_GetValue ()

Função TPAD_GetMaxValue ()

TPAD_Scan ()

Exemplo de programa

Main.c

Led.c

Led.h.

Tpad.c

Tpad.h

Timer.c

Timer.h

Exibição de resultado


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

 

 

Acho que você gosta

Origin blog.csdn.net/weixin_45590473/article/details/108212775
Recomendado
Clasificación