概要
实验名称:基于状态机的按键扫描器
实验环境:IAP15F2K61S2国信长天实验板
实验配置:J13跳线配置为I/O模式,J5配置为BTN模式,J2配置为1-1,2-4模式
实验内容:通过8个LED代表8个二进制位,R45为最高位,R32 为最低位,二进制数为按键RX的编号X(十进制)转化二进制的体现
备注:实验过程中蜂鸣器应处于关闭状态
技术实现
1.硬件环境
IAP15F2K61S2单片机部分
74HC138 译码器
74HC573锁存器
ULN2003
2.原理图 



3.代码实现
#include <STC15F2K60S2.h>
#include <intrins.h>
/*宏函数*/
#define LED(X) {P0=X;P2=((P2&0X1F)|0X80);P2 &= 0X1F;}
#define BUZ(X) {P0=X;P2=((P2&0X1F)|0XAF);P2 &= 0X1F;}
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
/*按键状态*/
typedef enum {
WAIT_FOR_PRESS,
KEY_PRESSED,
KEY_RELEASED,
LONG_PRESS
} Status;
int key_press_time = 0; /*注意此处的数据类型*/ /*key_press_time用来按键长按计时*/
Status key_status = WAIT_FOR_PRESS; /*按键状态变量*/
unsigned char key_value = 0x00; /*按键数值变量*/
unsigned char key_routine = 0; /*定时器辅助计数器*/
unsigned char key_mid = 0x00;
bit key_flag = 0; /*按键扫描标志位*/
bit key_long = 0; /*长按标志位*/
/*状态机按键扫描*/
void SCAN_KEY_BTN(void)
{
switch(key_status)
{
case WAIT_FOR_PRESS:
key_long = 0; /*按键进入待检测状态,切换为短按*/
if((S7==0) || (S6==0) || (S5==0) || (S4==0))
key_status = KEY_PRESSED;
break;
case KEY_PRESSED:
if(S7==0){
key_status = KEY_RELEASED;
key_value = 7;
key_mid = 7;
}else if(S6==0){
key_status = KEY_RELEASED;
key_value = 6;
key_mid = 6;
}else if(S5==0){
key_status = KEY_RELEASED;
key_value = 5;
key_mid = 5;
}else if(S4==0){
key_status = KEY_RELEASED;
key_value = 4;
key_mid = 4;
}else{
key_status = WAIT_FOR_PRESS;
}
break;
case KEY_RELEASED:
if(S7 && S6 && S5 && S4){ /*按键按下后释放*/
key_status = WAIT_FOR_PRESS;
key_value = key_mid;
}else{
if(key_press_time > 1000){ /*按键未释放持续时间大于1s*/
key_status = LONG_PRESS; /*进入长按状态*/
key_press_time = 0; //长按计数器清零
}
}
break;
case LONG_PRESS:
if(S7&& S6 && S5 && S4){ //按键释放后进入待检测状态
key_status = WAIT_FOR_PRESS;
}else
key_long = 1; //设置长按标志
break;
default :
break;
}
}
/*定时器0初始化*/
void Timer0_Init(void) //1毫秒@11.0592MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初始值
TH0 = 0xD4; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
}
/*定时器中断*/
void T0timer_ROUTINE(void) interrupt 1
{
key_routine++;
if(!(S7 && S6 && S5 && S4) && (key_status != LONG_PRESS)){ /*按键按下且未进入长按状态就开始计时*/
key_press_time++;
}
else{ /*按键释放就将计数器清零*/
key_press_time = 0;
}
if(key_routine == 10) /*每10ms执行一次按键扫描*/
{
key_routine = 0;
key_flag = 1;
}
}
/*主函数*/
void main(void)
{
BUZ(0X00);
LED(0XFF);
Timer0_Init();
while(1)
{
LED(key_long ? 0x00 : ~key_value);
/*
此处用长按标志位来决定LED显示,
达到长按全部点亮LED,释放后回归普通点亮效果
*/
if(key_flag)
{
SCAN_KEY_BTN();
key_flag = 0;
}
}
}
4.内容要点
1.状态机
状态机编程是一种将系统行为分解为有限状态的设计模式,通过定义状态、事件、转换条件和动作来管理程序流程。每个状态封装特定行为,事件触发状态间转换,使复杂逻辑清晰可控,常用于处理异步事件、协议解析或UI交互等场景,具有结构清晰、易维护、可扩展性强的特点。
WAIT_FOR_PRESS为状态机初始状态,按键状态机处于待检测状态,等待按键按下,key_long为按键长按标志位,默认状态下为0,即短按触发。当检测到按键按下,发生状态转换,状态机将转换至KEY_PRESSED状态,获取按键的值.
case WAIT_FOR_PRESS:
key_long = 0; /*按键进入待检测状态,切换为短按*/
if((S7==0) || (S6==0) || (S5==0) || (S4==0))
key_status = KEY_PRESSED;
break;
KEY_PRESSED为状态机检测到按键按下后进入的状态,在该状态下确定是哪一个按键被按下,然后发生状态转换,将状态机的状态更新至KEY_RELEASED状态,获取按键的值,由主副数值存储变量存储。若无按键按下,则将状态机的状态更新至WAIT)FOR_PRESS状态,状态机进入待检测状态。
case KEY_PRESSED:
if(S7==0){
key_status = KEY_RELEASED;
key_value = 7;
key_mid = 7;
}else if(S6==0){
key_status = KEY_RELEASED;
key_value = 6;
key_mid = 6;
}else if(S5==0){
key_status = KEY_RELEASED;
key_value = 5;
key_mid = 5;
}else if(S4==0){
key_status = KEY_RELEASED;
key_value = 4;
key_mid = 4;
}else{
key_status = WAIT_FOR_PRESS;
}
break;
KEY_RELEASED状态为状态机检测按键是否释放的状态,状态机将检测按键的释放情况,若按键释放,则将状态机更新至待检测状态WAIT_FOR_PRESS,主数值存储变量与副数值存储变量保持一致。否则判断按键的保持时间是否满足条件,保持时间达到1s,则认为按键为长按。将状态机的状态更新至长按状态LONG_PRESS。按键保持按下的时间通过变量key_press_time来计时。
case KEY_RELEASED:
if(S7 && S6 && S5 && S4){ /*按键按下后释放*/
key_status = WAIT_FOR_PRESS;
key_value = key_mid;
}else{
if(key_press_time > 1000){ /*按键未释放持续时间大于1s*/
key_status = LONG_PRESS; /*进入长按状态*/
key_press_time = 0; //长按计数器清零
}
}
break;
定时器0中断中,每1ms进行一次中断,检测是否有按键按下并且状态机的状态不是LONG_PRESS状态,按键计时加一,否则按键计时清零
/*定时器中断*/
void T0timer_ROUTINE(void) interrupt 1
{
key_routine++;
if(!(S7 && S6 && S5 && S4) && (key_status != LONG_PRESS)){ /*按键按下且未进入长按状态就开始计时*/
key_press_time++;
}
else{ /*按键释放就将计数器清零*/
key_press_time = 0;
}
if(key_routine == 10) /*每10ms执行一次按键扫描*/
{
key_routine = 0;
key_flag = 1;
}
}
LONG_PRESS状态机,检测按键是否释放,未释放的长按状态下将长按标志位置1,释放后将状态机更新至WAIT_FOR_PRESS待检测状态。
case LONG_PRESS:
if(S7&& S6 && S5 && S4){ //按键释放后进入待检测状态
key_status = WAIT_FOR_PRESS;
}else
key_long = 1; //设置长按标志
break;