人若无名 便可潜心练剑.|

hazy1k

园龄:7个月粉丝:14关注:0

2024-10-04 10:18阅读: 26评论: 0推荐: 0

第11章 电容触摸按键实验

第十一章 电容触摸按键实验

1. 硬件设计

本实验用到的硬件资源有:

  • 指示灯DS0和DS1

  • 定时器TIM2

  • 触摸按键TPAD

前面两个之前均有介绍,我们需要通过 TIM2_CH1(PA5)采集 TPAD 的信号,所以本实验需要用跳线帽短接多功能端口(P12)的 TPAD 和 ADC,以实现 TPAD 连接到 PA5。

屏幕截图 2024 09 18 083717

硬件设置(用跳线帽短接多功能端口的 ADC 和 TPAD 即可)好之后,下面我们开始软件设计。

2. 软件设计

2.1 编程大纲

  1. TIM2_CH1输入捕获初始化

  2. 电容按键复位

  3. 取得捕获值并处理

  4. 电容按键初始化

  5. 电容按键扫描

  6. 主函数测试

2.2 代码分析

2.2.1 TIM2通道1配置输入捕获

// 定时器2通道1输入捕获配置
void TIM2_CH1_Cap_Init(u32 arr, u16 psc)
{
	/* TIM2基础配置 */
	TIM_IC_InitTypeDef TIM2_CH1Config;
	TIM2_Handler.Instance = TIM2;
	TIM2_Handler.Init.Prescaler = psc; 
	TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
	TIM2_Handler.Init.Period = arr;
	TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
	HAL_TIM_IC_Init(&TIM2_Handler);
	/* 输入捕获配置 */
	TIM2_CH1Config.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿捕获
	TIM2_CH1Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
	TIM2_CH1Config.ICPrescaler = TIM_ICPSC_DIV1;
	TIM2_CH1Config.ICFilter = 0;
	HAL_TIM_IC_ConfigChannel(&TIM2_Handler, &TIM2_CH1Config, TIM_CHANNEL_1); // 配置TIM2_CH1
	HAL_TIM_IC_Start(&TIM2_Handler, TIM_CHANNEL_1);
}

// TIM2底层驱动
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
	GPIO_InitTypeDef GPIO_Initure;
	__HAL_RCC_TIM2_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_Initure.Pin = GPIO_PIN_5;
	GPIO_Initure.Mode = GPIO_MODE_AF_PP;
	GPIO_Initure.Pull = GPIO_NOPULL;
	GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_Initure.Alternate = GPIO_AF1_TIM2;
	HAL_GPIO_Init(GPIOA, &GPIO_Initure);
}

函数TIM2_CH1_Cap_Init和上一章输入捕获实验中函数TIM5_CH1_Cap_Init的配置过程几乎是一模一样的,不同的是上一章实验开 TIM5_CH1_Cap_Init 函数最后调用的是函数HAL_TIM_IC_Start_IT,使能输入捕获通道的同事开启了输入捕获中断,而该函数最后调用的函数是 HAL_TIM_IC_Start,只是开启了输入捕获通道,并没有开启输入捕获中断。

函数 HAL_TIM_IC_MspInit 是输入捕获通用 MSP 回调函数,该函数的作用是使能定时器和 GPIO 时钟,配置 GPIO 复用映射关系。该函数功能和输入捕获实验中该函数作用基本类似。

2.2.2 复位电容按键和定时器

// 复位电容按键:释放电容点亮,清楚定时器计数值
void TPAD_Reset(void)
{
	GPIO_InitTypeDef GPIO_Initure;
	GPIO_Initure.Pin = GPIO_PIN_5;
	GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_Initure.Pull = GPIO_PULLDOWN;
	GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_Initure);
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 释放电容
	delay_ms(5);
	__HAL_TIM_CLEAR_FLAG(&TIM2_Handler, TIM_FLAG_CC1|TIM_FLAG_UPDATE); // 清除中断标志位
	__HAL_TIM_SET_COUNTER(&TIM2_Handler, 0); // 清除计数器
	GPIO_Initure.Mode = GPIO_MODE_AF_PP;
	GPIO_Initure.Pull = GPIO_NOPULL;
	GPIO_Initure.Alternate = GPIO_AF1_TIM2; // PA5复用为TIM2_CH1
	HAL_GPIO_Init(GPIOA, &GPIO_Initure);
}

函数 TPAD_Reset 顾名思义,是进行一次复位操作。先设置 PA5 输出低电平,电容放电,同时清除中断标志位并且计数器值清零,然后配置 PA5 为复用功能浮空输入,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启 TIM2_CH1 的输入捕获。

2.2.3 取得定时器捕获值

// 取得捕获值
u16 TPAD_Get_Val(void)
{
	TPAD_Reset();
	while(__HAL_TIM_GET_FLAG(&TIM2_Handler, TIM_FLAG_CC1) == RESET) // 捕获上升沿
	{
		if(__HAL_TIM_GET_COUNTER(&TIM2_Handler) >= TPAD_ARR_MAX_VAL-500)
		{
			return __HAL_TIM_GET_COUNTER(&TIM2_Handler); // 已经超时,直接返回CNT值
		}
	};
	return HAL_TIM_ReadCapturedValue(&TIM2_Handler, TIM_CHANNEL_1); // 读取计数器值
}

// 读取n次,取最大值
u16 TPAD_Get_MaxVal(u8 n)
{ 
	u16 temp = 0; 
	u16 res = 0; 
	u8 lcntnum = n*2/3; // 至少2/3*n的有效个触摸,才算有效
	u8 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;
}  

2.2.4 电容按键初始化

// 电容按键初始化
uint8_t tpad_init(u16 psc)
{
    u16 buf[10];
    u16 temp;
    uint8_t i, j;
    TIM2_CH1_Cap_Init(TPAD_ARR_MAX_VAL, psc-1); // 初始化定时器2通道1,设置自动重装载值和预分频值
	for(i = 0; i < 10; i++)
	{
		buf[i] = TPAD_Get_Val(); // 读取电容触摸按键值
	}
	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];
	}
	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; // 电容按键初始化失败
	}
	return 0; // 电容按键初始化成功
}

函数 TPAD_Init 用于初始化输入捕获,并获取默认的 TPAD 值。该函数有一个参数,用来传递分频系数,其实是为了配置 TIM2_CH1_Cap_Init 的计数周期。在该函数中连续 10 次读取TPAD 值,将这些值升序排列后取中间 6 个值再做平均(这样做的目的是尽量减少误差),并赋值给 tpad_default_val,用于后续触摸判断的标准。

2.2.5 电容按键扫描

// 触摸按键扫描
u8 TPAD_Scan(u8 mode)
{
	static u8 keyen = 0; // 0,可以开始检测;>0,还不能开始检测	 
	u8 res = 0;
	u8 sample = 3; // 默认采样次数为3次	 
	u16 rval;
	if(mode)
	{
		sample = 6;	// 支持连按的时候,设置采样次数为6次
		keyen = 0;	// 支持连按	  
	}
	rval = TPAD_Get_MaxVal(sample); // 采样n次 
	if(rval>(tpad_default_val*4/3)&&rval<(10*tpad_default_val)) // 大于tpad_default_val+(1/3)*tpad_default_val,且小于10倍tpad_default_val,则有效
	{							 
		if(keyen==0)
			res = 1; // keyen==0,有效 
		//printf("r:%d\r\n",rval);		     	    					   
		keyen = 3; // 至少要再过3次之后才能按键有效   
	} 
	if(keyen)
		keyen--; // 计数器减1		   							   		     	    					   
	return res;
}  

函数 TPAD_Scan 用于扫描 TPAD 是否有触摸,该函数的参数 mode,用于设置是否支持连续触发。返回值如果是 0,说明没有触摸,如果是 1,则说明有触摸。该函数同样包含了一个静态变量,用于检测控制,类似第七章的 KEY_Scan 函数。所以该函数同样是不可重入的。在函数中,我们通过连续读取 3 次(不支持连续按的时候)TPAD 的值,取最大值和 tpad_default_val*4/3比较,如果大于则说明有触摸,如果小于,则说明无触摸。其中 tpad_default_val 是我们在调用TPAD_Init 函数的时候得到的值,然后取其 4/3 为门限值。

2.2.6 主函数

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

int main(void)
{
	u8 t=0; 
    HAL_Init();                   	
    Stm32_Clock_Init(336,8,2,7);  	
	delay_init(168);               	
	uart_init(115200);            
	LED_Init();						
	tpad_init(8); 
   	while(1)
	{					  						  		 
 		if(TPAD_Scan(0)) // 成功捕获到了一次上升沿(此函数执行时间至少15ms)
		{
			LED1=!LED1;	// LED1取反
		}
		t++;
		if(t == 15)		 
		{
			t = 0;
			LED0 = !LED0; // LED0取反,提示程序正在运行
		}
		delay_ms(10);
	}
}

}

该main 函数比较简单, TPAD_Init(8)函数执行之后,就开始触摸按键的扫描,当有触摸的时候,对 DS1 取反,而 DS0 则有规律的间隔取反,提示程序正在运行。

3. 小结

实际上这个实验就是运用的定时器的输入捕获功能,下面简单回顾一下:

3.1 硬件连接

  1. LED连接
  • 将LED1(比如连接到GPIOA的引脚5)连接到STM32的相应引脚。
  1. 电容按键连接
  • 使用一个电容按键(如连接到GPIOB的引脚6),并设置为输入模式。
  1. 定时器设置
  • 使用TIM2设置为输入捕获模式,监测电容按键的按下事件。

3.2 软件配置步骤

1. 初始化工程

  • 使用STM32CubeMX创建一个新的STM32F407项目。
  • 启用GPIOAGPIOB,配置PA5为输出(LED),PB6为输入(电容按键)。
  • 启用TIM2,设置为输入捕获模式,选择适当的通道(例如通道1)。
  • 生成代码并打开Keil/STM32CubeIDE进行代码编写。

2. 编写代码

main.c文件中添加以下代码:

#include "main.h"

TIM_HandleTypeDef htim2;
GPIO_InitTypeDef GPIO_InitStruct;

// LED状态
uint8_t ledState = 0;

// 输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        // 触发LED状态取反
        ledState ^= 1; // 取反
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, ledState); // 更新LED状态
    }
}

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);

// 主函数
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();

    // 启动输入捕获
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);

    while (1) {
        // 主循环可以做其他事情
    }
}

static void MX_GPIO_Init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // 初始化LED引脚
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 初始化电容按键引脚
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL; // 或根据需要选择上拉或下拉
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

static void MX_TIM2_Init(void) {
    __HAL_RCC_TIM2_CLK_ENABLE();

    // 配置TIM2
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 8399; // 让定时器计数频率为1kHz (84MHz/8400)
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFF; // 最大计数值
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);

    // 配置输入捕获
    TIM_IC_InitTypeDef sConfigIC;
    sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿触发
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
}

void SystemClock_Config(void) {
    // 系统时钟配置
    // 这里省略具体配置,可以使用CubeMX生成的代码
}

3.3 代码说明

  • LED状态管理:使用ledState变量来跟踪LED的状态,并在输入捕获中触发时取反。
  • 输入捕获回调:当TIM2接收到上升沿信号时,会调用HAL_TIM_IC_CaptureCallback函数,改变LED状态。
  • 定时器配置:TIM2被配置为输入捕获模式,监测PB6引脚的变化。

2024.10.4 第一次修订,后期不再维护

2025.3.4 修订程序,简化内容

本文作者:hazy1k

本文链接:https://www.cnblogs.com/hazy1k/p/18446396

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hazy1k  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起