前面的文章基于按键功能介绍了状态机原理和按键状态机示例,实现了按键点击、双击、长按等状态的检测。
在这篇文章中,我们继续使用状态机编程来实现一个更有趣的功能——全自动洗衣机。
1 全自动洗衣机功能分析
下图是全自动洗衣机的控制面板:
面板上有4个按钮:
开始/暂停:用于开始或暂停洗衣任务
面板上还有数码管显示当前工作状态和剩余时间。 可显示的工作模式有:
在本文中,我们将根据这款洗衣机的操作方法,实现相应的洗衣机控制逻辑。 需要注意的事项是:
2 绘制状态图
根据以上分析的全自动洗衣机的功能以及我们自己使用洗衣机的经验,可以得出以下全自动洗衣机的状态图:
第一步是打开机器电源。 洗衣机可以进行自检,检查洗衣机各部件是否正常。 这个过程非常短。
然后就处于空闲状态。 此时,用户可以设置水位和洗涤模式。 如果不设置,则默认水位和洗涤模式。
然后,当触发开始按钮时,清洁开始。 一般流程为:加水、清洗、沥干、甩干、结束。
根据不同的清洗方式,加水、清洗、排水三个过程会循环一定次数。
另外,在不同的工作阶段,按暂停键暂停洗衣任务,按继续继续洗衣任务。
3 编程实现 3.1 多按键检测功能
本文介绍的洗衣机的按钮只需要检测按钮点击,不需要双击和长按功能。 因此,可以使用上一篇文章中介绍的最基本的按钮状态机来为洗衣机状态机提供按钮事件。
之前介绍的按钮状态机只有一个按钮。 本文要求使用4个按钮(不包括电源按钮,3个也可以)。 因此,需要对按钮状态机稍作修改,以实现按钮状态机的复用。
之前介绍的按钮状态机使用几个全局变量来表示状态。 更合理的做法是封装它:
typedefstruct{
KEY_STATUS keyStatus;//当前循环结束的(状态机的)状态
KEY_STATUS nowKeyStatus;//当前状态(每次循环后与pKeyFsmData->keyStatus保持一致)
KEY_STATUS lastKeyStatus;//上次状态(用于记录前一状态以区分状态的来源)
int keyIdx;
}KeyFsmData;
请注意,添加了一个额外的键索引值来告诉状态机要检测哪个键。
然后以输入参数的形式将上面定义的结构体传入到原来的按钮状态机程序中,并以函数返回的形式返回按钮是否被按下。
修改后的按钮状态机是一个独立的模块,可以通过传入不同的参数来检测不同的按钮。
bool key_press_check(KeyFsmData *pKeyFsmData)
{
bool bPress = false;
switch(pKeyFsmData->keyStatus)
{
//省略...
对于本文需要的四个按钮的检测,可以定义四个数据结构,分别调用四次状态机函数,实现代码复用。
KeyFsmData pKey0FsmData;
KeyFsmData pKey1FsmData;
KeyFsmData pKey2FsmData;
KeyFsmData pKey3FsmData;
voidkey_check_init(KeyFsmData *pKeyFsmData,int keyIdx)
{
pKeyFsmData->keyStatus = KS_RELEASE;
pKeyFsmData->lastKeyStatus = KS_RELEASE;
pKeyFsmData->nowKeyStatus = KS_RELEASE;
pKeyFsmData->keyIdx = keyIdx;
}
voidmulti_key_check_init()
{
key_check_init(&pKey0FsmData,0);
key_check_init(&pKey1FsmData,1);
key_check_init(&pKey2FsmData,2);
key_check_init(&pKey3FsmData,3);
}
int g_keyPressIdx =-1;
voidmulti_key_check()
{
bool key0 =key_press_check(&pKey0FsmData);
bool key1 =key_press_check(&pKey1FsmData);
bool key2 =key_press_check(&pKey2FsmData);
bool key3 =key_press_check(&pKey3FsmData);
if(key0 key1 key2 key3)
{
//printf("key0:%d, key1:%d, key2:%d, key3:%d, \r\n", key0, key1, key2, key3);
if(key0)
{
g_keyPressIdx =0;
}
elseif(key1)
{
g_keyPressIdx =1;
}
elseif(key2)
{
g_keyPressIdx =2;
}
elseif(key3)
{
g_keyPressIdx =3;
}
}
}
intget_press_key_idx()
{
int idx = g_keyPressIdx;
g_keyPressIdx =-1;
return idx;
}
其中,multi_key_check函数放置在定时器中断服务函数中,周期为50ms,使按键状态机程序运行。
洗衣机程序中使用get_press_key_idx函数来获取不同按键的按下事件。 每次获取后,按键事件都会被清除(g_keyPressIdx被设置为无效值-1)
3.2 洗衣功能
根据上面画的洗衣机状态图,使用switch-case方法编写相应的程序:
#defineWASHER_STATUS_ENUM(STATUS)\
STATUS(WS_INIT)/*开机初始化自检*/\
STATUS(WS_IDLE)/*空闲(等待模式设置)状态*/\
STATUS(WS_ADD_WATER)/*加水状态*/\
STATUS(WS_WASH)/*清洗状态*/\
STATUS(WS_DRAIN_WATER)/*排水状态*/\
STATUS(WS_SPIN_DRY)/*甩干状态*/\
STATUS(WS_PAUSE)/*暂停状态*/\
STATUS(WS_NUM)/*状态总数(无效状态)*/
typedefenum
{
WASHER_STATUS_ENUM(ENUM_ITEM)
}WASHER_STATUS;
constchar* washer_status_name[]={
WASHER_STATUS_ENUM(ENUM_STRING)
};
WASHER_STATUS g_washerStatus = WS_INIT;//当前循环结束的(状态机的)状态
WASHER_STATUS g_nowWasherStatus = WS_INIT;//当前状态(每次循环后与pKeyFsmData->keyStatus保持一致)
WASHER_STATUS g_lastWasherStatus = WS_INIT;//上次状态(用于记录前一状态以区分状态的来源)
int g_WorkLoopCnt =0;
int g_WaterLevel =5;//1~8
int g_WashMode =2;//暂定为清洗次数:1~3
int g_curWashCnt =0;
voidwasher_run_loop()
{
switch(g_washerStatus)
{
/*开机初始化自检*/
case WS_INIT:
g_washerStatus =washer_do_init();
break;
/*空闲(等待模式设置)状态*/
case WS_IDLE:
g_washerStatus =washer_do_idle();
break;
/*加水状态*/
case WS_ADD_WATER:
g_washerStatus =washer_do_add_water();
break;
/*清洗状态*/
case WS_WASH:
g_washerStatus =washer_do_wash();
break;
/*排水状态*/
case WS_DRAIN_WATER:
g_washerStatus =washer_do_drain_water();
break;
/*甩干状态*/
case WS_SPIN_DRY:
g_washerStatus =washer_do_spin_dry();
break;
/*暂停状态*/
case WS_PAUSE:
g_washerStatus =washer_do_pause();
break;
default:break;
}
if(g_washerStatus != g_nowWasherStatus)
{
g_lastWasherStatus = g_nowWasherStatus;
g_nowWasherStatus = g_washerStatus;
//printf("new washer status:%d(%s)\r\n", g_washerStatus, washer_status_name[g_washerStatus]);
}
}
这里,洗衣机不同状态的处理逻辑是用函数来实现。