前言:
本实验通过STM32CubeMX和HAL库结合,实现正点原子的TPAD实验。
使用工具:
芯片:STM32F103ZET6(精英版)
软件:STM32CubeMx软件、MDK-Keil软件
库:STMF1 HAL库
知识概括:
GPIO控制LED的亮灭
定时器输入捕获
STM32CubeMx创建过程
什么是TPAD
我们使用的是检测电容充放电时间的方法来判断是否有触摸,图 中的 A、B 分别表示有无人体按下时电容的充放电曲线。其中 R 是外接的电容充电电阻, Cs 是没有触摸按下时 TPAD 与 PCB 之间的杂散电容。而 Cx 则是有手指按下的时候,手指与 TPAD 之间形成的电容。图中的开关是电容放电开关(实际使用时,由 STM32F1 的 IO 代替)。
先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R 给 Cs(或 Cs+Cx)充电, 当没有手指触摸的时候,Cs 的充电曲线如图中的 A 曲线。而当有手指触摸的时候,手指和 TPAD 之间引入了新的电容 Cx,此时 Cs+Cx 的充电曲线如图中的 B 曲线。从上图可以看出,A、B 两 种情况下,Vc 达到 Vth 的时间分别为 Tcs 和 Tcs+Tcx。 其中,除了 Cs 和 Cx 我们需要计算,其他都是已知的,根据电容充放电公式:
我们使用 PA1(TIM2_CH2(精英版))来检测 TPAD 是否有触摸,在每次检测之前,我们先配置 PA1 为推挽输出,将电容 Cs(或 Cs+Cx)放电,然后配置 PA1 为浮空输入,利用外部上拉电阻 给电容 Cs(Cs+Cx)充电,同时开启 TIM5_CH2 的输入捕获,检测上升沿,当检测到上升沿的时 候,就认为电容充电完成了,完成一次捕获检测。
每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值, 记为 tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与 tpad_default_val 的 对比,来判断是不是有触摸发生。
原理图:
由于设计时 PA1 不直接连接到电容触摸按键,而是引到了插针上,我们需要通过跳线帽把 P10 上标为 ADC 的引脚与标号为“TPAD”的标号连接到一起。
简单来说:
TAPD是一个电容,我们先对电容进行放电,之后电容进行充电的过程中,IO端口会由低电平转换为高电平,通过定时器的输入捕获获取电容从低电平转换为高电平的时间。时间为Tcs,手指按下时,电容会增大,低电平转换为高电平的时间会加长,为Tcx。通过两次时间对比判断TPAD是否有触摸。
注:以上部分内容转自正点原子
工程创建
1设置RCC
设置高速外部时钟HSE,选择外部时钟源
2设置时钟
3设置LED
将PE5设置为输出模式,上拉电阻,同理设置PB5
4定时器配置
设置TIM2的通道2
预分频系数为71,频率为72MHz / (71+1) = 1us
自动加载值为最大值65535
上升沿捕获
滤波值为0
同时在NVIC中使能TIM2的中断
5项目文件
6创建工程文件
点击右上角CENERATE CODE 创建工程
代码部分:
创建tapd.c tapd.h两个文件
tapd.h
#ifndef __TPAD_H__
#define __TPAD_H__
#include "main.h"
void tpad_init(void); // 初始化
uint8_t tapd_scan(uint8_t mode); // 扫描函数
#endif
tapd.c
#include "tpad.h"
#include "tim.h" // 需要使用到定时器的捕获功能
/*
@brief 复位TPAD
#node 将TPAD按键看作是一个电容,手指按下和不按下电容值有变化
先将GPIO设置为推挽输出,输出0,进行放电,在设置为GOIP为
浮空输入,等待电容充电,并且捕获上拉
*/
static uint8_t flag = 0; // 检测是否执行了输入捕获回调函数
uint16_t temp = 0; // 存取捕获的值
uint16_t tpad_default_val; // 手指不按下时,电容充电到高电平的时间
static void tpad_reset(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_1; // 将PA1设置为开漏输出
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // 将PA1置0,对电容进行放电
htim2.Instance->SR = 0; // 清除标记
htim2.Instance->CNT = 0; // 归零
GPIO_InitStruct.Pin = GPIO_PIN_1; // 设置为输入模式,进行输入捕获
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 设置为上升沿捕获
__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); // 开启定时器捕获
}
static void tpad_get_val(void)
{
tpad_reset(); // 先进行复位(放电,置位,开启定时器捕获)
while(flag == 0) // 判断捕获中断回调函数是否捕获完成
{
HAL_Delay(1);
}
flag = 0; // 捕获完成后再次将flag置0
}
// 捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
UNUSED(htim);
HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); // 先关闭定时器捕获
if (TIM2 == htim->Instance){
temp = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2); // 获取当前捕获值
flag = 1; // 将flag置1,说明捕获完成
}
}
/**
* @brief 读取 n 次, 取最大值
* @param n :连续获取的次数
* @retval n 次读数里面读到的最大读数值
*/
static uint16_t tapt_get_maxval(uint8_t n)
{
uint16_t maxval = 0;
while (n--) {
tpad_get_val(); // 进行数据捕获,捕获成功后temp的值更新为最新的捕获值
if (temp > maxval) {
maxval = temp; // 取捕获的最大值
}
}
return maxval; // 将数值返回
}
// 初始化触摸按键
void tpad_init(void)
{
uint16_t buf[10];
uint16_t m;
uint8_t i, j;
for (i = 0; i < 10; i++) { // 获取10个数据
tpad_get_val();
buf[i] = temp;
}
for (i = 0; i < 9; i++) { // 进行排序
for (j = i+1; j < 10; j++) {
if (buf[i] < buf[j]){
m = buf[i];
buf[i] = buf[j];
buf[j] = m;
}
}
}
m = 0;
for (i = 2; i < 8; i++) { // 取中间的6个数据
m += buf[i];
}
tpad_default_val = m / 6; // 求平均值作为没有触摸时的值
}
/**
* @brief 扫描触摸按键
* @param mode :扫描模式
* @arg 0, 不支持连续触发(按下一次必须松开才能按下一次);
* @arg 1, 支持连续触发(可以一直按下)
* @retval 0, 没有按下; 1, 有按下;
*/
uint8_t tapd_scan(uint8_t mode) // 扫秒模式
{
static uint8_t keyen = 0; /* 0, 可以开始检测; >0, 还不能开始检测; */
uint8_t res = 0;
uint8_t sample = 3; /* 默认采样次数为 3 次 */
uint16_t rval = 0;
if (mode) { // mode = 1为扫描模式,
sample = 6; /* 支持连按的时候,设置采样次数为 6 次 */
keyen = 0; /* 支持连按, 每次调用该函数都可以检测 */
}
rval = tapt_get_maxval(sample); // 获取读取的值
if (rval > (tpad_default_val + 15)) {
if (keyen == 0) {
res = 1;
}
keyen = 3;
}
if (keyen) keyen--; // 单次触发松手三次后将keyen置0,然后才能将res赋值为1
return res;
}
在main函数的中添加一下语句:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "tpad.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
tpad_init(); // TPAD电容按键初始化
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t t = 0;
while (1)
{
if (tapd_scan(0)) { // 非连续触发模式,返回1按下,返回0没有按下
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
}
t++;
if (t == 15) {
t = 0;
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
结果:
PE5连接的LED不停的闪烁说明程序在正常运行,按下电容按键后PB5连接的LED状态取反实现LED的亮灭。