4. 核心控制逻辑:状态机设计
做嵌入式系统,说白了就是在跟状态打交道。货架报警系统更是如此——你不能一有风吹草动就报警,那跟狼来了没区别。
我个人习惯用状态机来管理这套逻辑。为什么?因为状态机清晰、可靠,出了问题也好排查。你想想看,一个系统如果状态混乱,那调试起来简直要命。
4.1 四个核心状态
我设计的这套状态机,一共四个状态:正常、预警、报警、复位。它们之间不是随便跳的,有严格的触发条件。
| 状态 | 含义 | 触发条件 |
|---|---|---|
| 正常 | 货架一切安好 | 所有传感器数据在阈值内 |
| 预警 | 有异常苗头,但还不严重 | 某个参数超过预警阈值 |
| 报警 | 确认异常,需要处理 | 预警持续超时,或参数超过报警阈值 |
| 复位 | 人为干预后恢复 | 按下复位按钮,或远程指令 |
这里有个细节:预警状态是我特意加进去的。我在项目中遇到过,有些传感器数据会瞬间跳变,如果直接报警,那误报率会高得吓人。加个预警状态,相当于给系统一个「缓冲期」。
4.2 阈值设定策略
阈值怎么设?这不是拍脑袋定的。我一般分三步走:
- 理论计算:根据货架承重、倾斜角度的物理极限,算出安全范围。
- 实测校准:装上传感器后,采集24小时的空载数据,看看噪声有多大。
- 留余量:在理论值基础上,再放宽10%-20%。
举个例子:
货架倾斜角的安全极限是5度。但传感器本身有±0.3度的误差。那我设预警阈值就是3度,报警阈值是4.5度。这样既不会太敏感,也不会错过真问题。
嗯,这里要注意:阈值不是一成不变的。环境温度变化、传感器老化,都会影响数据。我建议每半年重新校准一次。
4.3 报警优先级判定
一个货架上可能同时出现多个异常——比如倾斜超了,同时重量也超了。这时候报哪个?
我的原则是:安全第一,破坏性优先。
- 一级优先级:结构安全类(倾斜、震动)—— 可能直接导致货架倒塌
- 二级优先级:负载异常类(超重、偏载)—— 长期会损坏货架
- 三级优先级:环境异常类(温度、湿度)—— 影响货物但不会立即危险
代码里怎么实现?我习惯用一个简单的优先级判断函数:
uint8_t get_alarm_priority(sensor_data_t *data) {
if (data->tilt > ALARM_TILT_THRESHOLD) return 1; // 最高优先级
if (data->weight > ALARM_WEIGHT_THRESHOLD) return 2;
if (data->temp > ALARM_TEMP_THRESHOLD) return 3;
return 0; // 无报警
}
这样写,一旦检测到倾斜异常,系统会立刻锁定这个报警,不再被其他低优先级事件打断。
4.4 消抖与防误报机制
这是整个系统里最容易踩坑的地方。我曾经吃过一次大亏——一个仓库的货架报警系统,一天误报十几次,最后运维人员直接把报警器拆了。你想想看,这多危险。
从那以后,我总结了三层防误报措施:
第一层:软件消抖
连续采样N次,如果N次中有M次超过阈值,才认为有效。这叫「N中取M」法。
我一般设N=5,M=3。也就是说,连续5次采样中,有3次超阈值,才触发预警。
第二层:延时确认
进入预警状态后,不立即报警。而是启动一个定时器,比如5秒。如果5秒内数据恢复正常,就退回正常状态。如果5秒后依然异常,才进入报警。
这个延时时间怎么定?我一般取传感器采样周期的3-5倍。
第三层:硬件滤波
在传感器信号输入端,加一个RC低通滤波器。这能滤掉高频噪声。
注意:RC时间常数不能太大,否则会延迟真实信号的响应。我一般取10ms-50ms。
这三层下来,误报率基本能降到1%以下。我在实际项目中验证过,效果很好。
4.5 状态机代码实现
最后,给你看看我常用的状态机框架。代码不长,但很实用:
typedef enum {
STATE_NORMAL,
STATE_WARNING,
STATE_ALARM,
STATE_RESET
} system_state_t;
system_state_t current_state = STATE_NORMAL;
void state_machine_run(sensor_data_t *data) {
switch (current_state) {
case STATE_NORMAL:
if (check_warning_condition(data)) {
current_state = STATE_WARNING;
start_warning_timer();
}
break;
case STATE_WARNING:
if (check_normal_condition(data)) {
current_state = STATE_NORMAL;
stop_warning_timer();
} else if (warning_timer_expired() ||
check_alarm_condition(data)) {
current_state = STATE_ALARM;
trigger_alarm();
}
break;
case STATE_ALARM:
if (reset_button_pressed()) {
current_state = STATE_RESET;
}
break;
case STATE_RESET:
clear_alarm();
delay_ms(1000); // 复位保持1秒
current_state = STATE_NORMAL;
break;
}
}
这段代码我用了很多年,改来改去,核心逻辑一直没变。你拿去用的时候,只需要根据实际传感器类型,修改 check_warning_condition() 和 check_alarm_condition() 这两个函数就行。
好了,状态机这块就讲这么多。下一节我们聊聊通信协议怎么选——是走I2C还是走RS485?这里面门道也不少。