【正点原子STM32连载】 第二十三章 电容触摸按键实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

您所在的位置:网站首页 stm32按一下亮再按一下灭 【正点原子STM32连载】 第二十三章 电容触摸按键实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

【正点原子STM32连载】 第二十三章 电容触摸按键实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

2023-06-06 20:10| 来源: 网络整理| 查看: 265

1)实验平台:正点原子stm32f103战舰开发板V4 2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420 3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第二十三章 电容触摸按键实验

上一章,我们介绍了STM32F1的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用TIM5的通道2(PA1)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制DS1的亮灭。从本章分为如下几个部分: 21.1电容触摸按键简介 21.2硬件设计 21.3软件设计 21.4下载验证

23.1 电容触摸按键简介

前面我们学习过了机械按键,这节我们将介绍另一种人机交互设备:电容触摸按键。 电容式触摸按键已经广泛应用在家用电器、消费电子市场,其主要优势有:无机械装置,使用寿命长;非接触式感应,面板不需要开孔;产品更加美观简洁;防水可以做到很好。 战舰F103上的触摸按键TPAD其实就是一小块覆铜区域,其形状就是正点原子的logo,如图23.1.1所示。 在这里插入图片描述

图23.1.1 电容按键TPAD外观 与机械按键不同,这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图23.1.2中的A、B分别表示有无人体按下时电容的充放电曲线。其中R是外接的电容充电电阻,Cs是没有触摸按下时TPAD与PCB之间的杂散电容。而Cx则是有手指按下的时候,手指与TPAD之间形成的电容。图中的开关是电容放电开关(实际使用时,由STM32F1的IO代替)。 在这里插入图片描述

图23.1.2 电容按键TPAD外 先用开关将Cs(或Cs+Cx)上的电放尽,然后断开开关,让R给Cs(或Cs+Cx)充电,当没有手指触摸的时候,Cs的充电曲线如图中的A曲线。而当有手指触摸的时候,手指和TPAD之间引入了新的电容Cx,此时Cs+Cx的充电曲线如图中的B曲线。从上图可以看出,A、B两种情况下,Vc达到Vth的时间分别为Tcs和Tcs+Tcx。 其中,除了Cs和Cx我们需要计算,其他都是已知的,根据电容充放电公式:

其中Vc为电容电压,V0为充电电压,R为充电电阻,C为电容容值,e为自然底数,t为充电时间。根据这个公式,我们就可以计算出Cs和Cx。利用这个公式,我们还可以把战舰开发板作为一个简单的电容计,直接可以测电容容量了,有兴趣的朋友可以捣鼓下。 在本章中,其实我们只要能够区分Tcs和Tcs+Tcx,就已经可以实现触摸检测了,当充电时间在Tcs附近,就可以认为没有触摸,而当充电时间大于Tcs+Tx时,就认为有触摸按下(Tx为检测阀值)。 本章,我们使用PA1(TIM5_CH2)来检测TPAD是否有触摸,在每次检测之前,我们先配置PA1为推挽输出,将电容Cs(或Cs+Cx)放电,然后配置PA1为浮空输入,利用外部上拉电阻给电容Cs(Cs+Cx)充电,同时开启TIM5_CH2的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。 在MCU每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与tpad_default_val的对比,来判断是不是有触摸发生。 关于输入捕获的配置,在上一章我们已经有详细介绍了,这里我们就不再介绍。至此,电容触摸按键的原理介绍完毕。 23.2 硬件设计

例程功能 LED0用来指示程序运行,150ms变换一次状态,即约300ms一次闪烁。不断扫描按键的状态,如果判定了电容触摸按键按下,我们就把LED1的状态翻转一次。硬件资源 1)LED灯: LED0 – PB5 LED1 – PE5 2) 定时器 TIM5 3) GPIO:PA1,用于控制触摸按键TPAD。原理图 触摸按键的原理图TPAD设计如图23.2.1所示 在这里插入图片描述

图23.2.1 电容按键TPAD连接原理图 由于设计时PA1不直接连接到电容触摸按键,而是引到了插针上,我们需要通过跳线帽把P10上标为ADC的引脚与标号为“TPAD”的标号连接到一起,连接图23.2.2所示。 在这里插入图片描述

图23.2.2 用跳线帽连接电容按键TPAD和PA1 23.3 程序设计 我们在基本定时器一节已经学习过定时器的输入捕获功能,这里我们可以类似地,用定时器5来实现这对TPAD引脚上的电平状态进行捕获的功能。本实验用到的HAL库驱动请回顾基本定时器实验的介绍。下面直接从程序流程图开始介绍。 23.3.1 程序流程图 在这里插入图片描述

图23.2.3.1.1 电容触摸按键实验程序流程图 23.3.2 程序解析 TPAD可以看作《实验9-3 通用定时器输入捕获实验》的一个应用案例,相关的HAL库函数函数在定时器章节我们已经介绍过了,所以这里就不重复介绍了,大家回过头复习一下即可。

TPAD驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,TPAD的驱动主要包括两个文件:tpad.c和tpad.h。 首先看tpad.h头文件的几个宏定义: /* TPAD 引脚 及 定时器 定义 */ #define TPAD_GPIO_PORT GPIOA #define TPAD_GPIO_PIN GPIO_PIN_1 /* PA口时钟使能 */ #define TPAD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) #define TPAD_TIMX_CAP TIM5 #define TPAD_TIMX_CAP_CHY TIM_CHANNEL_2 /* 通道Y, 1 GPIO_InitTypeDef gpio_init_struct; TIM_IC_InitTypeDef timx_ic_cap_chy; TPAD_GPIO_CLK_ENABLE(); /* TPAD引脚 时钟使能 */ TPAD_TIMX_CAP_CHY_CLK_ENABLE(); /* 定时器 时钟使能 */ gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */ gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */ HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚浮空输入 */ g_timx_cap_chy_handle.Instance = TPAD_TIMX_CAP; /* 定时器5 */ g_timx_cap_chy_handle.Init.Prescaler = psc; /* 定时器分频 */ g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/*向上计数模式 */ g_timx_cap_chy_handle.Init.Period = arr; /* 自动重装载值 */ g_timx_cap_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/*不分频*/ HAL_TIM_IC_Init(&g_timx_cap_chy_handle); timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */ timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;/*映射TI1*/ timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入不分频 */ timx_ic_cap_chy.ICFilter = 0; /* 配置输入滤波器,不滤波 */ HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy, &timx_ic_cap_chy, TPAD_TIMX_CAP_CHY);/* 配置TIM5通道2 */ HAL_TIM_IC_Start(&g_timx_cap_chy_handle,TPAD_TIMX_CAP_CHY);/* 使能输入捕获 */ }

这和我们《实验9-3 通用定时器输入捕获实验》的代码基本一样,原理就不重复解释了,接下我们通过控制变量法,每次先给TPAD放电(STM32输出低电平)相同时间,然后释放,监测VCC每次给TPAD的充电时间,由此可以得到一个充电时间,操作的代码如下:

/** * @brief 复位TPAD * @note 我们将TPAD按键看做是一个电容, 当手指按下/不按下时容值有变化 * 该函数将GPIO设置成推挽输出, 然后输出0, 进行放电, 然后再设置 * GPIO为浮空输入, 等待外部大电阻慢慢充电 * @param 无 * @retval 无 */ static void tpad_reset(void) { GPIO_InitTypeDef gpio_init_struct; gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */ gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */ HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚输出0, 放电 */ HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET); delay_ms(5); g_timx_cap_chy_handle.Instance->SR = 0; /* 清除标记 */ g_timx_cap_chy_handle.Instance->CNT = 0; /* 归零 */ gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的GPIO口 */ gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_NOPULL; /* 浮空 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */ HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD引脚浮空输入 */ } /** * @brief 得到定时器捕获值 * @note 如果超时, 则直接返回定时器的计数值 * 我们定义超时时间为: TPAD_ARR_MAX_VAL - 500 * @param 无 * @retval 捕获值/计数值(超时的情况下返回) */ static uint16_t tpad_get_val(void) { uint32_t flag = (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_1)?TIM_FLAG_CC1:\ (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_2)?TIM_FLAG_CC2:\ (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_3)?TIM_FLAG_CC3:TIM_FLAG_CC4; tpad_reset(); while (__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle ,flag) == RESET) { /* 等待通道CHY捕获上升沿 */ if (g_timx_cap_chy_handler.Instance->CNT > TPAD_ARR_MAX_VAL - 500) { return g_timx_cap_chy_handle.Instance->CNT; /* 超时了,直接返回CNT的值 */ } } return TPAD_TIMX_CAP_CHY_CCRX; /* 返回捕获/比较值 */ } /** * @brief 读取n次, 取最大值 * @param n :连续获取的次数 * @retval n次读数里面读到的最大读数值 */ static uint16_t tpad_get_maxval(uint8_t n) { uint16_t temp = 0; uint16_t maxval = 0; while (n--) { temp = tpad_get_val(); /* 得到一次值 */ if (temp > maxval)maxval = temp; } return maxval; }

得到充电时间后,接下来我们要做的就是获取没有按下TPAD时的充电时间,并把它作为基准来确认后续有无按下操作,我们定义全局变量g_tpad_default_val来保存这个值,通过多次平均的滤波算法来减小误差,编写的初始化函数tpad_init代码如下。

/** * @brief 初始化触摸按键 * @param psc : 分频系数(值越小, 越灵敏, 最小值为: 1) * @retval 0, 初始化成功; 1, 初始化失败; */ uint8_t tpad_init(uint16_t psc) { uint16_t buf[10]; uint16_t temp; uint8_t j, i; tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc - 1);/* 以72/(psc-1)Mhz的频率计数 */ for (i = 0; i for (j = i + 1; j temp = buf[i]; buf[i] = buf[j]; buf[j] = temp; } } } temp = 0; for (i = 2; i return 1; /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */ } return 0; } 得到初始值后,我们需编写一个按键扫描函数,以方便在需要监控TPAD的地方调用,代码如下: /** * @brief 扫描触摸按键 * @param mode :扫描模式 * @arg 0, 不支持连续触发(按下一次必须松开才能按下一次); * @arg 1, 支持连续触发(可以一直按下) * @retval 0, 没有按下; 1, 有按下; */ 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 > (g_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; }

TPAD函数到此就编写完了,接下来我们通过main函数编写测试代码来验证一下TPAD的逻辑是否正确。 2. main.c代码 在main.c里面编写如下代码:

int main(void) { uint8_t t = 0; HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ led_init(); /* 初始化LED */ tpad_init(6); /* 初始化触摸按键 */ while (1) { if (tpad_scan(0)) /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */ { LED1_TOGGLE(); /* LED1取反 */ } t++; if (t == 15) { t = 0; LED0_TOGGLE(); /* LED0取反 */ } delay_ms(10); } }

初始化了必要的外设后,我们通过循环来实现我们的代码操作。我们在扫描函数中定义了电容按触摸发生后的状态,通过判断返回值来判断是否符合按下的条件,如果按下我们就翻转一次LED1。LED0通过累计延时次数的方法,既能保证扫描的频率,又能达到定时翻转的目的。 23.4 下载验证 下载代码后,可以看到LED0不停闪烁(每300ms闪烁一次),用手指按下电容按键时,LED1的状态发生改变(亮灭交替一次)。这里记得TPAD引脚和PA1都是连接到开发板上的排针上的,开始测试前需要连接好,否则测试就不准了,如果下载代码前没有连接好,请连接后复位重新测试即可。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3