STM32学习7 按键扫描
- 一、实验电路介绍
- 二、按键GPIO初始化
- 三、扫描原理
- 1. GPIO引脚配置
- 2. 状态轮询
- 3. 按键状态检测
- 4. 循环扫描的优缺点
- 优点:
- 缺点:
- 四、一次扫描与持续扫描
- 五、代码实现
- 1. 头文件定义
- 2. 函数实现
- 3. 主体函数
一、实验电路介绍
本实验使用普中STM32-F1开发板,芯片型号是STM32F103ZET6。
其按键电路如下:
对应的芯片引脚:
从电路可以看出,键盘的 KEY_UP 键如果接通,会连接高电平 。
其它几个按键在按下的时候连接低电平,对应的GPIO口:
- KEY_UP:GPIOA GPIO_Pin0 引脚
- KEY_LEFT:GPIOE GPIO_Pin2 引脚
- KEY_RIGHT:GPIOE_GPIO_Pin4 引脚
- KEY_DOWN:GPIOE_GPIO_Pin3 引脚
二、按键GPIO初始化
按键 KEY_UP 和其它三个按键的接法不同,需要不同的配置方式。
其中 KEY_UP 按下后接高电平,在默认情况下需要置低,初始化时设置为输入下拉,代码如下:
// 开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 设置上引脚
GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN;
// 设置输入下拉模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(KEY_UP_PORT, &GPIO_InitStructure); // 初始化GPIOA
其它三个按键,按下时接低电平,默认置高,初始化设置为输入上拉,代码如下:
// 开 E 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
// 设置下、左、右引脚
GPIO_InitStructure.GPIO_Pin = KEY_DOWN_PIN | KEY_LEFT_PIN | KEY_RIGHT_PIN;
// 设置输入上拉模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(KEY_DOWN_PORT, &GPIO_InitStructure); // 初始化GPIOE
三、扫描原理
1. GPIO引脚配置
首先,需要将用于连接按键的GPIO引脚配置为输入模式。
2. 状态轮询
然后轮询每个按键的状态,以确定按键是否被按下或释放。轮询扫描可以通过在主循环中定期检查每个按键的状态来实现。例如,在每次主循环迭代中,都检查一次按键的状态。
3. 按键状态检测
一般来说,按键有两种状态:按下和释放。在检测按键状态时,需要注意去除按键的抖动干扰。抖动是指在按键被按下或释放时,由于机械接触或物理特性导致的瞬间状态变化。为了应对抖动,可以采用软件方法或硬件滤波器。
本示例采用延时10ms读取值的方法来去抖,示例:
if(key_up_value == 1 || key_down_value ==0 || key_left_value ==0 || key_right_value ==0){
delay_ms(10);
}
硬件方法去抖可以参考实现:SR触发器去抖
4. 循环扫描的优缺点
优点:
-
简单直观: 在循环中进行按键扫描的方法简单易懂,逻辑清晰,易于理解和实现。
-
灵活性: 可以根据具体需求灵活调整扫描的频率和方式,满足不同场景下的要求。
-
适用性广: 适用于小型嵌入式系统或者对按键响应速度要求不高的场景,适用性广泛。
-
资源消耗低: 相比于中断方式,循环扫描不需要额外的中断处理函数,减少了系统资源的占用。
缺点:
-
效率低下: 在循环中进行按键扫描会占用 CPU 的时间片,降低了系统的处理效率,特别是当系统有其他紧急任务需要处理时,会影响响应速度和实时性。
-
实时性差: 循环扫描需要不断地遍历所有按键状态,导致按键的检测周期相对较长,实时性差,无法满足对按键响应速度要求较高的场景。
-
占用 CPU 资源: 循环扫描需要持续占用 CPU 资源,特别是在大型系统中,可能会影响其他任务的执行,降低系统的整体性能。
-
功耗高: 循环扫描需要 CPU 不断地处于工作状态,会增加系统的功耗,对于对功耗要求较高的场景不太适用。
后面学习中会采用中断的方式来读取键盘。
四、一次扫描与持续扫描
这里的一次扫描,是指按下按键后,如果不松开,键盘的扫描函数不会继续输出所按键值。
而持续扫描,在按下按键后,如果手不松开,键盘的扫描函数仍会持续输出按键值。
五、代码实现
为方便看到演示效果,示例的代码在获取到扫描的按键后,会在数码管显示不同的数值。
- 上:显示0
- 下:显示1
- 左:显示2
- 右:显示3
1. 头文件定义
key_utils.h
#ifndef __KEY_UTILS_H__
#define __KEY_UTILS_H__
#include "stm32f10x.h"
// 引脚和端口
#define KEY_UP_PIN GPIO_Pin_0
#define KEY_UP_PORT GPIOA
#define KEY_LEFT_PIN GPIO_Pin_2
#define KEY_LEFT_PORT GPIOE
#define KEY_DOWN_PIN GPIO_Pin_3
#define KEY_DOWN_PORT GPIOE
#define KEY_RIGHT_PIN GPIO_Pin_4
#define KEY_RIGHT_PORT GPIOE
// 读取引脚状态
#define key_up_value GPIO_ReadInputDataBit(KEY_UP_PORT, KEY_UP_PIN)
#define key_down_value GPIO_ReadInputDataBit(KEY_DOWN_PORT, KEY_DOWN_PIN)
#define key_left_value GPIO_ReadInputDataBit(KEY_LEFT_PORT, KEY_LEFT_PIN)
#define key_right_value GPIO_ReadInputDataBit(KEY_RIGHT_PORT, KEY_RIGHT_PIN)
// 按键
#define KEY_UP 0
#define KEY_DOWN 1
#define KEY_LEFT 2
#define KEY_RIGHT 3
#define KEY_NONE 4
void key_init(void);
u8 key_scan(u8 mode);
#endif
2. 函数实现
#include "key_utils.h"
#include "sys_tick_utils.h"
void key_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO初始化结构体
// 开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 设置上引脚
GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN;
// 设置输入下拉模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(KEY_UP_PORT, &GPIO_InitStructure); // 初始化GPIOA
// 开 E 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
// 设置下、左、右引脚
GPIO_InitStructure.GPIO_Pin = KEY_DOWN_PIN | KEY_LEFT_PIN | KEY_RIGHT_PIN;
// 设置输入上拉模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(KEY_DOWN_PORT, &GPIO_InitStructure); // 初始化GPIOE
}
static u8 key_read(void){
if(key_up_value == 1 || key_down_value ==0 || key_left_value ==0 || key_right_value ==0){
delay_ms(10);
if(key_up_value == 1){
return KEY_UP;
}else if(key_down_value == 0){
return KEY_DOWN;
}else if(key_left_value == 0){
return KEY_LEFT;
}else if(key_right_value == 0){
return KEY_RIGHT;
}
}
return KEY_NONE;
}
u8 last_key;
/**
* @brief 按键扫描函数
* @param mode: 0 单次扫描 1: 连续扫描
*/
u8 key_scan(u8 mode)
{
if(mode==0){
u8 key = key_read();
if(key != KEY_NONE){
if(key == last_key){
return KEY_NONE;
}else{
last_key = key;
return key;
}
}else{
last_key = KEY_NONE;
}
}else{
return key_read();
}
return KEY_NONE;
}
3. 主体函数
#include "gpio_utils.h"
#include "rcc_utils.h"
#include "stm32f10x.h"
#include "sys_tick_utils.h"
#include "led_utils.h"
#include "key_utils.h"
// 主函数
int main(void)
{
GPIO_Configuration(); //调用GPIO配置函数
sys_tick_init(72);
led_all_off();
key_init();
while (1) //无限循环
{
delay_ms(10);
u8 key = key_scan(0);
if(key==KEY_UP){
led_lightn(0);
}else if(key==KEY_DOWN){
led_lightn(1);
}else if(key==KEY_LEFT){
led_lightn(2);
}else if(key==KEY_RIGHT){
led_lightn(3);
}else{
led_all_off();
}
}
}
本文源码地址:
https://gitee.com/xundh/stm32_arm_learn/tree/master/lesson7_key