3、设备状态机模型:初始化、配网、运行、休眠、唤醒的状态转换图

做物联网设备开发,说白了就是在跟状态机打交道。你想想看,一个设备从出厂到报废,无非就是在几个状态之间来回切换。我见过不少新手,一上来就写业务逻辑,结果设备跑着跑着就卡死了——为什么?状态没管好。

这一章,我就带你把这个状态机模型彻底理清楚。我个人习惯把物联网设备的状态归纳为五个核心阶段:初始化 → 配网 → 运行 → 休眠 → 唤醒。这五个状态构成了一个闭环,也是整个无线配网休眠唤醒机制的基石。

3.1 五状态核心模型

先看一张总图,把整体脉络搭起来。后面我们再逐个拆解。

设备五状态转换图 初始化 上电/复位 配网 Wi-Fi/BLE 配置 运行 数据采集/上报 休眠 低功耗待机 唤醒 定时/外部触发 未配网 已配网 配网成功 空闲超时 定时/中断 恢复上下文 配网超时 说明: 实线:正常转换路径 虚线:异常/超时转换路径 每个状态都有超时保护

这张图我建议你保存下来。每次写状态机代码时,对照着看,不容易漏掉边界情况。

3.2 各状态详解

3.2.1 初始化状态

设备上电或复位后,第一件事就是初始化。这个阶段做的事情很杂,但核心就三件:

  • 硬件初始化:时钟、GPIO、外设(SPI、I2C、UART)
  • 软件初始化:RTOS 任务创建、内存池分配、驱动加载
  • 状态决策:检查 NVM 中是否存有有效的配网信息

关键决策点:初始化完成后,设备根据配网标志位决定下一步去向。

  • 有有效配网信息 → 直接进入运行状态
  • 无配网信息或配网信息失效 → 进入配网状态

我记得有一次做智能插座项目,初始化阶段没处理好 GPIO 电平状态,结果设备一上电继电器就吸合了——差点把测试台烧了。从那以后,我坚持在初始化代码里先强制所有输出引脚为安全电平,再逐个配置。

3.2.2 配网状态

配网是物联网设备最头疼的阶段,没有之一。为什么?因为用户操作不可控。你可能遇到过这种情况:用户拿着手机扫了半天二维码,设备就是连不上。

配网状态的核心任务:

  1. 开启配网模式(Wi-Fi AP/BLE Beacon)
  2. 接收来自手机端的 SSID/密码
  3. 尝试连接路由器
  4. 连接成功后保存配网信息到 NVM
  5. 通知用户配网结果(LED 闪烁/蜂鸣器)

避坑指南:我曾经在一个项目中,配网超时时间设成了 30 秒。结果用户反馈说「还没输完密码就超时了」。后来我改成 120 秒,并增加了配网进度提示,问题才解决。

建议:配网超时时间至少 60 秒,最好可配置。

3.2.3 运行状态

这是设备的主状态。说白了,设备在这个阶段干它该干的活——采集传感器数据、上报到云端、执行控制指令。

运行状态要注意几个关键点:

关注点 说明 我的建议
心跳保活 定期向服务器发送心跳包 间隔 30-60 秒,避免被踢下线
数据上报频率 传感器数据上传间隔 根据业务需求动态调整,不要固定死
空闲检测 判断是否长时间无交互 连续 N 个周期无数据变化,考虑进入休眠
异常处理 网络断开、传感器故障 记录错误码,尝试重连,必要时复位

你想想看,如果一个温度传感器每 5 秒上报一次数据,而温度根本没变化——这不是浪费电吗?所以运行状态里一定要有空闲检测逻辑。

3.2.4 休眠状态

休眠是物联网设备省电的关键。对于电池供电的设备,休眠状态占了整个生命周期的 90% 以上。

休眠状态的核心策略:

  • 深度睡眠:CPU 停止运行,仅 RTC 和唤醒源工作,功耗可低至 μA 级
  • 浅度睡眠:CPU 低频运行,部分外设关闭,功耗在 mA 级
  • 内存保持:保留关键上下文(如配网信息、传感器校准值)

经验之谈:我个人习惯在进入休眠前,先把所有外设的电源彻底切断——不仅仅是关闭时钟。有些芯片的 GPIO 在休眠状态下会漏电,我踩过这个坑。建议在休眠前将未使用的 GPIO 设置为高阻输入或下拉。

3.2.5 唤醒状态

唤醒是休眠的逆过程。设备从低功耗模式恢复,重新初始化必要的硬件,恢复上下文,然后回到运行状态。

唤醒源通常有三种:

  1. 定时唤醒:RTC 闹钟,周期性地醒来上报数据
  2. 外部中断唤醒:按键、传感器触发、GPIO 电平变化
  3. 通信唤醒:Wi-Fi 的 DTIM 信标、BLE 的广播包

唤醒阶段最容易出问题的是上下文恢复。我记得有一次做智能门锁项目,唤醒后没有正确恢复 RTC 时间,导致所有时间戳都乱了。后来我加了一个校验机制:在进入休眠前计算关键数据的 CRC,唤醒后先校验再使用。

3.3 状态转换的触发条件

状态不会自己跳,得有触发条件。我把常见的触发条件整理成了一张表:

源状态 目标状态 触发条件 备注
初始化 配网 未检测到有效配网信息 首次上电或配网信息被清除
初始化 运行 检测到有效配网信息 快速启动
配网 运行 配网成功,连接路由器正常 保存配网信息到 NVM
配网 休眠 配网超时(如 120 秒无操作) 进入低功耗等待下次唤醒
运行 休眠 空闲超时(如 5 分钟无交互) 保存上下文后休眠
休眠 唤醒 定时到达 / 外部中断 唤醒后立即进入唤醒状态
唤醒 运行 上下文恢复完成 恢复传感器、网络连接

3.4 状态机代码骨架

说了这么多,不如直接看代码。下面是一个简化的状态机实现骨架,我用 C 语言写的,你可以在任何 RTOS 或裸机项目里直接套用。

typedef enum {
    STATE_INIT,
    STATE_CONFIG,
    STATE_RUN,
    STATE_SLEEP,
    STATE_WAKEUP
} device_state_t;

device_state_t current_state = STATE_INIT;
uint32_t idle_counter = 0;

void state_machine_run(void) {
    switch (current_state) {
        case STATE_INIT:
            // 硬件初始化
            hw_init();
            // 检查配网信息
            if (nvm_has_config()) {
                current_state = STATE_RUN;
            } else {
                current_state = STATE_CONFIG;
            }
            break;

        case STATE_CONFIG:
            // 开启配网模式
            start_config_mode();
            if (config_success()) {
                nvm_save_config();
                current_state = STATE_RUN;
            } else if (config_timeout()) {
                current_state = STATE_SLEEP;
            }
            break;

        case STATE_RUN:
            // 执行主业务逻辑
            do_work();
            // 空闲检测
            if (is_idle()) {
                idle_counter++;
                if (idle_counter > IDLE_THRESHOLD) {
                    save_context();
                    current_state = STATE_SLEEP;
                }
            } else {
                idle_counter = 0;
            }
            break;

        case STATE_SLEEP:
            // 进入低功耗模式
            enter_deep_sleep();
            // 醒来后自动进入 WAKEUP
            current_state = STATE_WAKEUP;
            break;

        case STATE_WAKEUP:
            // 恢复上下文
            restore_context();
            // 重新初始化外设
            hw_reinit();
            current_state = STATE_RUN;
            break;

        default:
            // 异常状态,强制复位
            system_reset();
            break;
    }
}

注意:上面的代码是简化版。实际项目中,每个状态内部还要处理超时、错误重试、看门狗喂狗等逻辑。特别是休眠状态,进入前一定要确保所有外设都处于安全状态。

3.5 状态机设计原则

最后,分享几条我在项目中总结的状态机设计原则:

  • 单一职责:每个状态只做一件事。初始化就只初始化,不要在里面做配网。
  • 有限状态:状态数量控制在 5-7 个以内,太多容易乱。
  • 超时保护:每个状态都要有超时退出机制,防止死锁。
  • 日志记录:状态切换时打印日志,方便调试。我习惯用「[STATE] INIT -> CONFIG」这种格式。
  • 看门狗配合:在状态机主循环里喂狗,防止某个状态卡死导致系统复位。