"Capacitive touch button experiment" example analysis

table of Contents

Capacitive touch button experiment

The basic principle of capacitive touch buttons (schematic level)

The production and function of new capacitors

Changes in charge and discharge performance

How the pulse is captured

Schematic level connection

General flow of hardware configuration

Basic principles of capacitive touch buttons (software configuration level)

Function description

TPAD_InitConfig() function

TPAD_Reset() function

TPAD_GetValue() function

TPAD_GetMaxValue() function

TPAD_Scan()

Program example

Main.c

Led.c

Led.h.

Tpad.c

Tpad.h

Timer.c

Timer.h

Result display


Capacitive touch button experiment

The basic principle of capacitive touch buttons (schematic level)

 

The production and function of new capacitors

Our classmates who have studied analog electronics know that our hand is actually equivalent to a metal plate that can store induced charges. When our hand is close to the screen, our hand and the metal plate Cx under the screen form a parallel capacitive plate. This The capacitance is connected in parallel with the Cs stray capacitance, "parallel capacitance C=C1+C2".

Changes in charge and discharge performance

We see that "VCC is constant; the larger the capacitance C, the greater the amount of charge stored under a certain voltage (Q=CU). On the contrary, the time it takes to charge to U is also more; the value of the resistance R must represent the The obstructive effect of electric charge is fixed."

Charging to V=Vth, the time TA is significantly less than TB; our real rectangular pulse is shown in the following figure:

 

How the pulse is captured

When the capacitor is charged from 0 to Vth, it is equivalent to a rising edge pulse. In fact, the charging and discharging process is very fast. The RC charging and discharging circuit we generally use is to observe the phenomenon and therefore make the time constant very large. However, in reality, we must quickly identify the time constant to be within a certain range.

Schematic level connection

 

STM_ADC is actually only one of the functions of this pin. We are not using the ADC function of the pin here. The following is the multiplexing of this pin:

 

What we use here is the TIM5_CH2 function of PA1, which is used to quickly capture the effective pulse edge change of TPAD.

General flow of hardware configuration

first step

The voltage of the TPAD pin is set to zero first, and the capacitor Cs is discharged

Second step

TPAD pin is set to floating input (because TPAD and PA1 are connected together, so PA1 is set to floating input mode, in order to identify valid pulse edges)

third step

The initial value of the TIM5 counter is set to 0 and the rising edge counting mode is turned on

the fourth step

Turn on the input capture mode of TIM5_CH2

the fifth step

Wait for the charging to be completed, read the captured value at this time, "pulse edge duration = (capture value-initial value) * unit increment time"

Note: When it is not pressed, the charging time is T1 (default). Press TPAD, the capacitance becomes larger, so the charging time is T2. We can determine whether to press or not by detecting the charge and discharge time. If T2-T1 is greater than a certain value, you can judge

A button is pressed.

 

Basic principles of capacitive touch buttons (software configuration level)

Function name

Return value type

Function function

TPAD_Init()

void

Used to capture the charging time of the capacitor under contactless conditions and configure the initial properties of the TIM5_CH2 pin

TPAD_Get_Val()

int

Get a capacitor charging time

TPAD_Get_MaxVal()

int

Get the maximum value of 4 readings of "capacitor charging time"

TPAD_Scan()

char(true or false)

Used to scan to determine whether TPAD is operating

TPAD_Reset()

void

Reset counter and capture value (reset the previous reading result)

Function description

TPAD_InitConfig() function

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); // 向串口打印未触摸时的电容充电时长  
}  

 

TPAD_Reset() function

It is worth noting here: the change of the input and output mode of the PA1 port and the use of the TIM_ClearFlag() function.

Changes in the input and output modes of the PA1 port:

We will ask "Why does the PA1 port change the input and output mode?" As mentioned in our above process, we must first set the TPAD (PA1) port to 0 to fully re-count the rising edge caused by charging the capacitor. Discharge. How do we discharge it?

 

In the figure, PA1 and TPAD are connected by jumper caps, and then in order to read the rising edge generated in TPAD, we must set PA1 to the function of TIM5_CH2, at this time PA1 is in floating input mode, but we have finished reading After a valid pulse edge, if you continue to read, you must first discharge it. At this point, we configure PA1 to be in push-pull output mode, and then change to floating input mode after discharge.

The meaning of TIM_ClearFlag() library function:

Our students who use the library function may have some questions: What is the difference between the TIM_ClearFlag() function and the TIM_ClearITPendingBit() function? Here I will explain.

The TIM_ClearFlag() function is used to "clear the flag bit", for example: when the rising edge pulse (valid pulse edge) is detected, the corresponding TIM_IT_CC2 flag will appear. In fact, these flag bits and the interrupt flag bit in the TIM_ClearITPendingBit () function It's exactly the same, but because the interrupt is not enabled, once the event occurs, only the flag bit will be set to 1, and the interrupt will not be triggered. In fact, in the following program, we just want to clear the flag bit, so TIM_ClearITPendingBit (TIM5, TIM_IT_CC2) can also be used. The function of these two functions is to remove the corresponding flag bit. The same function can be converted to use.

TIM_ClearFlag() and TIM_ClearITPendingBit() function body comparison

TIM_ClearFlag() function body

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寄存器
}  

 

Function body of 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寄存器
}  

 

From the comparison of the function bodies of TIM_ClearFlag() and TIM_ClearITPendingBit(), we find that they are exactly the same and can be used interchangeably.

//复位一次  
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);   
} 

 

TPAD_GetValue() function

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);     
}  

 

Here are a few lines of code that deserve special attention:

if(TIM_GetCounter(TIM5)>MAX_ARR-500)    
{    
    return TIM_GetCounter(TIM5);//超时了,直接返回CNT的值    
} 

   

Here is a concept of "checking for errors". We can check whether the capacitor charging time is too long while checking whether there is a rising edge pulse trigger. If the charging time is too long, return the value directly to end the polling (while(1) ), at this time we consider the effective pulse edge duration to be the value of the counter at this time.

TPAD_GetMaxValue() function

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;  
    }  
}  

 

The essence code that does not support continuous pressing:

    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;  
    }  
// 希望大家理解代码执行的逻辑,我在这里就不仔细讲解了。

 

One thing we have to be clear is: all the functions we wrote before are prepared for writing TPAD_Scan(), we only use the TPAD_Scan() function in the main function. Our good habit when writing functions is: pay attention to which are auxiliary functions and which are the functions to be used in the main function. The function of the function must not be too concentrated or too scattered.

Program example

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  

 

Result display

 

 

Guess you like

Origin blog.csdn.net/weixin_45590473/article/details/108212775