前面幾篇文章,以按鍵功能,介紹了狀態機的原理與按鍵狀態機實例,實現按鍵單擊、雙擊、長按等狀態的檢測。
本篇,繼續使用狀態機編程,來實現一個更有趣的功能——全自動洗衣機。
1 全自動洗衣機功能分析
下面是一個全自動洗衣機的控制面板:
面板上有4個按鍵:
電源:控制洗衣機通電與斷電
水位:選擇洗衣時需要的水位,有1~8個水位
程序:選擇不同的洗衣模式,有1~10個模式
01:標準
02:輕柔
03:快速
...
10:桶風干
啟動/暫停:用于啟動或暫停洗衣任務
面板上還有一個數碼管,用于顯示當前的工作狀態與剩余時間,可顯示的工作模式有:
AA:浸泡
BB:洗滌
CC:漂洗
DD:脫水
本篇,就按照這款洗衣機的操作方式實現對應的洗衣機控制邏輯。需注意的是:
實際的洗衣機有水位檢測傳感器,本篇中,暫用時間延時模擬水位的增加,且默認開機時水位為0
實際的洗衣機中的洗衣模式,會根據不同的模式自動設置清洗次數與每次清洗的時間以及清洗力度,本篇為了簡化起見,清洗模式的設置僅用于區分不同的清洗次數
某些特殊的清洗模式,如單獨的脫水,桶風干等,本篇暫不實現
對于狀態的顯示 ,本篇先以串口打印的實現展示,下篇使用OLED小屏幕來顯示不同清洗狀態的圖標等信息
2 畫狀態圖
根據上面分析的全自動洗衣機的功能,以及我們自己使用洗衣機時的經驗,可以畫出如下的全自動洗衣機的狀態圖:
首先是上電開機,洗衣機可能會開機自檢,檢測洗衣機的各個部件是否正常,次過程很短。
然后就處于空閑狀態,此時用戶可以設置水位與清洗模式,若不設置,則為默認的水位與洗衣模式。
接著觸發開始按鍵后,就開始清洗了,一般流程就是:加水、清洗、排水、甩干、結束。
根據不同的清洗模式,加水、清洗和排水這3個過程會循環執行一定的次數。
另外,在不同的工作階段,按下暫停鍵可以讓洗衣任務暫停,再按繼續可讓洗衣任務繼續。
3 編程實現
3.1 多按鍵檢測功能
本篇介紹的洗衣機的按鍵,僅需要檢測按鍵單擊即可,不需要雙擊與長按功能,因此,可使用之前文章介紹的最基礎的按鍵狀態機來為洗衣機狀態機提供按鍵事件。
之前介紹的按鍵狀態機,只有一個按鍵,本篇需要用到4個按鍵(除去電源鍵,3個也可以),因此,需要對按鍵狀態機稍加修改,實現按鍵狀態機的復用。
之前介紹的按鍵狀態機,使用了幾個全局變量來表示狀態,更合理的做法是將其封裝起來:
typedef struct {
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)
{
//省略...
對于本篇需要的4個按鍵的檢測,就可以定義4個數據結構體,分別調用4次狀態機函數即可,實現代碼的復用。
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函數,放到50ms周期的定時器中斷服務函數中,使按鍵狀態機程序運行起來。
get_press_key_idx函數,用于洗衣機程序來獲取不同按鍵的按下事件,每次獲取后,將按鍵事件清除(g_keyPressIdx設為無效值-1)
3.2 洗衣功能
按照上面繪制的洗衣機狀態圖,使用switch-case法編寫對應的程序即可:
#define WASHER_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) /*狀態總數(無效狀態)*/
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; //當前循環結束的(狀態機的)狀態
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;
void washer_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]);
}
}
這里將洗衣機不同狀態時的處理邏輯,都分別使用函數來實現,并將其返回值作為下一個要跳轉的狀態。
各個狀態的處理函數如下:
/*開機初始化自檢*/
WASHER_STATUS washer_do_init()
{
WASHER_STATUS nextStatus = WS_INIT;
g_WorkLoopCnt++;
if (10 == g_WorkLoopCnt) //自檢結束
{
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;
}
/*空閑(等待模式設置)狀態*/
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;
}
/*加水狀態*/
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)) //加水結束
{
g_WorkLoopCnt = 0;
nextStatus = WS_WASH;
printf("[%s] done\r\n", __func__);
printf("wash...\r\n");
}
}
return nextStatus;
}
/*清洗狀態*/
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)) //清洗結束
{
g_WorkLoopCnt = 0;
nextStatus = WS_DRAIN_WATER;
printf("[%s] done\r\n", __func__);
printf("drain water...\r\n");
}
}
return nextStatus;
}
/*排水狀態*/
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)) //排水結束
{
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;
}
/*甩干狀態*/
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) //甩干結束
{
g_WorkLoopCnt = 0;
nextStatus = WS_IDLE;
printf("[%s] done\r\n", __func__);
printf("enter idle mode\r\n");
}
}
return nextStatus;
}
/*暫停狀態*/
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;
}
對于按鍵的獲取,這里定義了幾個對應的功能按鍵,并從按鍵狀態機函數中獲取按鍵索引,再轉為洗衣機程序所需的對應功能按鍵:
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;
}
洗衣機狀態機主程序,可以放到main函數中,每隔100ms調用一次,使其運行起來:
int main(void)
{
delay_init();
KEY_Init();
uart_init(115200);
TIM3_Int_Init(500-1,7200-1); //調用定時器使得50ms產生一個中斷
printf("hello\r\n");
while(1)
{
washer_run_loop();
delay_ms(100);
}
}
3.3 測試
將代碼燒寫到STM32板子中,通過3個按鍵(電源鍵暫不使用)操作,并通過串口打印,查看全自動洗衣機的運行情況:
4 總結
本篇實現了一款全自動洗衣機的基礎洗衣控制流程,可實現不同水位與清洗次數的設置,以及任務的暫停與繼續。此外,通過對之前按鍵狀態機的進一步優化修改,實現了按鍵狀態機的復用,實現多個按鍵的檢測。下篇文章將進一步進行功能優化,添加OLED小屏幕實現不同狀態的可視化展示。
審核編輯:湯梓紅
-
STM32
+關注
關注
2270文章
10921瀏覽量
356963 -
洗衣機
+關注
關注
13文章
680瀏覽量
43360 -
狀態機
+關注
關注
2文章
492瀏覽量
27613
發布評論請先 登錄
相關推薦
評論