前面幾篇文章,以按鍵功能,介紹了狀態(tài)機(jī)的原理與按鍵狀態(tài)機(jī)實例,實現(xiàn)按鍵單擊、雙擊、長按等狀態(tài)的檢測。
本篇,繼續(xù)使用狀態(tài)機(jī)編程,來實現(xiàn)一個更有趣的功能——全自動洗衣機(jī)。
1 全自動洗衣機(jī)功能分析
下面是一個全自動洗衣機(jī)的控制面板:
面板上有4個按鍵:
電源:控制洗衣機(jī)通電與斷電
水位:選擇洗衣時需要的水位,有1~8個水位
程序:選擇不同的洗衣模式,有1~10個模式
01:標(biāo)準(zhǔn)
02:輕柔
03:快速
...
10:桶風(fēng)干
啟動/暫停:用于啟動或暫停洗衣任務(wù)
面板上還有一個數(shù)碼管,用于顯示當(dāng)前的工作狀態(tài)與剩余時間,可顯示的工作模式有:
AA:浸泡
BB:洗滌
CC:漂洗
DD:脫水
本篇,就按照這款洗衣機(jī)的操作方式實現(xiàn)對應(yīng)的洗衣機(jī)控制邏輯。需注意的是:
實際的洗衣機(jī)有水位檢測傳感器,本篇中,暫用時間延時模擬水位的增加,且默認(rèn)開機(jī)時水位為0
實際的洗衣機(jī)中的洗衣模式,會根據(jù)不同的模式自動設(shè)置清洗次數(shù)與每次清洗的時間以及清洗力度,本篇為了簡化起見,清洗模式的設(shè)置僅用于區(qū)分不同的清洗次數(shù)
某些特殊的清洗模式,如單獨(dú)的脫水,桶風(fēng)干等,本篇暫不實現(xiàn)
對于狀態(tài)的顯示 ,本篇先以串口打印的實現(xiàn)展示,下篇使用OLED小屏幕來顯示不同清洗狀態(tài)的圖標(biāo)等信息
2 畫狀態(tài)圖
根據(jù)上面分析的全自動洗衣機(jī)的功能,以及我們自己使用洗衣機(jī)時的經(jīng)驗,可以畫出如下的全自動洗衣機(jī)的狀態(tài)圖:
首先是上電開機(jī),洗衣機(jī)可能會開機(jī)自檢,檢測洗衣機(jī)的各個部件是否正常,次過程很短。
然后就處于空閑狀態(tài),此時用戶可以設(shè)置水位與清洗模式,若不設(shè)置,則為默認(rèn)的水位與洗衣模式。
接著觸發(fā)開始按鍵后,就開始清洗了,一般流程就是:加水、清洗、排水、甩干、結(jié)束。
根據(jù)不同的清洗模式,加水、清洗和排水這3個過程會循環(huán)執(zhí)行一定的次數(shù)。
另外,在不同的工作階段,按下暫停鍵可以讓洗衣任務(wù)暫停,再按繼續(xù)可讓洗衣任務(wù)繼續(xù)。
3 編程實現(xiàn)
3.1 多按鍵檢測功能
本篇介紹的洗衣機(jī)的按鍵,僅需要檢測按鍵單擊即可,不需要雙擊與長按功能,因此,可使用之前文章介紹的最基礎(chǔ)的按鍵狀態(tài)機(jī)來為洗衣機(jī)狀態(tài)機(jī)提供按鍵事件。
之前介紹的按鍵狀態(tài)機(jī),只有一個按鍵,本篇需要用到4個按鍵(除去電源鍵,3個也可以),因此,需要對按鍵狀態(tài)機(jī)稍加修改,實現(xiàn)按鍵狀態(tài)機(jī)的復(fù)用。
之前介紹的按鍵狀態(tài)機(jī),使用了幾個全局變量來表示狀態(tài),更合理的做法是將其封裝起來:
typedef struct {
KEY_STATUS keyStatus; //當(dāng)前循環(huán)結(jié)束的(狀態(tài)機(jī)的)狀態(tài)
KEY_STATUS nowKeyStatus; //當(dāng)前狀態(tài)(每次循環(huán)后與pKeyFsmData->keyStatus保持一致)
KEY_STATUS lastKeyStatus; //上次狀態(tài)(用于記錄前一狀態(tài)以區(qū)分狀態(tài)的來源)
int keyIdx;
}KeyFsmData;
注意,額外增加了一個按鍵索引值,用來告訴狀態(tài)機(jī)要檢測哪個按鍵。
再將原來的按鍵狀態(tài)機(jī)程序,通過入?yún)?/strong>的形式將上述定義的結(jié)構(gòu)體傳入,并通過函數(shù)返回的形式返回按鍵是否被按下。
這樣修改后的按鍵狀態(tài)機(jī),就是一個獨(dú)立的模塊了,可以通過傳入不同的參數(shù),實現(xiàn)不同按鍵的檢測。
bool key_press_check(KeyFsmData *pKeyFsmData)
{
bool bPress = false;
switch(pKeyFsmData->keyStatus)
{
//省略...
對于本篇需要的4個按鍵的檢測,就可以定義4個數(shù)據(jù)結(jié)構(gòu)體,分別調(diào)用4次狀態(tài)機(jī)函數(shù)即可,實現(xiàn)代碼的復(fù)用。
KeyFsmData pKey0FsmData;
KeyFsmData pKey1FsmData;
KeyFsmData pKey2FsmData;
KeyFsmData pKey3FsmData;
void key_check_init(KeyFsmData *pKeyFsmData, int keyIdx)
{
pKeyFsmData->keyStatus = KS_RELEASE;
pKeyFsmData->lastKeyStatus = KS_RELEASE;
pKeyFsmData->nowKeyStatus = KS_RELEASE;
pKeyFsmData->keyIdx = keyIdx;
}
void multi_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;
void multi_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;
}
else if (key1)
{
g_keyPressIdx = 1;
}
else if (key2)
{
g_keyPressIdx = 2;
}
else if (key3)
{
g_keyPressIdx = 3;
}
}
}
int get_press_key_idx()
{
int idx = g_keyPressIdx;
g_keyPressIdx = -1;
return idx;
}
其中,multi_key_check函數(shù),放到50ms周期的定時器中斷服務(wù)函數(shù)中,使按鍵狀態(tài)機(jī)程序運(yùn)行起來。
get_press_key_idx函數(shù),用于洗衣機(jī)程序來獲取不同按鍵的按下事件,每次獲取后,將按鍵事件清除(g_keyPressIdx設(shè)為無效值-1)
3.2 洗衣功能
按照上面繪制的洗衣機(jī)狀態(tài)圖,使用switch-case法編寫對應(yīng)的程序即可:
#define WASHER_STATUS_ENUM(STATUS) \
STATUS(WS_INIT) /*開機(jī)初始化自檢*/ \
STATUS(WS_IDLE) /*空閑(等待模式設(shè)置)狀態(tài)*/ \
STATUS(WS_ADD_WATER) /*加水狀態(tài)*/ \
STATUS(WS_WASH) /*清洗狀態(tài)*/ \
STATUS(WS_DRAIN_WATER) /*排水狀態(tài)*/ \
STATUS(WS_SPIN_DRY) /*甩干狀態(tài)*/ \
STATUS(WS_PAUSE) /*暫停狀態(tài)*/ \
STATUS(WS_NUM) /*狀態(tài)總數(shù)(無效狀態(tài))*/
typedef enum
{
WASHER_STATUS_ENUM(ENUM_ITEM)
}WASHER_STATUS;
const char* washer_status_name[] = {
WASHER_STATUS_ENUM(ENUM_STRING)
};
WASHER_STATUS g_washerStatus = WS_INIT; //當(dāng)前循環(huán)結(jié)束的(狀態(tài)機(jī)的)狀態(tài)
WASHER_STATUS g_nowWasherStatus = WS_INIT; //當(dāng)前狀態(tài)(每次循環(huán)后與pKeyFsmData->keyStatus保持一致)
WASHER_STATUS g_lastWasherStatus = WS_INIT; //上次狀態(tài)(用于記錄前一狀態(tài)以區(qū)分狀態(tài)的來源)
int g_WorkLoopCnt = 0;
int g_WaterLevel = 5; //1~8
int g_WashMode = 2; //暫定為清洗次數(shù):1~3
int g_curWashCnt = 0;
void washer_run_loop()
{
switch(g_washerStatus)
{
/*開機(jī)初始化自檢*/
case WS_INIT:
g_washerStatus = washer_do_init();
break;
/*空閑(等待模式設(shè)置)狀態(tài)*/
case WS_IDLE:
g_washerStatus = washer_do_idle();
break;
/*加水狀態(tài)*/
case WS_ADD_WATER:
g_washerStatus = washer_do_add_water();
break;
/*清洗狀態(tài)*/
case WS_WASH:
g_washerStatus = washer_do_wash();
break;
/*排水狀態(tài)*/
case WS_DRAIN_WATER:
g_washerStatus = washer_do_drain_water();
break;
/*甩干狀態(tài)*/
case WS_SPIN_DRY:
g_washerStatus = washer_do_spin_dry();
break;
/*暫停狀態(tài)*/
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]);
}
}
這里將洗衣機(jī)不同狀態(tài)時的處理邏輯,都分別使用函數(shù)來實現(xiàn),并將其返回值作為下一個要跳轉(zhuǎn)的狀態(tài)。
各個狀態(tài)的處理函數(shù)如下:
/*開機(jī)初始化自檢*/
WASHER_STATUS washer_do_init()
{
WASHER_STATUS nextStatus = WS_INIT;
g_WorkLoopCnt++;
if (10 == g_WorkLoopCnt) //自檢結(jié)束
{
printf("default water level:%d\r\n", g_WaterLevel);
printf("default wash mode:%d\r\n", g_WashMode);
printf("washer idle...\r\n");
g_WorkLoopCnt = 0;
nextStatus = WS_IDLE;
}
return nextStatus;
}
/*空閑(等待模式設(shè)置)狀態(tài)*/
WASHER_STATUS washer_do_idle()
{
WASHER_STATUS nextStatus = WS_IDLE;
const WASHER_KEY key = check_key_press();
switch(key)
{
case W_KEY_START_PAUSE:
g_WorkLoopCnt = 0;
nextStatus = WS_ADD_WATER;
printf("add water...\r\n");
break;
case W_KEY_WATER_LEVEL:
g_WaterLevel = (g_WaterLevel == 8) ? 1 : (g_WaterLevel+1);
printf("set water level:%d\r\n", g_WaterLevel);
break;
case W_KEY_WASH_MODE:
g_WashMode = (g_WashMode == 3) ? 1 : (g_WashMode+1);
printf("set wash mode:%d\r\n", g_WashMode);
break;
default: break;
}
return nextStatus;
}
/*加水狀態(tài)*/
WASHER_STATUS washer_do_add_water()
{
WASHER_STATUS nextStatus = WS_ADD_WATER;
const WASHER_KEY key = check_key_press();
if (key == W_KEY_START_PAUSE)
{
nextStatus = WS_PAUSE;
printf("[%s] pause...\r\n", __func__);
}
else
{
g_WorkLoopCnt++;
if (g_WaterLevel == (g_WorkLoopCnt / 10)) //加水結(jié)束
{
g_WorkLoopCnt = 0;
nextStatus = WS_WASH;
printf("[%s] done\r\n", __func__);
printf("wash...\r\n");
}
}
return nextStatus;
}
/*清洗狀態(tài)*/
WASHER_STATUS washer_do_wash()
{
WASHER_STATUS nextStatus = WS_WASH;
const WASHER_KEY key = check_key_press();
if (key == W_KEY_START_PAUSE)
{
nextStatus = WS_PAUSE;
printf("[%s] pause...\r\n", __func__);
}
else
{
g_WorkLoopCnt++;
if (6 == (g_WorkLoopCnt / 10)) //清洗結(jié)束
{
g_WorkLoopCnt = 0;
nextStatus = WS_DRAIN_WATER;
printf("[%s] done\r\n", __func__);
printf("drain water...\r\n");
}
}
return nextStatus;
}
/*排水狀態(tài)*/
WASHER_STATUS washer_do_drain_water()
{
WASHER_STATUS nextStatus = WS_DRAIN_WATER;
const WASHER_KEY key = check_key_press();
if (key == W_KEY_START_PAUSE)
{
nextStatus = WS_PAUSE;
printf("[%s] pause...\r\n", __func__);
}
else
{
g_WorkLoopCnt++;
if (3 == (g_WorkLoopCnt / 10)) //排水結(jié)束
{
printf("[%s] done\r\n", __func__);
g_curWashCnt++;
printf("current wash and drain count:%d(target:%d)\r\n", g_curWashCnt, g_WashMode);
g_WorkLoopCnt = 0;
if (g_WashMode == g_curWashCnt)
{
printf("spin dry...\r\n");
nextStatus = WS_SPIN_DRY;
}
else
{
printf("add water...\r\n");
nextStatus = WS_ADD_WATER;
}
}
}
return nextStatus;
}
/*甩干狀態(tài)*/
WASHER_STATUS washer_do_spin_dry()
{
WASHER_STATUS nextStatus = WS_SPIN_DRY;
const WASHER_KEY key = check_key_press();
if (key == W_KEY_START_PAUSE)
{
nextStatus = WS_PAUSE;
printf("[%s] pause...\r\n", __func__);
}
else
{
g_WorkLoopCnt++;
if (100 == g_WorkLoopCnt) //甩干結(jié)束
{
g_WorkLoopCnt = 0;
nextStatus = WS_IDLE;
printf("[%s] done\r\n", __func__);
printf("enter idle mode\r\n");
}
}
return nextStatus;
}
/*暫停狀態(tài)*/
WASHER_STATUS washer_do_pause()
{
WASHER_STATUS nextStatus = WS_PAUSE;
const WASHER_KEY key = check_key_press();
if (key != W_KEY_NULL)
{
switch (g_lastWasherStatus)
{
case WS_ADD_WATER: nextStatus = WS_ADD_WATER; printf("resume...\r\n"); break;
case WS_WASH: nextStatus = WS_WASH; printf("resume...\r\n"); break;
case WS_DRAIN_WATER: nextStatus = WS_DRAIN_WATER; printf("resume...\r\n"); break;
case WS_SPIN_DRY: nextStatus = WS_SPIN_DRY; printf("resume...\r\n"); break;
default: break;
}
}
return nextStatus;
}
對于按鍵的獲取,這里定義了幾個對應(yīng)的功能按鍵,并從按鍵狀態(tài)機(jī)函數(shù)中獲取按鍵索引,再轉(zhuǎn)為洗衣機(jī)程序所需的對應(yīng)功能按鍵:
typedef enum
{
W_KEY_NULL, //沒有按鍵按下
W_KEY_POWER, //電源鍵按下
W_KEY_WATER_LEVEL, //水位鍵按下
W_KEY_WASH_MODE, //清洗模式鍵按下
W_KEY_START_PAUSE //啟動/暫停鍵按下
}WASHER_KEY;
WASHER_KEY check_key_press()
{
WASHER_KEY washerKey = W_KEY_NULL;
int idx = get_press_key_idx();
if (idx != -1)
{
switch(idx)
{
case 0: washerKey = W_KEY_POWER; break; //電源鍵按下
case 1: washerKey = W_KEY_WATER_LEVEL; break; //水位鍵按下
case 2: washerKey = W_KEY_WASH_MODE; break; //清洗模式鍵按下
case 3: washerKey = W_KEY_START_PAUSE; break; //啟動/暫停鍵按下
default: break;
}
//printf("%s idx:%d -> washerKey:%d\r\n", __func__, idx, washerKey);
}
return washerKey;
}
洗衣機(jī)狀態(tài)機(jī)主程序,可以放到main函數(shù)中,每隔100ms調(diào)用一次,使其運(yùn)行起來:
int main(void)
{
delay_init();
KEY_Init();
uart_init(115200);
TIM3_Int_Init(500-1,7200-1); //調(diào)用定時器使得50ms產(chǎn)生一個中斷
printf("hello\r\n");
while(1)
{
washer_run_loop();
delay_ms(100);
}
}
3.3 測試
將代碼燒寫到STM32板子中,通過3個按鍵(電源鍵暫不使用)操作,并通過串口打印,查看全自動洗衣機(jī)的運(yùn)行情況:
4 總結(jié)
本篇實現(xiàn)了一款全自動洗衣機(jī)的基礎(chǔ)洗衣控制流程,可實現(xiàn)不同水位與清洗次數(shù)的設(shè)置,以及任務(wù)的暫停與繼續(xù)。此外,通過對之前按鍵狀態(tài)機(jī)的進(jìn)一步優(yōu)化修改,實現(xiàn)了按鍵狀態(tài)機(jī)的復(fù)用,實現(xiàn)多個按鍵的檢測。下篇文章將進(jìn)一步進(jìn)行功能優(yōu)化,添加OLED小屏幕實現(xiàn)不同狀態(tài)的可視化展示。
審核編輯:湯梓紅
-
STM32
+關(guān)注
關(guān)注
2258文章
10828瀏覽量
352507 -
洗衣機(jī)
+關(guān)注
關(guān)注
11文章
676瀏覽量
43000 -
狀態(tài)機(jī)
+關(guān)注
關(guān)注
2文章
489瀏覽量
27395
發(fā)布評論請先 登錄
相關(guān)推薦
評論