4. 核心控制逻辑:状态机设计

做嵌入式系统,说白了就是在跟状态打交道。货架报警系统更是如此——你不能一有风吹草动就报警,那跟狼来了没区别。

我个人习惯用状态机来管理这套逻辑。为什么?因为状态机清晰、可靠,出了问题也好排查。你想想看,一个系统如果状态混乱,那调试起来简直要命。

4.1 四个核心状态

我设计的这套状态机,一共四个状态:正常预警报警复位。它们之间不是随便跳的,有严格的触发条件。

状态 含义 触发条件
正常 货架一切安好 所有传感器数据在阈值内
预警 有异常苗头,但还不严重 某个参数超过预警阈值
报警 确认异常,需要处理 预警持续超时,或参数超过报警阈值
复位 人为干预后恢复 按下复位按钮,或远程指令

这里有个细节:预警状态是我特意加进去的。我在项目中遇到过,有些传感器数据会瞬间跳变,如果直接报警,那误报率会高得吓人。加个预警状态,相当于给系统一个「缓冲期」。

4.2 阈值设定策略

阈值怎么设?这不是拍脑袋定的。我一般分三步走:

  1. 理论计算:根据货架承重、倾斜角度的物理极限,算出安全范围。
  2. 实测校准:装上传感器后,采集24小时的空载数据,看看噪声有多大。
  3. 留余量:在理论值基础上,再放宽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?这里面门道也不少。