基于状态机的按键扫描实验
基于正点原子精英板(stm32zet6),实验一共实现三个按键的短按和长按,其中一个按键
配置为下拉模式,另外两个按键为上拉模式,原理图见下
视频可以去B站搜郭天祥老师
一、原理图
二、状态机逻辑
三、代码实现
systick.c用于获取系统时钟
#ifndef __SYSTICK_H
#define __SYSTICK_H
#include "sys.h"
void SystickInit(void);
void SysTick_Handler(void);
uint64_t GetSysRunTime(void);
#endif
#include "systick.h"
#include <stdint.h>
#include "core_cm3.h"
static uint64_t g_sysRunTime=0;
/**
* @brief systick初始化
* @param
* @retval
*/
void SystickInit(void)
{
/*系统嘀嗒校准值固定为 9000,当系统嘀嗒时钟设定为 9MHz ( HCLK/8 的最大值),产生 1ms 时间基准。*/
/*1ms产生一次中断*/
if(SysTick_Config(SystemCoreClock/8/1000))// 系统时钟8分频产生1秒的时基,在除以1000产生1毫秒时基
{
while(1);
}
}
/**
* @brief 定时中断服务函数,1ms产生一次中断
* @param
* @retval
*/
void SysTick_Handler(void)
{
g_sysRunTime++;
}
/**
* @brief 获取系统运行时间
* @param
* @retval 已1ms为单位
*/
uint64_t GetSysRunTime(void)
{
return g_sysRunTime;
}
key.c实现按键扫描
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY0 0
#define KEY1 1
#define KEY_UP 2
//长按短按的码值
#define KEY0_SHORT_PRESS 0x01
#define KEY0_LONG_PRESS 0x81
#define KEY1_SHORT_PRESS 0x02
#define KEY1_LONG_PRESS 0x82
#define KEY_UP_SHORT_PRESS 0x03
#define KEY_UP_LONG_PRESS 0x83
void KeyInit(void);
uint8_t GetKeyVal(void);
#endif
#include "key.h"
#include "systick.h"
/**
* @brief 按键硬件初始化
* @param
* @retval
*/
void KeyInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
#define CONFIRM_TIME 10 //按键消抖时间窗10ms
#define LONGPRESS_TIME 1000 //长按时间窗1S
typedef enum
{
KEY_RELEASE=0, //释放松开
KEY_CONFIRM, //消抖确认
KEY_SHORTPRESS, //短按
KEY_LONGPRESS //长按
}KEY_STATE;
typedef struct
{
KEY_STATE keyState;
uint64_t prvSysTime;
}Key_Info_t;
static Key_Info_t g_keyInfo[3];
/**
* @brief 按键扫描函数
* @param
* @retval 返回码值
*/
static uint8_t KeyScan(uint8_t keyIndex)
{
uint8_t keypress;
uint64_t curSysTime;
if(keyIndex==KEY0)
keypress=GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4);
else
if(keyIndex==KEY1)
keypress=GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3);
else
keypress=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
switch(g_keyInfo[keyIndex].keyState)
{
case KEY_RELEASE ://按键释放状态
if(keyIndex==KEY_UP)
{
if(keypress)
{
g_keyInfo[keyIndex].keyState=KEY_CONFIRM;
g_keyInfo[keyIndex].prvSysTime=GetSysRunTime();
}
}
else
{
if(!keypress)
{
g_keyInfo[keyIndex].keyState=KEY_CONFIRM;
g_keyInfo[keyIndex].prvSysTime=GetSysRunTime();
}
}
break;
case KEY_CONFIRM ://按键按下确认状态
if(keyIndex==KEY_UP)
{
if(keypress)
{
curSysTime=GetSysRunTime();
if(curSysTime-g_keyInfo[keyIndex].prvSysTime > CONFIRM_TIME)
{
g_keyInfo[keyIndex].keyState=KEY_SHORTPRESS;
}
}
else
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
}
}
else
{
if(!keypress)
{
curSysTime=GetSysRunTime();
if(curSysTime-g_keyInfo[keyIndex].prvSysTime > CONFIRM_TIME)
{
g_keyInfo[keyIndex].keyState=KEY_SHORTPRESS;
}
}
else
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
}
}
break;
case KEY_SHORTPRESS ://按键短按状态
if(keyIndex==KEY_UP)
{
if(!keypress)
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
return (keyIndex+1); //返回按键码值
}
else
{
curSysTime=GetSysRunTime();
if(curSysTime-g_keyInfo[keyIndex].prvSysTime > LONGPRESS_TIME)
{
g_keyInfo[keyIndex].keyState=KEY_LONGPRESS;
}
}
}
else
{
if(keypress)
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
return (keyIndex+1); //返回短按按键码值
}
else
{
curSysTime=GetSysRunTime();
if(curSysTime-g_keyInfo[keyIndex].prvSysTime > LONGPRESS_TIME)
{
g_keyInfo[keyIndex].keyState=KEY_LONGPRESS;
}
}
}
break;
case KEY_LONGPRESS ://按键长按状态
if(keyIndex==KEY_UP)
{
if(!keypress)
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
return (0x83); //返回长按按键码值
}
}
else
{
if(keypress)
{
g_keyInfo[keyIndex].keyState=KEY_RELEASE;
return (keyIndex+0x81); //返回长按按键码值
}
}
break;
default :
g_keyInfo[keyIndex].keyState=KEY_RELEASE;break;
}
return 0;
}
/**
* @brief 获取按键码值
* @param
* @retval 三个按键码值,短按0x01,0x02,0x03,长按0x81,0x82,0x83,没有按下为0
*/
uint8_t GetKeyVal(void)
{
uint8_t res=0,i;
for(i=0;i<3;i++)
{
res=KeyScan(i);
if(res != 0)
{
break;
}
}
return res;
}
main.c验证实验现象
别忘了滴答时钟初始化
SystickInit()
#include "sys.h"
#include "led.h"
#include "key.h"
#include "systick.h"
int main(void)
{
uint8_t keyval;
SystickInit(); //别忘了滴答时钟初始化
LED_Init(); //初始化与LED连接的硬件接口
KeyInit();
while(1)
{
keyval=GetKeyVal();
switch(keyval)
{
case KEY_UP_SHORT_PRESS : LED0=0;break; //led亮
case KEY_UP_LONG_PRESS : LED0=1; break;
case KEY1_SHORT_PRESS : LED1=0;break;
case KEY1_LONG_PRESS : LED1=1; break;
}
}
}