STM32电容触摸按键检测

STM32电容触摸按键检测

  • 电容触摸按键
    • 简介
    • 检测原理
  • CubeMX配置
  • 代码展示&讲解
    • TPAD.c
    • TPAD.h

本期内容我们将学习电容触摸按键的检测原理。以及代码实现思路

电容触摸按键

简介

在这里插入图片描述

  • 电容触摸按键依赖的是电容的充放电
  • 相对于机械按键更加耐用,不容易受外界环境干扰
  • 在我们的开发板(正点原子的STM32F411RCT6 NANO板)上长这样:
    在这里插入图片描述

检测原理

前面将电容触摸按键依赖的是电容的充放电,然而我们看开发板上的元件摆放图,发现没有电容在TPAD位置:

在这里插入图片描述

这个要从PCB的层叠结构说起;

  • 我们的NANo板使用的是双层PCB结构,大概结构就是有两个铜箔中间夹着一个基材,
  • 铜箔构成PCB的顶层和底层,负责PCB的走线,完成信号的传递,
  • 铜箔外侧有丝印,阻焊等等,同时也负责元器件的摆放。
  • 铜箔内部有基材,通常以RF-4为多,这个部分占PCB的大部分厚度,为此PCB的基本外观的硬度
  • 具体结构大体如下(这个手画的有点抽象):
    在这里插入图片描述

有了这个概念,我们就可以进行理论推导了:

首先,电容的充电公式如下:Vc=E (1-e (-t/R*C))
其中:

  • Vc为电容两端的电压,其随时间变化
  • E为电容充电的最大值,即充满电的的电压
  • e为自然对数的底数
  • t为时间
  • R为电容充电回路的电阻
  • C为电容的容值

通过以上公式,我们可以得到曲线
在这里插入图片描述

再看原理图:

在这里插入图片描述
在这里插入图片描述

  • 通过通过电阻R32和前面板材PCB铜箔之间的电容1(这里叫Cs),构成充放电回路,STM_ADC充当开关,控制PB1的充放电
  • 当手指触摸板材时,铜箔与手指之间的电容2(这里叫Cx)加入充电回路,电容增加,充电速度变慢,换句话说,电容达到相同电压的时间变成,利用这一区别,我们就能实现电容触摸按键的检测
    在这里插入图片描述
  • 图中Cth表示单片机GPIO识别为高电平的最小值。当Vc小于Vth时,识别为低电平,反之识别为高电平。越过Vth的过程可以被单片机识别为上升沿。可通过定时器的输入捕获功能捕捉
    具体配置过程如下:
  1. TPAD引脚设置为推挽输出,输出低电平,使电容放电
  2. TPAD引脚设置为浮空输入,电容开始充电
  3. 开启TPAD输入捕获功能,捕获上升沿(即电压达到Vth的时刻)
  4. 等待充电过程,直至捕获成功
  5. 计算充电时间。

CubeMX配置

由于电容触摸按键检测过程中TPAD引脚模式会频繁切换,所以我们再CubeMX中不进行配置,直接从代码中自行编写配置过程。

代码展示&讲解

代码源码来自正点原子,经稍加修改便于移植和理解

TPAD.c

#include "tpad.h"

//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK NANO STM32F4开发板
//TPAD驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/4/23
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2019-2029
//All rights reserved									  
// 	
TIM_HandleTypeDef TIM3_Handler;         //定时器3句柄 

#define TPAD_ARR_MAX_VAL  0XFFFF		//最大的ARR值(TIM3是16位定时器)	  
vu16 tpad_default_val=0;				没有手指按下获取的充电时间(这里指Vc从0到Vth的充电时间)

//触摸按键初始化函数,获得没有手指按下时的充电时间平均值
//psc:Timer3时钟(100MHz)分频系数,越小,计数器加1时间间隔越短,灵敏度越高.
//返回值:0,初始化成功;1,初始化失败
uint8_t TPAD_Init(uint8_t psc)
{
	uint16_t buf[10];      //存放十次没有手指按下时的充电时间
	uint16_t temp;         //临时变量
	uint8_t j,i;           //计数变量
	TIM3_CH4_Cap_Init(TPAD_ARR_MAX_VAL,psc-1);//设置时钟分频系数
	for(i=0;i<10;i++)//连续读取10次没有触摸时的充电时间
	{				 
		buf[i]=TPAD_Get_Val();    //将读取的充电时间存入数组
//		HAL_Delay(10);	          //延时,实测好像没有什么用,不过源码中有
	}
    
    //对读取的时间进行排序,去除两个最大值和两个最小值,算平均数    
	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;
			}
		}
	}
	temp=0;
	for(i=2;i<8;i++)temp+=buf[i];//取中间的6个数据进行平均
	tpad_default_val=temp/6;
	printf("tpad_default_val:%d\r\n",tpad_default_val);	  //获取到十次手指未按下时读取时间的平均值
	if(tpad_default_val>(vu16)TPAD_ARR_MAX_VAL/2)return 1;//初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常!
	return 0;		     	    					   
}
//复位一次
//释放电容电量,并清除定时器的计数值
void TPAD_Reset(void)
{
    GPIO_InitTypeDef GPIO_Initure;
	
    GPIO_Initure.Pin=GPIO_PIN_1;            //PB1
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);	//PB1输出0,放电
    HAL_Delay(5);      //等待放电完成
    __HAL_TIM_CLEAR_FLAG(&TIM3_Handler,TIM_FLAG_CC4|TIM_FLAG_UPDATE);   //清除标志位
    __HAL_TIM_SET_COUNTER(&TIM3_Handler,0); //计数器值归0
    
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;  	//推挽复用
    GPIO_Initure.Pull=GPIO_NOPULL;          //不带上下拉
	GPIO_Initure.Alternate=GPIO_AF2_TIM3;   //PB1复用为TIM3通道4
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);         
}

//得到定时器捕获值
//如果超时,则直接返回定时器的计数值.
//返回值:捕获值/计数值(超时的情况下返回)
uint16_t TPAD_Get_Val(void)   
{
    TPAD_Reset();     //TPAD引脚放电,设置为定时器输入捕获上升沿触发模式
    while(__HAL_TIM_GET_FLAG(&TIM3_Handler,TIM_FLAG_CC4)==RESET) //等待TPAD引脚捕获到上升沿
    {
        if(__HAL_TIM_GET_COUNTER(&TIM3_Handler)>TPAD_ARR_MAX_VAL-500)   //一直未检测到上升沿,且时间过大时返回cnt
        {                                                               //此时过大的cnt返回,会被当成最大值去除掉
          return __HAL_TIM_GET_COUNTER(&TIM3_Handler);//超时了,直接返回CNT的值  
        }
          
    };
    return HAL_TIM_ReadCapturedValue(&TIM3_Handler,TIM_CHANNEL_4);      //捕获到上升沿,返回正常充电时间
}


//读取n次,取最大值
//n:连续获取的次数
//返回值:n次读数里面读到的最大读数值
uint16_t TPAD_Get_MaxVal(uint8_t n)
{ 
	uint16_t temp=0; 
	uint16_t res=0;      //最大值结果
	uint8_t lcntnum=n*2/3;//至少2/3*n的有效个触摸,才算有效
	uint8_t okcnt=0;
	while(n--)
	{
		temp=TPAD_Get_Val();//得到一次值
		if(temp>(tpad_default_val*5/4))okcnt++;//至少大于默认值的5/4才算有效
		if(temp>res)res=temp;
	}
	if(okcnt>=lcntnum)return res;//至少2/3的概率,要大于默认值的5/4才算有效
	else return 0;
}  

//扫描触摸按键
//mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下)
//返回值:0,没有按下;1,有按下;	
#define TPAD_GATE_VAL 	30	//触摸的门限值,也就是必须大于tpad_default_val+TPAD_GATE_VAL,才认为是有效触摸.
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>(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;
}	

//定时器3通道4输入捕获配置
//arr:自动重装值(TIM2是16位的!!)
//psc:时钟预分频数
void TIM3_CH4_Cap_Init(uint32_t arr,uint16_t psc)
{  
    TIM_IC_InitTypeDef TIM3_CH4Config;  
    
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//分频因子
    HAL_TIM_IC_Init(&TIM3_Handler);
    
    TIM3_CH4Config.ICPolarity=TIM_ICPOLARITY_RISING;    //上升沿捕获
    TIM3_CH4Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到TI1上
    TIM3_CH4Config.ICPrescaler=TIM_ICPSC_DIV1;          //配置输入分频,不分频
    TIM3_CH4Config.ICFilter=0;                          //配置输入滤波器,不滤波
    HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH4Config,TIM_CHANNEL_4);//配置TIM3通道4
    HAL_TIM_IC_Start(&TIM3_Handler,TIM_CHANNEL_4);      //开始捕获TIM3的通道4
}

//定时器3底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_IC_Init()调用
//htim:定时器3句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟
	
    GPIO_Initure.Pin=GPIO_PIN_1;            //PB1
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;  	 //推挽复用
    GPIO_Initure.Pull=GPIO_NOPULL;          //不带上下拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
	GPIO_Initure.Alternate=GPIO_AF2_TIM3;   //PB1复用为TIM3通道4
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
}

TPAD.h

#ifndef __TPAD_H
#define __TPAD_H

#include "main.h"
#include "usart.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK NANO STM32F4开发板
//TPAD驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/4/23
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2019-2029
//All rights reserved									  
// 

//空载的时候(没有手按下),计数器需要的时间
//这个值应该在每次开机的时候被初始化一次
typedef __IO uint16_t vu16;
extern vu16 tpad_default_val;
							   	    
void TPAD_Reset(void);
uint16_t  TPAD_Get_Val(void);
uint16_t TPAD_Get_MaxVal(uint8_t n);
uint8_t   TPAD_Init(uint8_t systick);
uint8_t   TPAD_Scan(uint8_t mode);
void TIM3_CH4_Cap_Init(uint32_t arr,uint16_t psc);
#endif


不得不说,正点原子写的代码还是太妙了,所以我们在这里斗胆来窥探一下:

  1. void TPAD_Reset(void);
    在这里插入图片描述

这里负责的是TPAD引脚的复位,完成TPAD引脚的放电,并设置为输入捕获功能,这是进行充电时间读取前的必备操作

  1. uint16_t TPAD_Get_Val(void);
    在这里插入图片描述

这里读取的是电容充电时间,不论是否超时都会被读取

  1. uint16_t TPAD_Get_MaxVal(uint8_t n);
    在这里插入图片描述

这里是读取有手指按下的最大充电时间,当没有手指按下时,返回0
这里的5/4只是个判断标准,确保是有手指按下的情况
2/3则是用来判误触,静电等情况

  1. uint8_t TPAD_Init(uint8_t systick);
//触摸按键初始化函数,获得没有手指按下时的充电时间平均值
//psc:Timer3时钟(100MHz)分频系数,越小,计数器加1时间间隔越短,灵敏度越高.
//返回值:0,初始化成功;1,初始化失败
uint8_t TPAD_Init(uint8_t psc)
{
	uint16_t buf[10];      //存放十次没有手指按下时的充电时间
	uint16_t temp;         //临时变量
	uint8_t j,i;           //计数变量
	TIM3_CH4_Cap_Init(TPAD_ARR_MAX_VAL,psc-1);//设置时钟分频系数
	for(i=0;i<10;i++)//连续读取10次没有触摸时的充电时间
	{				 
		buf[i]=TPAD_Get_Val();    //将读取的充电时间存入数组
//		HAL_Delay(10);	          //延时,实测好像没有什么用,不过源码中有
	}
    
    //对读取的时间进行排序,去除两个最大值和两个最小值,算平均数    
	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;
			}
		}
	}
	temp=0;
	for(i=2;i<8;i++)temp+=buf[i];//取中间的6个数据进行平均
	tpad_default_val=temp/6;
	printf("tpad_default_val:%d\r\n",tpad_default_val);	  //获取到十次手指未按下时读取时间的平均值
	if(tpad_default_val>(vu16)TPAD_ARR_MAX_VAL/2)return 1;//初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常!
	return 0;		     	    					   
}

这里则是完成TPAD模块的初始化。读取10次手指未按下时的充电时间
排序去除最大值和最小值是为了减小未捕获上升沿,或过快捕获到上升沿导致的误差

  1. uint8_t TPAD_Scan(uint8_t mode);

在这里插入图片描述

  • 扫描函数可以分为支持连续触发和单次触发
  • 单次触发:第一次触发时会将res置1,后返回表示按键按下,后续计入函数,如果手指一直不拿开,会导致res无法置1(keyen一直等于3),返回0表示按键为按下
  • 连续触发:无论那一次触发进入函数,keyen都会被置1,后返回1表示按键按下。

以上就是本期的全部内容,emmm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/181402.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode (力扣) 97. 交错字符串(动态规划)

文章目录 题目描述思路分析完整代码 题目描述 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 … sn t …

低代码开发与IT开发的区别

目录 一、含义不同 二、开发门槛不同 三、两者之间的区别 1、从技术特征来看 2、从目标开发者来看 四、低代码平台使用感受&#xff1f; &#xff08;1&#xff09;自定义模块&#xff0c;满足不同的业务需求 &#xff08;2&#xff09;工作流引擎&#xff0c;简化复杂流程的管…

MeterSphere | 接口测试请求体中,int类型的入参实现动态化变量

项目场景&#xff1a; 在接口自动化的时候&#xff0c;要把上一个接口的 Int 变量传入到 下一个接口中进行使用&#xff0c;但编译器会出现 红色的 X 符号 问题描述 如何实现 int 类型的入参实现动态化变量&#xff1f; 解决方案&#xff1a; 忽视掉这个红色 X 号&#xff0…

Go语言网络爬虫工程经验分享:pholcus库演示抓取头条新闻的实例

网络爬虫是一种自动从互联网上获取数据的程序&#xff0c;它可以用于各种目的&#xff0c;如数据分析、信息检索、竞争情报等。网络爬虫的实现方式有很多&#xff0c;不同的编程语言和框架都有各自的优势和特点。在本文中&#xff0c;我将介绍一种使用Go语言和pholcus库的网络爬…

selenium 简单案例 <批量下载文件> <网页自动化点击上报>

一、批量下载文件 网页分析 点击跳转到下载页面 from selenium import webdriver import timedef get_link_list():# 创建浏览器对象driver webdriver.Chrome(executable_pathrC:\Users\nlp_1\Desktop\chromedriver\chromedriver-win32\chromedriver.exe)url https://www…

登陆页面模板

简单好看的登陆页面 vue项目代码 可忽略js部分 先来个效果图 <template><div class"login"><div class"content"><p >账户密码登录</p><div class"unit"><label class"label">用户名</…

非遗数字保护的崭新篇章:十八数藏柏松的文化守护

在数字时代&#xff0c;非遗数字保护崭新的篇章由十八数藏柏松书写。这个数字保护的使者不仅仅是文化的守护者&#xff0c;更是文化传承的崭新篇章的开创者。 首先&#xff0c;十八数藏柏松以数字技术作为媒介&#xff0c;将传统非物质文化遗产数字化&#xff0c;为其创造了一个…

Java字节码指令集概述及分类详解

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 1、字节码指令集与解析概述 Java字节码对于虚拟机&#xff0c;就好像汇编语言对于计算机&#xff0c;属于基本执行指令。 Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字&#xff08;称为操作码&a…

Linux之实现简易的shell

1.打印提示符并获取命令行 我们在使用shell的时候&#xff0c;发现我们在输入命令是&#xff0c;前面会有&#xff1a;有用户名&#xff0c;版本&#xff0c;当前路径等信息&#xff0c;这里我们可以用环境变量去获取: 1 #include <stdio.h>2 #include <stdlib.h>…

检验LIS系统:医院信息管理的重要组成部分

检验LIS系统源码&#xff0c;云LIS系统源码 云LIS系统是医院信息管理的重要组成部分之一&#xff0c;集申请、采样、核收、计费、检验、审核、发布、质控、查询、耗材控制等检验科工作为一体的网络管理系统。LIS系统不仅是自动接收检验数据&#xff0c;打印检验报告&#xff0c…

因果发现31种高效经典方案汇总,附配套算法和代码

因果发现&#xff08;Causal Discovery&#xff09;是一个复杂的过程&#xff0c;其目标是从大量的数据中确定变量之间的因果关系。这个过程通常涉及到的是如何从纷繁复杂的数据中发现其中隐含的因果关系。有时&#xff0c;研究者可以通过随机实验进行干预来发现因果关系&#…

Windows Python3安装salt模块失败处理

复现CVE-2020-11651时候运行CVE-2020-11651的poc时候需要salt模块 在下载时出现了错误 尝试在网上寻找解决方法&#xff1a; 1.更新 setuptools 和 wheel pip install --upgrade setuptools wheel 2. 安装Microsoft Visual C 14.0 因为salt模块包包使用了 C/C 扩展&#x…

【速看】如何提高微信权重?影响微信权重的加分、扣分行为

微信具有一套权重判定系统&#xff0c;类似于搜索引擎的PR值&#xff0c;可以看做是一个“积分系统”。好的操作会增加积分&#xff0c;负面操作会减少积分。 当积分低于特定标准&#xff08;即底线&#xff09;时&#xff0c;将会被严重惩罚或封号。这样&#xff0c;微信确保了…

C# Onnx PP-Vehicle 车辆分析(包含:车辆检测,识别车型和车辆颜色)

目录 效果 模型信息 mot_ppyoloe_s_36e_ppvehicle.onnx vehicle_attribute_model.onnx 项目 代码 下载 其他 C# Onnx PP-Vehicle 车辆分析&#xff08;包含&#xff1a;车辆检测&#xff0c;识别车型和车辆颜色&#xff09; 效果 模型信息 mot_ppyoloe_s_36e_ppvehi…

代码随想录算法训练营Day 59 || 503.下一个更大元素II、42. 接雨水

503.下一个更大元素II 力扣题目链接(opens new window) 给定一个循环数组&#xff08;最后一个元素的下一个元素是数组的第一个元素&#xff09;&#xff0c;输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更…

docker 安装常用环境

一、 安装linux&#xff08;完整&#xff09; 目前为止docker hub 还是被封着&#xff0c;用阿里云、腾讯云镜像找一找版本直接查就行 默认使用latest最新版 #:latest 可以不写 docker pull centos:latest # 拉取后查看 images docker images #给镜像设置标签 # docker tag […

某基金公司赵哥“逆袭”了!!!

赵哥&#xff0c;在上海一家基金公司做运维主管。 平时工作的首要任务&#xff0c;就是保障公司各项信息系统的安全运行。 万一系统运行中出现了一些重要问题&#xff0c;他还要负责进行调查、记录与汇报... 总之&#xff0c;责任很重&#xff0c;该说不说&#xff0c;搞不好…

10.分组循环练习题

分组循环 https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528771/jiao-ni-yi-ci-xing-ba-dai-ma-xie-dui-on-zuspx/?envTypedaily-question&envId2023-11-16 分组循环 适用场景&#xff1a; 按照题目要求&#xff0c;数组会被分割成若…

大型养殖场需要哪些污水处理设备

大型养殖场是一个涉及环境保护和可持续发展的关键行业&#xff0c;对于处理养殖场产生的污水有着明确的要求和标准。为了确保污水得到有效处理和处理效果达到国家排放标准&#xff0c;大型养殖场需要配备一系列污水处理设备。以下是几种常见的污水处理设备&#xff1a; 1. 水解…

厦门市委常委、常务副市长黄晓舟调研极狐(GitLab)

11 月 22 日&#xff0c;厦门市委常委、常务副市长黄晓舟&#xff0c;厦门市工信局副局长许文恭&#xff0c;厦门市高新技术创业中心有限公司董事长邸国栋等一行人员莅临极狐(GitLab)进行参观调研&#xff0c;深入了解极狐(GitLab)的发展情况。 黄晓舟副市长&#xff08;左&…