前面几篇文章,以按键功能,介绍了状态机的原理与按键状态机实例,实现按键单击、双击、长按等状态的检测。
本篇,继续使用状态机编程,来实现一个更有趣的功能——全自动洗衣机。
1 全自动洗衣机功能分析
下面是一个全自动洗衣机的控制面板:
面板上有4个按键:
- 电源:控制洗衣机通电与断电
- 水位:选择洗衣时需要的水位,有1~8个水位
- 程序:选择不同的洗衣模式,有1~10个模式
- 01:标准
- 02:轻柔
- 03:快速
- …
- 10:桶风干
- 启动/暂停:用于启动或暂停洗衣任务
面板上还有一个数码管,用于显示当前的工作状态与剩余时间,可显示的工作模式有:
本篇,就按照这款洗衣机的操作方式实现对应的洗衣机控制逻辑。需注意的是:
- 实际的洗衣机有水位检测传感器,本篇中,暂用时间延时模拟水位的增加,且默认开机时水位为0
- 实际的洗衣机中的洗衣模式,会根据不同的模式自动设置清洗次数与每次清洗的时间以及清洗力度,本篇为了简化起见,清洗模式的设置仅用于区分不同的清洗次数
- 某些特殊的清洗模式,如单独的脱水,桶风干等,本篇暂不实现
- 对于状态的显示 ,本篇先以串口打印的实现展示,下篇使用OLED小屏幕来显示不同清洗状态的图标等信息
2 画状态图
根据上面分析的全自动洗衣机的功能,以及我们自己使用洗衣机时的经验,可以画出如下的全自动洗衣机的状态图:
首先是上电开机,洗衣机可能会开机自检,检测洗衣机的各个部件是否正常,次过程很短。
然后就处于空闲状态,此时用户可以设置水位与清洗模式,若不设置,则为默认的水位与洗衣模式。
接着触发开始按键后,就开始清洗了,一般流程就是:加水、清洗、排水、甩干、结束。
根据不同的清洗模式,加水、清洗和排水这3个过程会循环执行一定的次数。
另外,在不同的工作阶段,按下暂停键可以让洗衣任务暂停,再按继续可让洗衣任务继续。
3 编程实现
3.1 多按键检测功能
本篇介绍的洗衣机的按键,仅需要检测按键单击即可,不需要双击与长按功能,因此,可使用之前文章介绍的最基础的按键状态机来为洗衣机状态机提供按键事件。
之前介绍的按键状态机,只有一个按键,本篇需要用到4个按键(除去电源键,3个也可以),因此,需要对按键状态机稍加修改,实现按键状态机的复用。
之前介绍的按键状态机,使用了几个全局变量来表示状态,更合理的做法是将其封装起来:
1 2 3 4 5 6
| typedef struct { KEY_STATUS keyStatus; KEY_STATUS nowKeyStatus; KEY_STATUS lastKeyStatus; int keyIdx; }KeyFsmData;
|
注意,额外增加了一个按键索引值,用来告诉状态机要检测哪个按键。
再将原来的按键状态机程序,通过入参的形式将上述定义的结构体传入,并通过函数返回的形式返回按键是否被按下。
这样修改后的按键状态机,就是一个独立的模块了,可以通过传入不同的参数,实现不同按键的检测。
1 2 3 4 5 6 7
| bool key_press_check(KeyFsmData *pKeyFsmData) { bool bPress = false; switch(pKeyFsmData->keyStatus) {
|
对于本篇需要的4个按键的检测,就可以定义4个数据结构体,分别调用4次状态机函数即可,实现代码的复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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) { 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法编写对应的程序即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #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; WASHER_STATUS g_lastWasherStatus = WS_INIT;
int g_WorkLoopCnt = 0; int g_WaterLevel = 5; int g_WashMode = 2; 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; } }
|
这里将洗衣机不同状态时的处理逻辑,都分别使用函数来实现,并将其返回值作为下一个要跳转的状态。
各个状态的处理函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
| 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; }
|
对于按键的获取,这里定义了几个对应的功能按键,并从按键状态机函数中获取按键索引,再转为洗衣机程序所需的对应功能按键:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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; } } return washerKey; }
|
洗衣机状态机主程序,可以放到main函数中,每隔100ms调用一次,使其运行起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main(void) { delay_init(); KEY_Init(); uart_init(115200); TIM3_Int_Init(500-1,7200-1);
printf("hello\r\n"); while(1) { washer_run_loop(); delay_ms(100); } }
|
3.3 测试
将代码烧写到STM32板子中,通过3个按键(电源键暂不使用)操作,并通过串口打印,查看全自动洗衣机的运行情况:
4 总结
本篇实现了一款全自动洗衣机的基础洗衣控制流程,可实现不同水位与清洗次数的设置,以及任务的暂停与继续。此外,通过对之前按键状态机的进一步优化修改,实现了按键状态机的复用,实现多个按键的检测。下篇文章将进一步进行功能优化,添加OLED小屏幕实现不同状态的可视化展示。