用C语言给51单片机写个‘双层’状态机:从扫地机器人代码看HSM架构实战
用C语言给51单片机写个‘双层’状态机:从扫地机器人代码看HSM架构实战
嵌入式开发中,状态机是处理复杂逻辑的利器。但传统平面状态机在面对多层级业务逻辑时,往往显得力不从心。本文将从一个扫地机器人的实际案例出发,带你用C语言实现层次状态机(HSM),在51单片机上构建清晰可控的业务逻辑框架。
1. 为什么扫地机器人需要层次状态机
想象一下你的扫地机器人正在工作时遇到低电量。传统状态机可能这样处理:
enum State {
IDLE,
CLEANING,
CHARGING,
AVOIDING_OBSTACLE
};
void handle_event(enum State *current_state, enum Event event) {
switch(*current_state) {
case CLEANING:
if(event == LOW_BATTERY) {
*current_state = CHARGING;
}
// 其他事件处理...
}
}
这种写法的问题很快会暴露:当"低电量"事件发生时,需要手动处理停止清扫、寻找充电座、对接充电等一系列子状态。代码迅速膨胀,状态转换逻辑变得难以维护。
HSM通过状态嵌套解决了这个问题。在HSM中,充电状态可以包含"寻找充电座"、"对接"、"充电中"等子状态,形成清晰的层次关系:
Cleaning (父状态)
├─ NormalCleaning
├─ SpotCleaning
└─ EdgeCleaning
Charging (父状态)
├─ LocatingDock
├─ Docking
└─ Charging
2. HSM核心概念与C语言实现
2.1 状态节点数据结构设计
我们使用结构体表示每个状态节点:
typedef struct StateNode {
void (*entry_action)(void);
void (*exit_action)(void);
struct StateNode *parent;
struct StateNode *children[MAX_CHILDREN];
int num_children;
} StateNode;
关键设计要点:
- entry/exit动作:进入/退出状态时执行的操作
- 父状态指针:实现状态层级的关键
- 子状态数组:支持状态嵌套
2.2 状态转换的黄金法则
HSM的状态转换遵循三个原则:
- 退出当前状态链:从当前状态开始,依次向上执行exit动作,直到遇到共同祖先
- 进入目标状态链:从共同祖先开始,向下执行entry动作,直到目标状态
- 最小化转换路径:只执行必要的entry/exit动作
用代码表示这一过程:
void transition_to(StateNode *target) {
// 1. 找出共同祖先
StateNode *common_ancestor = find_common_ancestor(current_state, target);
// 2. 退出当前状态链
StateNode *s = current_state;
while(s != common_ancestor) {
if(s->exit_action) s->exit_action();
s = s->parent;
}
// 3. 进入目标状态链
StateNode *path[MAX_DEPTH];
int path_len = build_path(common_ancestor, target, path);
for(int i=0; i<path_len; i++) {
if(path[i]->entry_action) path[i]->entry_action();
}
current_state = target;
}
3. 扫地机器人HSM实战实现
3.1 状态树构建
我们为扫地机器人设计如下状态层次:
// 顶层状态
StateNode root = {
.entry_action = NULL,
.exit_action = NULL,
.parent = NULL
};
// 一级状态
StateNode idle = { .parent = &root };
StateNode cleaning = { .parent = &root };
StateNode charging = { .parent = &root };
StateNode error = { .parent = &root };
// 二级状态(清洁子状态)
StateNode normal_cleaning = { .parent = &cleaning };
StateNode spot_cleaning = { .parent = &cleaning };
StateNode edge_cleaning = { .parent = &cleaning };
// 二级状态(充电子状态)
StateNode locating_dock = { .parent = &charging };
StateNode docking = { .parent = &charging };
StateNode charging_in_progress = { .parent = &charging };
3.2 事件处理机制
事件处理需要考虑状态层级:
void handle_event(StateNode *current, Event event) {
StateNode *s = current;
// 自底向上查找能处理该事件的状态
while(s != NULL) {
if(state_handles_event(s, event)) {
// 找到处理者,执行处理
s->event_handlers[event](event);
return;
}
s = s->parent;
}
// 没有状态处理该事件
log_unhandled_event(event);
}
这种设计实现了事件处理的继承性:子状态可以处理特定事件,未处理的事件会自动交由父状态处理。
4. 优化技巧与资源管理
4.1 状态机内存优化
在51单片机等资源受限环境中,可以采用这些优化策略:
-
状态标志压缩:使用位域存储状态标志
typedef struct { unsigned is_cleaning : 1; unsigned is_charging : 1; unsigned cleaning_mode : 2; // 0=normal,1=spot,2=edge } StateFlags; -
动态分配取舍:预先分配所有状态节点,避免动态内存分配
-
表格驱动优化:用查表法替代switch-case
const EventHandler event_handlers[MAX_STATES][MAX_EVENTS] = { [IDLE] = { [START_BUTTON] = handle_start, // ... }, // ... };
4.2 调试与日志技巧
添加这些调试支持会事半功倍:
// 状态转换日志
void log_transition(StateNode *from, StateNode *to) {
printf("State change: ");
print_state_path(from);
printf(" -> ");
print_state_path(to);
printf("\n");
}
// 打印状态路径
void print_state_path(StateNode *state) {
if(state->parent) {
print_state_path(state->parent);
printf("/");
}
printf("%s", state_names[state->id]);
}
5. 真实案例:避障状态处理
当扫地机器人遇到障碍物时,HSM展现出强大优势:
Cleaning
├─ NormalCleaning
│ └─ (遇到障碍物) → AvoidingObstacle
└─ AvoidingObstacle
├─ TurnLeft
├─ TurnRight
└─ BackAndTurn
实现代码清晰反映业务逻辑:
void handle_obstacle(Event event) {
if(current_state == &normal_cleaning) {
transition_to(&avoiding_obstacle);
// 根据传感器数据选择避障策略
if(left_sensor > right_sensor) {
transition_to(&turn_right);
} else {
transition_to(&turn_left);
}
}
}
这种结构下,当避障完成后,只需简单返回到父状态:
void handle_avoidance_complete(Event event) {
// 从当前避障子状态返回到AvoidingObstacle
transition_to(current_state->parent);
// 然后返回到Cleaning状态
transition_to(current_state->parent);
}
6. 状态机与调度器集成
在实际系统中,状态机需要与主调度器配合工作:
void main_loop(void) {
init_hsm();
while(1) {
// 1. 读取传感器数据
poll_sensors();
// 2. 处理定时事件
check_timers();
// 3. 处理外部事件
Event event = get_next_event();
if(event != NO_EVENT) {
handle_event(current_state, event);
}
// 4. 执行当前状态的持续行为
if(current_state->do_action) {
current_state->do_action();
}
// 5. 低功耗处理
enter_idle_if_possible();
}
}
这种架构下,状态机成为系统的核心决策引擎,而调度器负责协调各个模块的执行。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)