目录
一、按键原理简介
二、cubeMX的配置
三、按键的短按代码
四、按键的长按代码
一、按键原理简介
在STM32中,按键连接通常使用GPIO(通用输入/输出)端口来实现。当按键未被按下时,GPIO端口处于高电平状态(即1),当按键被按下时,GPIO端口会被拉低(即0)。因此,通过检测GPIO端口的电平状态变化,可以检测到按键是否被按下。
为了防止按键抖动,通常需要使用软件消抖。消抖的方法通常是在检测到按键被按下时,等待一段时间,并再次检测GPIO端口的状态,只有当GPIO端口仍然处于低电平状态时,才认为按键被有效触发。同时,还可以通过使用外部上拉电阻或下拉电阻,以确保GPIO端口在未连接按键时处于稳定状态。上拉电阻将GPIO端口拉高,下拉电阻将GPIO端口拉低,这样可以避免未连接按键时的漂浮状态。
总结来说,STM32按键的工作原理是通过检测GPIO端口的电平状态变化来判断按键是否被按下,并通过软件消抖和外部上拉/下拉电阻来确保按键的稳定性。
二、cubeMX的配置
在cubeMX中,我们除了需要配置四个按键引脚的模式外,还需要配置定时器相关的参数等。我们用定时器来实现按键消抖,即通过定时器每过10ms检测一次按键引脚的电平。具体配置操作如下:
1、打开cubeMX软件,将开发板上的四个按键对应的引脚设置为输入模式,即将PA0、PB0、PB1、PB2设置为GPIO_Input
2、点击左边的GPIO,选中PA0、PB0、PB1、PB2四个GPIO口,并如图设置为上拉模式,即按下时GPIO口为低电平(0)
3、点击Timers,选择一个定时器,我选择的是通用定时器TIM3,如图设置时钟源为内部时钟。再设置定时器的预分频器值和计数器重载值,由于我们设置的定时器时钟频率为80MHz,通过定时器的计算公式,当我们想要定时10ms时,我们需要将预分频器值设置为80-1,数器重载值设置为10000-1.公式如下:
定时时间 = (预分频器值计数器重载值)/ 定时器时钟频率
三、按键的短按代码
interrupt.h
代码后已经注释了代码的大致含义
// interrupt.h
#ifndef _INTERRUPT_H
#define _INTERRUPT_H
#include "main.h" // 在main.h中宏定义uchar、uint
struct keys // 定义一个结构体,设置三个状态变量
{
uchar judge_sta; // 设置标志位,反应定时器中断服务函数进行到哪一步
uchar key_sta; // 检测按键引脚的电平并保存到key_sta,当按键按下时key_sta为0
uchar flag; // 当按键真正按下后,让flag = 1
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 中断服务函数
#endif
interrupt.c
这里主要就是编写定时器的中断服务函数,首先读取四个按键引脚的电平,通过定时器每过10ms检测一次电平,当第一次检测为低电平时,等待10ms后再检测一次,若仍为低电平,则视为按键真的按下,即令flag = 1.
// interrupt.c
#include "interrupt.h"
struct keys key[4] = {0, 0, 0, 0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3) // 使用定时器TIM3
{
// 读取四个按键引脚的电平
key[0].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key[1].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key[2].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key[3].key_sta = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
}
for(int i=0; i<4; i++) // 循环扫描四个按键的状态
{
switch (key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta == 0)
{
key[i].judge_sta = 1;
}
}
break;
case 1:
{
if(key[i].key_sta == 0)
{
key[i].flag = 1;
key[i].judge_sta = 2;
}
else key[i].judge_sta = 0;
}
break;
case 2:
{
if(key[i].key_sta == 1)
{
key[i].judge_sta = 0;
}
}
break;
}
}
}
main.c
主函数中我只展示与按键相关的代码,首先需要声明一个全局变量extern struct keys key[]来方便后面使用key[i].flag进行判断
然后需要打开定时器中断服务函数,使用HAL_TIM_Base_Start_IT(&htim3);
最后在while循环中用key[i].flag判断按键是否真的按下,当按键真的按下时执行一系列指令
// main.c
#include "main.h"
#include "tim.h"
#include "gpio.h"
#include "led.h"
#include "interrupt.h"
extern struct keys key[];
HAL_TIM_Base_Start_IT(&htim3);
while (1)
{
if(key[0].flag == 1)
{
LED(0x01);
key[0].flag = 0;
}
if(key[1].flag == 1)
{
LED(0x00);
key[1].flag = 0;
}
}
四、按键的长按代码
interrupt.h
与短按相比,长按的头文件只是在结构体中多定义两个变量:key_time和long_flag
key_time用来判断按键按下的时间长短
long_flag用来判断按键是否长按
// interrupt.h
#ifndef _INTERRUPT_H
#define _INTERRUPT_H
#include "main.h"
struct keys
{
uchar judge_sta;
uchar key_sta;
uchar flag;
uint key_time;
uchar long_flag;
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
#endif
interrupt.c
与短按类似,只是当第一次判断按键按下后,key[i].time开始自加,直到按键松开后判断key[i].time的时间,大于70ms视为长按,小于70ms视为短按
// interrupt.c
#include "interrupt.h"
struct keys key[4] = {0, 0, 0, 0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
key[0].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key[1].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key[2].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key[3].key_sta = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
}
for(int i=0; i<4; i++)
{
switch (key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta == 0)
{
key[i].judge_sta = 1;
key[i].key_time = 0;
}
}
break;
case 1:
{
if(key[i].key_sta == 0)
{
key[i].judge_sta = 2;
}
else
{
key[i].judge_sta = 0;
}
}
break;
case 2:
{
if(key[i].key_sta == 1)
{
key[i].judge_sta = 0;
if(key[i].key_time < 70)
key[i].flag = 1;
}
else key[i].key_time++;
if(key[i].key_time > 70) key[i].long_flag = 1;
}
break;
}
}
}
main.c
主函数除了while循环的判断之外,都与短按类似
while (1)
{
if(key[0].flag == 1)
{
LED(0x00);
key[0].flag = 0;
}
if(key[1].long_flag == 1)
{
LED(0x01);
key[1].long_flag = 0;
}
}