使用CubeMX配置输入引脚
本章我们要把按键作为输入源,使用单片机来检测引脚的电平状态。首先要查看原理图,按键与那些引脚相连。
我使用的板子,按键K2 -K5分别对应PA4-PA7,且按键按下去以后,引脚接地。因此,我们要将单片机的PA4-PA7设置为上拉输入。
点击生成代码并打开工程,可以看到STM32CubeMX配置好的引脚输入初始化代码如下(已省略部分无关代码):
//main.c
static void MX_GPIO_Init(void)
{
/*Configure GPIO pins : PA4 PA5 PA6 PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
按键的输入、消抖与松手检测
按键检测可以调用库函数HAL_GPIO_ReadPin,也可以使用位带操作PAin(n)。其实,只需要一步跳转,就可以发现HAL_GPIO_ReadPin其实也是在调用IDR寄存器而已。
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIO_PinState bitstatus;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
if((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
{
bitstatus = GPIO_PIN_SET;
}
else
{
bitstatus = GPIO_PIN_RESET;
}
return bitstatus;
}
为了提高程序的可读性,使用宏定义将按键与引脚关联起来。注意,代码只允许填写在USER CODE包含的区域,否则使用STM32CubeMX时,会删除区域外的代码。
//main.h
/* USER CODE BEGIN EM */
#define LED1 PCout(10)
#define LED2 PCout(11)
#define KEY_D HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)
#define KEY_C HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)
#define KEY_B HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)
#define KEY_A HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)
/* USER CODE END EM */
在主函数的死循环中,可以编写代码来获取引脚的电平状态,并且用LED作为状态指示。注意要带松手检测。
//main.c main()
while (1)
{
if(0 == KEY_A)
{
HAL_Delay(10);
while(!KEY_A)
;
LED1 = !LED1;
}
if(0 == KEY_B)
{
HAL_Delay(10);
while(!KEY_B)
;
LED2 = !LED2;
}
}
新增c文件与按键扫描函数
我们接下来尝试编写一个按键扫描函数。然而,如果新增的函数都放在main.c,那么main.c会变得很臃肿,既不方便代码阅读,又不方便理清程序的分层与架构。一般情况下,不同的函数要放在不同的c文件中。
接下来按照CubeMX生成的工程的架构,新建IO.c与IO.h文件。
将IO.c文件添加到工程中。
头文件无需手动添加,只需在c文件中包含即可。我参照了正点原子的按键扫描函数,把按键扫描函数剪贴到IO.c中,并添加一些函数说明信息。
//IO.c
#include "IO.h"
/**
* @brief 按键扫描函数
* @param 模式,是否支持连按(长按)
* @retval 按下的键值
*/
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY_A==0||KEY_B==0||KEY_C==0||KEY_D==0))
{
HAL_Delay(10);//去抖动
key_up=0;
if(KEY_A==0)return KEY_A_PRES;
else if(KEY_B==0)return KEY_B_PRES;
else if(KEY_C==0)return KEY_C_PRES;
else if(KEY_D==0)return KEY_D_PRES;
}else if(KEY_A==1&&KEY_B==1&&KEY_C==1&&KEY_D==1)key_up=1;
return 0;// 无按键按下
}
在IO.h中定义一些IO操作的宏定义
#ifndef __IO_H
#define __IO_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#define LED1 PCout(10)
#define LED2 PCout(11)
#define KEY_D HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)
#define KEY_C HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)
#define KEY_B HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)
#define KEY_A HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)
#define KEY_A_PRES 5
#define KEY_B_PRES 4
#define KEY_C_PRES 3
#define KEY_D_PRES 2
u8 KEY_Scan(u8 mode);
#ifdef __cplusplus
}
#endif
#endif
在主函数死循环中,可以使用switch-case语法,把函数的返回值作为判断条件。
//main.c main()
while (1)
{
switch(KEY_Scan(0))
{
case KEY_A_PRES: LED1 = !LED1; break;
case KEY_B_PRES: LED2 = !LED2; break;
case KEY_C_PRES: LED1 = !LED2; break;
case KEY_D_PRES: LED2 = !LED1; break;
default: break;
}
}