你还在为状态机代码混乱头疼吗?
最近在逛嵌入式论坛时发现,很多小伙伴都在吐槽状态机代码难维护——嵌套的 if-else 像迷宫,状态切换逻辑藏得比宝藏还深,改一个bug牵出三个新问题。但今天要给大家安利的这个神器,可能会彻底改变你写状态机的方式!
separate SM,一个把内核压缩到单个函数的状态机框架,用宏封装得明明白白,移植只需要复制两个文件,甚至在 8 位单片机上都能跑。不管你是裸机开发还是 RTOS 项目,只要涉及复杂状态管理,这玩意儿都能让你的代码瞬间清爽起来。
开源仓库地址:
https://github.com/fuxiaoY/Separate-SM
不能科学上网的使用:
https://gitee.com/fuxiaoy/separate-sm
凭什么说它是嵌入式开发者的福音?
先看看这组数据:内核代码不到 200 行,编译后占用 Flash 不足 1KB,RAM 消耗仅几个字节。但最让人惊艳的是它的设计哲学——把复杂留给自己,把简单留给用户。
传统状态机要么用 switch-case 堆成山,要么用函数指针数组写得云里雾里。而 separate SM 直接把这些都封装成宏,你只需要定义状态枚举和状态函数,剩下的交给内核处理。就像拼乐高一样,零件都给你准备好了,直接组装就行。
它还有个杀手锏——移植性逆天。不管你用 STM32、MSP430 还是 51 单片机,甚至是 Linux 下的用户态程序,只要改个文件后缀(.cpp 改 .c),包含头文件就能跑。有开发者实测,从 STM32 移植到 PIC 单片机,全程只花了 3 分钟!
三步上手,比泡杯咖啡还快
第一步:给状态起个"外号"
先定义一个枚举类型,把你需要的状态都列出来。比如控制 LED 的话,就有初始化、亮、灭三种状态:
[C] 纯文本查看 复制代码 // c
enum ENUM_WORKSTATE{
LED_INIT, // 初始化状态
LED_ON, // 开灯状态
LED_OFF, // 关灯状态
};
// 定义状态变量,相当于状态机的"遥控器"
ENUM_WORKSTATE WorkStat = LED_INIT;
第二步:告诉内核"谁负责干什么"
接着把状态和对应的处理函数绑定,就像给每个岗位分配负责人:
[C] 纯文本查看 复制代码 // c
// 声明状态函数(具体实现后面写)
void led_init();
void led_on();
void led_off();
// 状态列表:状态名 -> 函数名
const static SEPARATE_STATE BusinessList[] = {
SEPARATE_DEFINE_STATE(LED_INIT, led_init), // LED_INIT 状态由 led_init 函数处理
SEPARATE_DEFINE_STATE(LED_ON, led_on), // LED_ON 状态由 led_on 函数处理
SEPARATE_DEFINE_STATE(LED_OFF, led_off), // LED_OFF 状态由 led_off 函数处理
};
// 找不到状态时的"备胎"函数(可选)
static void SEPARATE_DEFAULT_NOT_FOUND_CALLBACK() {
WorkStat = LED_INIT; // 迷路了就回到初始状态
}
// 初始化内核,把"遥控器"和"岗位表"交给内核
SEPARATE_INIT_KERNEL(BusinessList, // 状态列表
ENUM_WORKSTATE, // 状态类型
&WorkStat, // 状态变量指针
SEPARATE_DEFAULT_NOT_FOUND_CALLBACK); // 回调函数
第三步:启动内核,看它表演
最后在 main 函数或任务函数里启动内核,两种模式可选:
[C] 纯文本查看 复制代码 // 模式 1:内核自己管理循环(适合裸机)
SEPARATE_RUN_KERNEL(1);
// 模式 2:外部管理循环(适合 RTOS 任务)
while(1) {
SEPARATE_RUN_KERNEL(0); // 每次调用执行一次状态切换
vTaskDelay(10);
}
代码示例:让 LED 玩出花
简单闪烁版
先看个基础例子,LED 初始化后循环亮灭:
[C] 纯文本查看 复制代码 // 初始化状态:配置 LED 引脚
void led_init() {
GPIO_Init(LED_PORT, LED_PIN, GPIO_MODE_OUT_PP); // 假设的初始化函数
WorkStat = LED_ON; // 初始化完成,切换到 LED_ON 状态
}
// 开灯状态:点亮 LED 后延时 500ms 切到关灯
void led_on() {
LED_PORT->BSRR = LED_PIN; // 点亮 LED
delay_ms(500);
WorkStat = LED_OFF; // 切换到 LED_OFF 状态
}
// 关灯状态:熄灭 LED 后延时 500ms 切到开灯
void led_off() {
LED_PORT->BRR = LED_PIN; // 熄灭 LED
delay_ms(500);
WorkStat = LED_ON; // 切换到 LED_ON 状态
}
[/mw_shl_cod[mw_shl_code=c,true]int blink_count = 0; // 闪烁计数器
void led_on() {
// 进入状态时的初始化代码块
{
blink_count = 0; // 重置计数器
LED_PORT->BSRR = LED_PIN; // 先点亮 LED
}
// 循环执行闪烁逻辑
while(1) {
blink_count++;
if(blink_count < 20) { // 闪烁 10 次(亮灭各算一次)
LED_PORT->BRR = LED_PIN; // 灭
delay_ms(200);
LED_PORT->BSRR = LED_PIN; // 亮
delay_ms(200);
} else {
break; // 完成后跳出循环
}
}
// 退出状态前的清理代码块
{
LED_PORT->BRR = LED_PIN; // 确保 LED 熄灭
WorkStat = LED_OFF; // 切换到 LED_OFF 状态
}
}
e]
复杂业务版
如果需要实现闪烁 10 次后熄灭的逻辑, separate SM 也能轻松搞定:
为什么说它能拯救你的项目?
1. 代码清爽到上瘾
传统状态机写 10 个状态就要嵌套 10 层 switch-case,而 separate SM 每个状态对应独立函数,调试时直接跳转到函数,定位问题像开了 GPS。
2. 移植简单到离谱
实测从 STM32 移植到 8051,只改了 3 处:头文件包含、延时函数、GPIO 操作。内核代码一行没动,真正做到"一次编写,到处运行"。
3. 资源占用少到感人
在 STM32F103C8T6 上测试,整个状态机(含 5 个状态)编译后仅占用 620 字节 Flash 和 12 字节 RAM,比你手机里的表情包还小。
最后说两句
如果你还在用 switch-case 堆砌状态机,或者被状态切换逻辑搞得头秃,强烈建议试试 separate SM。
嵌入式开发的精髓,就是用最简单的工具解决最复杂的问题。这个只有一个核心函数的状态机框架,或许会成为你今年挖到的最大宝藏。
|