之前学过机械按键,可参考:
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了。
先配置继电器(这里是因为板上有四个触摸按键,但只有3个LED灯,所以就借用这里的D6来作为一个LED使用,也能感受下直接驱动和使用中断驱动的区别):
配置接继电器的PG13引脚:
配置PE0~PE3为外部中断(下降沿触发):
初始化对应中断:
关于MX中断的配置,有必要说明下:
stm32的优先级配置
STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作“亚优先级”或“副优先级”或“从优先级”,每个中断源都需要被指定这两种优先级。
- 高抢占优先级的中断可以打断低抢占优先级的中断
- 相同抢占优先级,高响应优先级无法打断低响应优先级的中断
- 相同抢占优先级,两个中断同时触发时,优先执行高响应优先级的中断
- 若所有优先级都相同,谁先触发执行对应中断
- 优先级数字越低代表优先级越高
抢占式优先级和响应优先级通过中断优先级组进行分配
中断优先级组在stm32中一般可分为0-5组,分组配置在寄存器SCB->AIRCR中:
- 组0就是4位都用来设置成响应优先级,2^4=16位都是响应优先级
- 组1分为2^1两个抢占优先级,在这两个抢占优先级里面还分别有2^3八个响应优先级
- 组2分为2^2四个抢占优先级,在这四个抢占优先级里面还分别有2^2四个响应优先级
- 组3分为2^3八个抢占优先级,在这八个抢占优先级里面还分别有2^1两个响应优先级
- 组4分为2^4十六个都是抢占优先级
在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(……);
时间关系,此处暂时不写了,后面有空再补充吧。