3、血压计状态机顶层设计:系统级状态划分
好,咱们今天聊聊血压计最顶层的状态机设计。说白了,就是整个系统怎么“跑”起来的。
我刚开始做血压计项目时,第一版代码写得特别“直”——一个 while 大循环,里面塞满了 if-else。结果呢?充气还没完成,按键响应就乱了;测量中途用户拔掉袖带,系统直接死机。嗯,那叫一个惨。
后来我学乖了。顶层状态机,必须先把“系统级状态”划分清楚。这是整个架构的骨架,骨架歪了,后面怎么填肉都别扭。
3.1 六大核心状态
我个人习惯,把血压计的工作流程拆成六个状态。你想想看,从用户按下“开始”键,到最终看到结果,中间经历了什么?
- 空闲(IDLE):待机,等待用户操作。
- 充气(INFLATE):气泵工作,袖带加压。
- 测量(MEASURE):压力稳定,采集脉搏波信号。
- 放气(DEFLATE):测量完成,缓慢或快速放气。
- 结果显示(RESULT):在屏幕上展示收缩压、舒张压、心率。
- 错误处理(ERROR):任何异常,比如袖带漏气、用户乱动。
这六个状态,覆盖了血压计99%的工作场景。剩下的1%,比如固件升级、自检,可以单独处理,不放在主状态机里。
3.2 状态转换图(核心逻辑)
状态之间怎么跳?我画了一张表,你一看就明白:
| 当前状态 | 触发事件 | 下一状态 | 备注 |
|---|---|---|---|
| 空闲 | 用户按下“开始”键 | 充气 | 同时检查袖带是否连接 |
| 充气 | 压力达到预设阈值(如160mmHg) | 测量 | 阈值可自适应调整 |
| 充气 | 压力异常(如超过安全值) | 错误处理 | 立即停止充气,快速放气 |
| 测量 | 脉搏波采集完成 | 放气 | 通常采集30-40个心跳周期 |
| 测量 | 信号质量差(如用户移动) | 错误处理 | 提示“测量失败,请重试” |
| 放气 | 压力降至安全值以下 | 结果显示 | 放气速度需控制,避免用户不适 |
| 结果显示 | 用户确认或超时(如30秒) | 空闲 | 自动返回待机 |
| 错误处理 | 错误已排除(如重新连接袖带) | 空闲 | 或直接进入充气重试 |
这张表,就是状态机的“宪法”。所有代码逻辑,都得按这个来。
3.3 代码骨架(C语言实现)
我习惯用枚举定义状态,用 switch-case 实现状态机。简单、清晰、好调试。
// 状态枚举
typedef enum {
STATE_IDLE,
STATE_INFLATE,
STATE_MEASURE,
STATE_DEFLATE,
STATE_RESULT,
STATE_ERROR
} sys_state_t;
// 全局状态变量
sys_state_t current_state = STATE_IDLE;
// 状态机主循环(简化版)
void state_machine_run(void) {
switch (current_state) {
case STATE_IDLE:
if (button_start_pressed()) {
// 检查袖带连接
if (cuff_connected()) {
current_state = STATE_INFLATE;
start_pump();
} else {
show_error("请连接袖带");
}
}
break;
case STATE_INFLATE:
if (pressure_reached_target()) {
current_state = STATE_MEASURE;
stop_pump();
start_sampling();
} else if (pressure_too_high()) {
current_state = STATE_ERROR;
emergency_release();
}
break;
case STATE_MEASURE:
if (sampling_complete()) {
current_state = STATE_DEFLATE;
start_deflation();
} else if (signal_noise_detected()) {
current_state = STATE_ERROR;
show_error("信号干扰");
}
break;
// ... 其他状态类似处理
}
}
你看,每个 case 里只做两件事:判断条件,然后跳转。千万别在 case 里写复杂的业务逻辑,否则状态机就变成“面条机”了。
3.4 避坑指南
我曾经踩过一个坑:在充气状态里,直接调用了放气函数。结果呢?气泵还在转,放气阀也开了,两边打架,压力忽高忽低。用户还以为血压计要爆炸了。
所以,我后来强制规定:每个状态只能操作自己的硬件资源。进入新状态前,必须释放上一个状态的资源。比如:
- 进入“测量”前,确保气泵已关闭。
- 进入“放气”前,确保采样已停止。
- 进入“空闲”前,确保所有外设已断电。
3.5 为什么这样划分?
你可能会问:为什么非要把“充气”和“测量”分开?合在一起不行吗?
嗯,这里要注意。充气阶段,气泵噪音大,压力不稳定,这时候采集脉搏波,信号质量极差。强行合在一起,算法复杂度会翻倍,而且容易误判。分开之后,每个阶段的任务单一,代码好写,调试也方便。
我记得有一次,客户要求增加“快速测量”模式。如果状态机是耦合的,我得改一大片代码。但因为我们划分得清晰,只需要在“充气”状态里加一个“快速充气”分支,其他状态完全不用动。这就是顶层设计带来的好处。
3.6 小结
顶层状态划分,说白了就是“分而治之”。把一个大问题,切成六个小问题,每个小问题单独解决。你想想看,是不是这个理?
下一章,咱们会深入“充气状态”的细节,聊聊怎么控制气泵、怎么检测压力异常。到时候,我会分享一个我当年调试气泵时遇到的“灵异事件”——嗯,保证让你印象深刻。