前言
使用STM32CubeIDE实现按键实验。
硬件:STM32F103C8T6最小系统板 + 自制扩展板
软件:STM32CubeIDE
一、实验目的
实现扩展板上的KEY1控制LED1翻转,KEY2控制LED2翻转,KEY3控制蜂鸣器响停,KEY4控制跑马灯的开关。
二、学习内容
- 学习GPIO的输入使用
- 学习按键(KEY)控制
三、实践操作
1.硬件介绍
硬件:STM32F103C8T6最小系统板 + 自制扩展板
自制扩展板中有一个蜂鸣器、LED灯与按键,LED1为蓝灯,LED2为橙灯;
蜂鸣器的IO为PA15,LED1的IO为PB3,LED2的IO为PB4,KEY1的IO为PB0,KEY2的IO为PB1,KEY3的IO为PB8,KEY4的IO为PB9;
蜂鸣器为高电平有效,LED1为低电平有效,KEY1~4均为检测低电平;
无外部上拉/下拉电阻(最好在硬件上进行上下拉操作),需在软件中进行弱上拉/下拉。
原理图如下图所示(仅介绍按键,其他外设在上一篇文章中有介绍):
由上图可以看出,当按键按下时,IO口读取到低电平;上图中的C1~4为滤波电容,滤除机械开关噪声,避免一次按键被误读多次(防抖)。
实物图如下图所示:
2.软件介绍
首先新建工程并进行初始化配置:
在此不进行赘述,详细步骤可移步至之前文章:2.STM32CubeIDE跑马灯实验
与上一篇文章不同的是,需要配置PB0、PB1、PB8、PB9,如下图所示:
由上图可以看到,需要将按键IO配置为输入模式;配置弱上拉;进行引脚重命名(用户标签)。
接下来进行按键实验的代码编程
- 为了使代码更加简洁,同样采用新建文件夹的方式存放key.h与key.c文件,并且用宏定义的方式读取按键的状态,详细步骤可移步至之前文章:2.STM32CubeIDE跑马灯实验。
其中key.h文件代码如下:
#ifndef BSP_KEY_KEY_H_
#define BSP_KEY_KEY_H_
#include "main.h"
#define Read_KEY1 HAL_GPIO_ReadPin(GPIOB,KEY1_Pin) /* 读取KEY1引脚 */
#define Read_KEY2 HAL_GPIO_ReadPin(GPIOB,KEY2_Pin) /* 读取KEY2引脚 */
#define Read_KEY3 HAL_GPIO_ReadPin(GPIOB,KEY3_Pin) /* 读取KEY3引脚 */
#define Read_KEY4 HAL_GPIO_ReadPin(GPIOB,KEY4_Pin) /* 读取KEY4引脚 */
#define KEY1_PRES 1 /* KEY1按下 */
#define KEY2_PRES 2 /* KEY2按下 */
#define KEY3_PRES 3 /* KEY3按下 */
#define KEY4_PRES 4 /* KEY4按下 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif /* BSP_KEY_KEY_H_ */
为了使代码进一步简洁,用宏定义的方式实现LED与蜂鸣器的状态翻转。
led.h中新增代码:
/* led.h */
#define LED1_TOGGLE() HAL_GPIO_TogglePin(GPIOB, LED1_Pin)
#define LED2_TOGGLE() HAL_GPIO_TogglePin(GPIOB, LED2_Pin)
beep.h中新增代码:
/* beep.h */
#define BEEP_TOGGLE() HAL_GPIO_TogglePin(GPIOA, Buzzer_Pin)
其中HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
为HAL库的翻转IO状态的函数,第一个参数为IO的组(例如GPIOA),第二个参数为IO的引脚(例如GPIO_PIN_0);用户对引脚重命名后,可在main.c中查看具体的GPIO相关的宏定义,例如:
/* main.h */
#define KEY1_Pin GPIO_PIN_0
#define KEY1_GPIO_Port GPIOB
#define KEY2_Pin GPIO_PIN_1
#define KEY2_GPIO_Port GPIOB
#define Buzzer_Pin GPIO_PIN_15
#define Buzzer_GPIO_Port GPIOA
#define LED1_Pin GPIO_PIN_3
#define LED1_GPIO_Port GPIOB
#define LED2_Pin GPIO_PIN_4
#define LED2_GPIO_Port GPIOB
#define KEY3_Pin GPIO_PIN_8
#define KEY3_GPIO_Port GPIOB
#define KEY4_Pin GPIO_PIN_9
#define KEY4_GPIO_Port GPIOB
- 编写跑马灯函数
之前实现跑马灯是在main.c中完成的,我们可以将其封装为一个函数,之后只需要在main.c中调用即可,函数定义需要写在.c文件中,函数声明需要写在.h文件中。
/* led.h */
void LED_Marquee();
/* led.c */
void LED_Marquee()
{
LED1(0);
LED2(1);
HAL_Delay(500);
LED1(1);
LED2(0);
HAL_Delay(500);
}
- 编写按键程序
轻触按键在按下时,会产生机械抖动,我们在硬件上可以通过增加滤波电容对其进行消除,也可以通过软件实现,当然两个都存在更好;
轻触按键在按下时,如果不松开,IO口会持续读取到低电平,将其称为连按状态,在该状态下,如果不松手,则会一直执行按键的功能,有些时候我们是不希望这样的;
当有多个按键存在时,可以通过函数实现对按键检测代码进行封装,通过返回不同的值来确定是哪个按键按下,从而进一步实现代码的简洁。
具体代码如下(参考正点原子按键实验代码):
/* key.h */
#ifndef BSP_KEY_KEY_H_
#define BSP_KEY_KEY_H_
#include "main.h"
#define Read_KEY1 HAL_GPIO_ReadPin(GPIOB,KEY1_Pin) /* 读取KEY1引脚 */
#define Read_KEY2 HAL_GPIO_ReadPin(GPIOB,KEY2_Pin) /* 读取KEY2引脚 */
#define Read_KEY3 HAL_GPIO_ReadPin(GPIOB,KEY3_Pin) /* 读取KEY3引脚 */
#define Read_KEY4 HAL_GPIO_ReadPin(GPIOB,KEY4_Pin) /* 读取KEY4引脚 */
#define KEY1_PRES 1 /* KEY1按下 */
#define KEY2_PRES 2 /* KEY2按下 */
#define KEY3_PRES 3 /* KEY3按下 */
#define KEY4_PRES 4 /* KEY4按下 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif /* BSP_KEY_KEY_H_ */
/* key.c */
#include "key.h"
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): KEY1 > KEY2 > KEY3 > KEY4!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY1_PRES, 1, KEY1按下
* KEY2_PRES, 2, KEY2按下
* KEY3_PRES, 3, KEY3按下
* KEY4_PRES, 4, KEY4按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && ((Read_KEY1 == 0) || (Read_KEY2 == 0) || (Read_KEY3 == 0) || (Read_KEY4 == 0) )) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
HAL_Delay(10); /* 去抖动,延时后再一次判断是否按下 */
key_up = 0; /* 按键松开标志置0 */
if (Read_KEY1 == 0) keyval = KEY1_PRES;
if (Read_KEY2 == 0) keyval = KEY2_PRES;
if (Read_KEY3 == 0) keyval = KEY3_PRES;
if (Read_KEY4 == 0) keyval = KEY4_PRES;
}
else if ((Read_KEY1 == 1) && (Read_KEY2 == 1) && (Read_KEY3 == 1) && (Read_KEY4 == 1)) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
- 编写main.c,首先需要包含key.h头文件,可采用相对路径或绝对路径,具体区别与方法移步至之前文章:2.STM32CubeIDE跑马灯实验。
接下来就是编写控制逻辑代码,根据实验目的,具体代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t key = 0;
uint8_t Marquee = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
key = key_scan(0); /* 不支持连按 */
switch (key)
{
case KEY1_PRES: /* 按下KEY1 */
LED1_TOGGLE();
break;
case KEY2_PRES: /* 按下KEY2 */
LED2_TOGGLE();
break;
case KEY3_PRES: /* 按下KEY3 */
BEEP_TOGGLE();
break;
case KEY4_PRES: /* 按下KEY4 */
Marquee = 1; /* 跑马灯标志位 */
while(Marquee)
{
LED_Marquee(); /* 跑马灯函数 */
if(key_scan(0) != (0 && KEY4_PRES)) /* 检测是否按下其他按钮 */
{
Marquee = 0; /* 按下其他按键,跑马灯标志位置0 */
}
}
default:
break;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
通过按键扫描函数返回的值来判断是哪个按键按下;通过判断是否按下其他按键来跳出跑马灯的while循环.
具体现象:按下KEY1,翻转LED1;按下KEY2,翻转LED2;按下KEY3,翻转蜂鸣器状态(响与不响);按下KEY4,实现跑马灯。
但该程序有一定瑕疵,存在于跑马灯函数中,由于在跑马灯函数中采用的是HAL_Delay(500)
的方式实现延时,因此在按下按键的时候,如果程序刚好在HAL_Delay(500)
中,则会出现无法识别到这次按键按下,就会出现按下按键但反应的情况,后续可以通过其他进行优化,拭目以待。
由此就可以实现扩展板上的KEY1控制LED1翻转,KEY2控制LED2翻转,KEY3控制蜂鸣器响停,KEY4控制跑马灯开关的效果啦!
总结
本篇介绍了如何实现一个按键实验,分别从硬件方面与软件方面阐述,在硬件方面主要介绍了硬件组成与原理图分析,在软件方面主要介绍了key.c、key.h与main.c文件的代码编写。完美完成本文开篇提到的学习任务与功能!
对于实验复现,无需限制在本文使用的IO,了解原理之后,可以做出更有趣的功能!