当前位置: 首页 > news >正文

STM32实战总结:HAL之触摸按键

之前学过机械按键,可参考:

51单片机外设篇:按键_路溪非溪的博客-CSDN博客

外设篇:按键和CPU的中断系统_路溪非溪的博客-CSDN博客

原理图

触摸按键也是一种按键,我们先看其电路连接:

四个触摸按键。

TTP224N-BSB是一个触摸按键芯片,四个输出,四个输入,具体查看芯片数据手册。

触摸按键的输出端连接到了PE0~PE3。PE0~PE3是EXTI0~EXTI3。

触摸按键的原理跟触摸屏有点像,手指按上去的时候,会引起电容改变,从而引起电路改变,芯片通过检测这种细微变化来识别是否按下了按键。

在以上TTP224N-BSB芯片中,TOG和OD引脚是浮空的,即默认值。其中,AHLB、VDD和LPMB引脚被连接到了电源上;SM、VSS和MOT0接地。

这些引脚什么意思呢?

以下附TTP224N-BSB芯片的关键特性:

******************************************************************************************************

TTP224是一款使用电容式感应原理设计的触摸IC,其稳定的感应方式可以应用到各种不同电子类产品,面板介质可以是完全绝源的材料,专为取代传统的机械结构开关或普通按键而设 计。提供4个触摸输入端口及4个直接输出端口。

工作电压 2.4V~5.5V

可以由外部Option选择是否启用内部稳压电路功能

工作电流@VDD=3V无负载时:

        低功耗模式下典型值2.5uA

        快速模式下典型值9uA

@VDD=3V时,在快速模式下KEY最快响应时间为100mS,低功耗模式下为200mS.

各KEY灵敏度可以由外部电容进行调节(0~50pF).

提供LPMB端口选择快速模式或低功耗模式.

提供直接输出模式,触发模式,开漏输出, CMOS高电平有效或低电平有效输出, 经由 TOG/AHLB/OD端口选择.

提供两个无二极管保护的输出端口TPQ0D,TPQ2D仅限于低电平有效.

提供MOT1, MOT0端口选择最大输出时间:120秒/64秒/16秒/无穷大

上电后约有0.5秒的系统稳定时间,在此期间内不要触摸Touch PAD,且触摸功能无效

有自动校准功能,当无按键被触摸时,系统重新校准周期约为4.0秒

******************************************************************************************************

以上电路中,出去电源和地,还有另外6个引脚:

TOG——0——直接模式,直接模式还是触发模式有何区别?直接模式就是按下是一种状态,不按是一种状态,固定的,比如按下是高电平,不按就是低电平;触发模式就是按一下就变一下,比如一开始是0,按一下就变成了1,再按一下就变成了0……如此循环。

OD——1——CMOS 输出

AHLB——1——低电平有效,这里的高电平有效还是低电平有效不好理解,指的是,当按下时,输出什么电平,低电平有效,那么当按下按键就输出低电平,高电平有效,那么当按下按键就输出高电平;

LPMB——1——快速模式

SM——0——单键模式

MOT0——0——最长输出时间16秒

******************************************************************************************************

更多细节参考:按键模块TTP224N-BSB - 百度文库

要想写程序,得弄明白按键按下时,会有什么变化,才能触发外部中断。

根据上面的各引脚电平状态可知,按下时是低电平,那么不按时就是高电平,所以就是按下按键时,输出就从高电平变成了低电平,可以通过低电平/下降沿来触发中断。

外部中断

在51中,有外部中断引脚,将按键连接到外部中断引脚,就可以设置相应的触发。

那么,在32中呢?外部中断在哪?

先弄清楚一个概念:

中断和事件的区别:

STM32之中断与事件---中断与事件的区别_无痕幽雨的博客-CSDN博客_stm32中断和事件

具体查阅参考手册。此处仅贴上外部中断映像图:

每个外部中断只能选择一个引脚作为中断线。比如选择了PE0作为外部中断0的中断线,那么其他端口PA/PB/PC/PD/PF/PG就不能配置EXT0了。

MX初始化

先配置继电器(这里是因为板上有四个触摸按键,但只有3个LED灯,所以就借用这里的D6来作为一个LED使用,也能感受下直接驱动和使用中断驱动的区别):

配置接继电器的PG13引脚:

配置PE0~PE3为外部中断(下降沿触发):

初始化对应中断:

关于MX中断的配置,有必要说明下:

stm32的优先级配置

STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作“亚优先级”或“副优先级”或“从优先级”,每个中断源都需要被指定这两种优先级。

  • 高抢占优先级的中断可以打断低抢占优先级的中断
  • 相同抢占优先级,高响应优先级无法打断低响应优先级的中断
  • 相同抢占优先级,两个中断同时触发时,优先执行高响应优先级的中断
  • 若所有优先级都相同,谁先触发执行对应中断
  • 优先级数字越低代表优先级越高


抢占式优先级和响应优先级通过中断优先级组进行分配
中断优先级组在stm32中一般可分为0-5组,分组配置在寄存器SCB->AIRCR中:

中断优先级组

  • 组0就是4位都用来设置成响应优先级,2^4=16位都是响应优先级
  • 组1分为21两个抢占优先级,在这两个抢占优先级里面还分别有23八个响应优先级
  • 组2分为22四个抢占优先级,在这四个抢占优先级里面还分别有22四个响应优先级
  • 组3分为23八个抢占优先级,在这八个抢占优先级里面还分别有21两个响应优先级
  • 组4分为24十六个都是抢占优先级


在MX中,就是这样的原理。组数字和优先级的数字是不同的,组数字是一种分组,而优先级数字是有优先级意义的,一定要注意。

接下来,要实现一个功能。 

点击一下按键,就能亮灯;

长按按键,灯就会闪烁;

这里的思路是这样的:按下按键,就能通过下降沿触发外部中断,中断服务函数中,点亮相应的灯,并输出相应的串口数据;LED的驱动已经写过了;要增加一个继电器的灯的驱动。

点击可以通过下降沿触发中断;那么长按怎么解决呢?通过低电平触发吗?但是已经设置成了下降沿触发,怎么还能用低电平触发呢?

下降沿能触发中断,但是中断里可以通过检测多长时间的低电平后再执行相关代码。

搭建框架

先根据我自己的思路去搭建框架。

初始化后生成代码,之前的LED灯和串口的代码不变。

生成代码后先编译一遍看有没有问题。根据初始化可知,3个LED灯是亮的,继电器的灯是灭的,按键能触发中断,但是此时不会产生什么影响。

GPIO的初始化代码有增加:

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, LED1_Pin|LED2_Pin|LED3_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pins : PEPin PEPin PEPin PEPin */
  GPIO_InitStruct.Pin = KEY2_Pin|KEY3_Pin|KEY0_Pin|KEY1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pins : PEPin PEPin PEPin */
  GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin|LED3_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = Relay_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(Relay_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);

  HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI1_IRQn);

  HAL_NVIC_SetPriority(EXTI2_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI2_IRQn);

  HAL_NVIC_SetPriority(EXTI3_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI3_IRQn);

}

跟上面的配置是一致的。每次配置MX后类似的内容都是一样的,后续不再赘述。

开始搭建框架

先写继电器的代码,建立两个文件,relay.c和relay.h

relay.h

#ifndef _RELAY_H_
#define _RELAY_H_

//确定要实现的led功能
typedef struct
{ 
    //打开继电器
    void (*relayOpen)(void);
    //关闭继电器
    void (*relayClose)(void);
    //转换继电器状态
    void (*relaySwitch)(void); 
} relay_t;

//将结构体声明出去
extern relay_t relayObj;

#endif

relay.c

#include "myapplication.h"

static void RelayOpen(void);
static void RelayClose(void);
static void RelaySwitch(void);

relay_t relayObj = 
{
    RelayOpen,
	RelayClose,
	RelaySwitch
};
    
static void RelayOpen(void)
{
	HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_SET);
}

static void RelayClose(void)
{   
	HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_RESET);
}

static void RelaySwitch(void)
{
	HAL_GPIO_TogglePin(Relay_GPIO_Port, Relay_Pin);
}

这里只是操作GPIO口,和LED的操作是一模一样的。

外部中断代码

因为配置了外部中断,所以在中断文件stm32f1xx_it.c中,会有相应的中断服务函数:

/**
  * @brief This function handles EXTI line0 interrupt.
  */
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY0_Pin);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}

/**
  * @brief This function handles EXTI line1 interrupt.
  */
void EXTI1_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI1_IRQn 0 */

  /* USER CODE END EXTI1_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
  /* USER CODE BEGIN EXTI1_IRQn 1 */

  /* USER CODE END EXTI1_IRQn 1 */
}

/**
  * @brief This function handles EXTI line2 interrupt.
  */
void EXTI2_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI2_IRQn 0 */

  /* USER CODE END EXTI2_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
  /* USER CODE BEGIN EXTI2_IRQn 1 */

  /* USER CODE END EXTI2_IRQn 1 */
}

/**
  * @brief This function handles EXTI line3 interrupt.
  */
void EXTI3_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI3_IRQn 0 */

  /* USER CODE END EXTI3_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY3_Pin);
  /* USER CODE BEGIN EXTI3_IRQn 1 */

  /* USER CODE END EXTI3_IRQn 1 */
}

根据调用的函数继续往下走,跳转到了stm32f1xx_hal_gpio.c中:

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

/**
  * @brief  EXTI line detection callbacks.
  * @param  GPIO_Pin: Specifies the pins connected EXTI line
  * @retval None
  */
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
}

上面的中断处理函数又调用了下面的那个回调函数。

同理,我们要重写这个函数。

//重写外部中断函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin)
	{
		case KEY0_Pin :
			printf("first key is running.\n\r");
			led_operater_middle.ledMiddle(LED1, LedExtinguish);
			break;
		case KEY1_Pin :
			printf("second key is running.\n\r");
			led_operater_middle.ledMiddle(LED2, LedExtinguish);
			break;
		case KEY2_Pin :
			printf("third key is running.\n\r");
			led_operater_middle.ledMiddle(LED3, LedExtinguish);
			break;
		case KEY3_Pin :
			printf("forth key is running.\n\r");
			relayObj.relayOpen();
			break;
		default:
			printf("key fault!please click right key.\n\r");	
	}
}

这能够完成点击触发中断的功能。

那么,按键长按怎么解决呢?

长按可以在触发中断后的处理函数中解决,下降沿之后,判断是短暂的低电平,还是有个持续的低电平,如果是短暂的低电平,那么就是单击;如果是连续的低电平,那么就认为是长按。判断是否是高低电平,有个读引脚状态的函数HAL_GPIO_ReadPin(……);

时间关系,此处暂时不写了,后面有空再补充吧。

相关文章:

  • 湖仓一体(Lakehouse)是什么?
  • 【测绘程序设计】Excel度(°)转换度分秒(° ‘ “)模板附代码超实用版
  • odoo 视图部分详解(四)
  • Java中对象的打印
  • STARK Low Degree Testing——FRI
  • 基于孤立森林的信用卡欺诈 Python 实战案例,最佳参数选择、可视化等
  • B/S 架构 与 C/S 架构
  • 【JAVAEE框架】Mybatis常用操作(CRUD)
  • 【PCB专题】如何在嘉立创8月1日起的新规则下免费打样
  • ElasticSearch--写入数据的流程(原理)
  • Java 下数据业务逻辑开发技术 JOOQ 和 SPL
  • 嵌入式系统多线程学习笔记
  • 【DaVinci Developer专题】-44-Software Component软件组件的Multiple Instantiation多次实例化
  • Docker 进阶指南(下)- 使用Docker Compose编排多个容器
  • 走进Prime Time系列 - 走进PT - 01
  • 10个最佳ES6特性 ES7与ES8的特性
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • CSS3 变换
  • Github访问慢解决办法
  • HTML-表单
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Linux CTF 逆向入门
  • Octave 入门
  • 后端_MYSQL
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 记录:CentOS7.2配置LNMP环境记录
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 实现简单的正则表达式引擎
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 做一名精致的JavaScripter 01:JavaScript简介
  • kubernetes资源对象--ingress
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • ![CDATA[ ]] 是什么东东
  • #微信小程序(布局、渲染层基础知识)
  • #微信小程序:微信小程序常见的配置传值
  • $jQuery 重写Alert样式方法
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (三)mysql_MYSQL(三)
  • (一) storm的集群安装与配置
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • .bat批处理出现中文乱码的情况
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .Net CF下精确的计时器
  • .NET Core WebAPI中封装Swagger配置
  • .net core 依赖注入的基本用发
  • .NET Core跨平台微服务学习资源
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .Net6使用WebSocket与前端进行通信