STM32电容触摸按键及原理(TPAD)

目录

一、电容触摸按键简介

1.RC充电充电原理

2.RC电路充放电公式

3.检测电容触摸按键过程

二、程序思路

三、程序实现


一、电容触摸按键简介

        电容式感应触摸按键可以穿透绝缘材料外壳 8mm (玻璃、塑料等等)以上,准确无误地侦测到手指的有效触摸。它普遍使用在开关,各类电器上面。比如抽油烟机的按键,如下图。

STM32电容触摸按键及原理(TPAD)_第1张图片

1.RC充电充电原理

        首先电容和电阻串联在V1上,当按键按下的瞬间,经过R电阻,给CX电容充电,等待时间足够让CX电容充满电后,充电后的CX的电压Vt等于V1的电压,之后就没有电流经过R。下面右图就是充电的时间,当CX一直充电,Vt和V1的电压差不断缩小,所经过的电流也越来越小。

STM32电容触摸按键及原理(TPAD)_第2张图片

2.RC电路充放电公式

Vt = V0 + (V1 - V0) * (1-e^{t/RC})

V0:电容上的初始电压(一般为0);

V1:电容最终可充到或放到的电压值;

Vt:t时刻电容上的电压值

        结论:同样的条件下,电容值C跟时间值t成正比关系,电容越大,充电满的时间越长。

3.检测电容触摸按键过程

①:TPAD引脚设置为推挽输出,输出0,实现电容放电0;

②:TPAD引脚设置为浮空输入(IO复位后的状态),电容开始充电;

③:同时开启TPAD引脚的输入捕获开始捕获;

④:等待充电完成(充电到满Vx,检测上升沿);

⑤:计算充电时间。

        没有按下的时候,充电时间为T1。按下TPAD,电容变大,所以充电时间为T2。我们可以通过检测充放电时间,来判断是否按下。如果T2-T1大于某个值,就可以判断有按键按下。

二、程序思路

        1.TPAD_Init:TIM2_CH1_Cap_Init()初始化输入捕获,10次调用TPAD_Get_Val(),取中间6次的平均值赋值给TPAD_default_val变量;

        2.TPAD_Reset:初始化PA5为推挽输出,输出0,放电,并且初始化PA5为浮空输入,等待按下充电。

        3.TPAD_Get_Val:刚开始就TPAD_Reset(),等待上升沿,之后捕获上升沿,返回捕获寄存器的值。

        4.TPAD_Scan:调用TPAD_Get_MaxVal,获得N次的平均值

三、程序实现

        1.tpad.h

#ifndef __TPAD_H
#define __TPAD_H

#include "sys.h"

extern vu16 TPAD_Default_Val;

void TPAD_Reset(void);
void TIM2_CH1_Cap_Init(uint32_t arr, uint16_t psc);
uint16_t TPAD_Get_Val(void);
uint16_t TPAD_Get_Max_Val(uint8_t n);
uint16_t TPAD_Init(uint8_t psc);
uint8_t TPAD_Scan(uint8_t mode);

#endif

        2.tpad.c

#include "tpad.h"
#include "delay.h"

#define TPAD_ARR_MAX_VAL 0XFFFFFFFF           // 最大的ARR的值,并且STM32429的TIM2是32位定时器
vu16 TPAD_Default_Val = 0;           // 空载的时候(没有手按下),计数器需要的时间

TIM_HandleTypeDef TIM2_IC_Struct;             // TIM句柄
TIM_IC_InitTypeDef TIM2_IC_CHannel1_Define;   // TIM_IC句柄
GPIO_InitTypeDef GPIO_Struct;


// 1.初始化触摸按键
// 2.获得空载的时候触摸按键取值
// return:1,初始化成功;0,初始化失败
uint16_t TPAD_Init(uint8_t psc)
{

    uint8_t i,j;
    uint16_t arr[10];
    uint16_t temp = 0;

    TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL, psc - 1);       // 公式里需要减1
    
    for ( i = 0; i < 10; i++)                       // 取10次TPAD_Get_Val的值
    {

        arr[i] = TPAD_Get_Val();
        delay_ms(10);

    }
    
    for ( i = 0; i < 9; i++)                        // 把取出来的10次捕获值按升序排序
    {
        for ( j = i + 1; j < 10; j++)
        {
            
            if (arr[i] > arr[j])
            {
                
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;

            }
        
        }
        
    }

    for ( i = 2; i < 8; i++)                        // 取中间的6个数                                
    {
        
        temp += arr[i];

    }
    
    TPAD_Default_Val = temp / 6;                    // 取平均值
    
    if (TPAD_Default_Val > TPAD_ARR_MAX_VAL / 2)    // 如果默认值大于最大值/2 就说明Default的值不正常
    {
        
        return 0;                                   

    }
    
    return 1;

}

// 先复位一次,然后让电容放电,并清除定时器的计数值
void TPAD_Reset()
{

    GPIO_Struct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出
    GPIO_Struct.Pin = GPIO_PIN_5;               // PA5
    GPIO_Struct.Pull = GPIO_PULLDOWN;           // 下拉
    GPIO_Struct.Speed = GPIO_SPEED_FAST;        // 高速

    HAL_GPIO_Init(GPIOA, &GPIO_Struct);

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);             // PA5=0,给PA5放电
    delay_ms(20);

    __HAL_TIM_CLEAR_FLAG(&TIM2_IC_Struct, TIM_FLAG_CC1 | TIM_FLAG_UPDATE);      // 清除标志位
    __HAL_TIM_SET_COUNTER(&TIM2_IC_Struct, 0);                                  // 清除计数器的值

    GPIO_Struct.Mode = GPIO_MODE_AF_PP;     // 推挽复用输出
    GPIO_Struct.Pull = GPIO_NOPULL;         // 不带上下拉
    GPIO_Struct.Alternate = GPIO_AF1_TIM2;  // 复用为TIM2

    HAL_GPIO_Init(GPIOA, &GPIO_Struct);

}

// 1.得到定时器捕获值
// 2.如果超时,直接返回定时器的计数值
// 返回值:捕获值或计数值(超时的时候返回)
uint16_t TPAD_Get_Val(void)
{

    TPAD_Reset();

    while (__HAL_TIM_GET_FLAG(&TIM2_IC_Struct, TIM_FLAG_CC1) == RESET)                 // 等待捕获上升沿,把TIM2计数器的值赋值给TIM_FLAG_CC1标签
    {
        
        if (__HAL_TIM_GET_COUNTER(&TIM2_IC_Struct) > TPAD_ARR_MAX_VAL - 500)           // 判断TIM2的值是否超过32位计数器的最大值,-500是留缓存一下
        {
            
            return __HAL_TIM_GET_COUNTER(&TIM2_IC_Struct);                             // 超时直接返回CNT的值

        } 

    }

    return HAL_TIM_ReadCapturedValue(&TIM2_IC_Struct, TIM_CHANNEL_1);              // 不超时就返回捕捉TIM2_CH1的值

    
}

// 1.读取n次,取最大的值(n:连续捕获的次数)
// 返回值:n次里面读取的最大的数值
uint16_t TPAD_Get_Max_Val(uint8_t n)
{

    uint16_t rest = 0;                        // 最大捕获值
    uint16_t temp;                            // 记录当前捕获值或计数值(超时时返回)
    uint8_t ok = 0;                           // 大于默认值的5/4才算有效的次数
    uint8_t Cnt_Number = n * 2/3;             // 至少2/3*n的有效个触摸,才算有效      

    while (n--)
    {
        
        temp = TPAD_Get_Val();

        if (temp > TPAD_Default_Val*5/4)          // 至少大于默认值的5/4才算有效
        {
            
            ok++;

        }

        if (temp > rest)                          // 判断当前值捕获值是否大于最大值
        {

            rest = temp;

        }
        
    }

    if (ok >= Cnt_Number)                     // 至少2/3*n的有效个触摸,默认值的5/4才能返回最大值
    {
         
        return rest;

    }
    else
    {
            
        return 0;                             // 没有找到最大捕获值,返回0

    }
        
}

// 扫描按键
// mode(0):不支持连续触发(按下一次后必须松开才能按下一次)
// mode(1):支持连续触发
// return 0 or 1
uint8_t TPAD_Scan(uint8_t mode)
{

    static uint8_t key = 0;     // 0,可以检测;>0,不可以检测
    uint8_t sample = 3;         // 默认采样次数是3
    uint16_t TPAD_MaxValue = 0; // 获得TPAD的最大采样值
    uint8_t rest = 0;           // 返回值 0:没有按下,1:按下了

    if (mode)
    {

        sample = 6;             // 支持连续按键的时候,设置采样次数位6
        key = 0;                // 连按可以检测

    }
    
    TPAD_MaxValue = TPAD_Get_Max_Val(sample);       // 在sample次中取得最大采样值

    if (TPAD_MaxValue > (TPAD_Default_Val * 4/3) && TPAD_MaxValue < (TPAD_Default_Val * 10))    // 判断TPAD最大采样值是否超过TPAD_Default_Val的三分之四,并且小于TPAD_Default_Val的10倍才有效
    {

        if (key == 0)       // 可以进行检测
        {

            rest = 1;       // 有按下

        }

        printf("最大采样值位:%d\r\n", TPAD_MaxValue);

        key = 3;            // 至少按下三次,按键才有效
        
    }

    if (key)                // 判断key是否大于0的数,是则自减1
    {

        key--;

    }
    
    return  rest;

}

void TIM2_CH1_Cap_Init(uint32_t arr, uint16_t psc)
{

    TIM2_IC_Struct.Instance = TIM2;                                  // 通用定时器2
    TIM2_IC_Struct.Init.CounterMode = TIM_COUNTERMODE_UP;            // 计数模式为向上计数
    TIM2_IC_Struct.Init.Period = arr;                                // 自动重装载值(ARR)
    TIM2_IC_Struct.Init.Prescaler = psc;                             // 分频系数

    HAL_TIM_IC_Init(&TIM2_IC_Struct);

    TIM2_IC_CHannel1_Define.ICFilter = 0;                             // 捕获滤波器为0
    TIM2_IC_CHannel1_Define.ICPolarity = TIM_ICPOLARITY_RISING;       // 捕获极性为上升沿捕获
    TIM2_IC_CHannel1_Define.ICPrescaler = TIM_ICPSC_DIV1;             // 无分频系数
    TIM2_IC_CHannel1_Define.ICSelection = TIM_ICSELECTION_DIRECTTI;   // 映射TI1上

    HAL_TIM_IC_ConfigChannel(&TIM2_IC_Struct, &TIM2_IC_CHannel1_Define, TIM_CHANNEL_1);

    HAL_TIM_IC_Start(&TIM2_IC_Struct, TIM_CHANNEL_1);                // 开启TIM_IC
}

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{

    if (htim->Instance == TIM2)
    {
        
        __HAL_RCC_GPIOA_CLK_ENABLE();       // GPIOA使能
        __HAL_RCC_TIM2_CLK_ENABLE();        // TIM2使能

        GPIO_Struct.Alternate = GPIO_AF1_TIM2;
        GPIO_Struct.Mode = GPIO_MODE_AF_PP;         // 推挽复用输出
        GPIO_Struct.Pin = GPIO_PIN_5;
        GPIO_Struct.Pull = GPIO_NOPULL;             // 不带上下拉
        GPIO_Struct.Speed = GPIO_SPEED_FAST;        // 高速

        HAL_GPIO_Init(GPIOA, &GPIO_Struct);

    }
    
}

        3.main.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "tpad.h"
#include "led.h"

int main(void)
{

	uint8_t t = 0;
	HAL_Init();
	Stm32_Clock_Init(360, 25, 2, 8);
	delay_init(180);
	uart_init(115200);
	LED_Init();
	TPAD_Init(2);

	while (1)
	{

		if (TPAD_Scan(0))		// 判断是否捕获到一次上升沿
		{
			
			LED1 = !LED1;

		}
		
		t++;

		if (t == 15)			// Scan扫描了15次就反转一次LED0
		{

			LED0 = !LED0;

		}
		delay_ms(10);
	}

}

你可能感兴趣的:(stm32,单片机,嵌入式硬件)