FreeRtos——9、状态机(FSM)与面向对象在 RTOS 中的使用

前言

在掌握了任务、通信、内存和时序之后,你已经拥有了所有的"部件"。但如何将这些零件组装成一辆能跑十年的"赛车",而不是一堆随时会散架的"部件"?

这需要我们从工程思维 跃迁到系统思维 。在 RTOS 中,最优雅的写法是:外层靠 RTOS 调度,内层靠状态机驱动,封装靠面向对象。利用 C 语言实现"面向对象"的封装,并在 Task 内部植入状态机(FSM),实现真正的模块化、可复用架构。

1. 为什么任务代码越来越难维护?

  • 硬编码: 任务里直接操作 hspi1huart2,导致代码无法复用到另一路外设。

  • 状态混乱: 靠大量的 bool 变量(is_connected, is_running)来维持逻辑,极易进入非法状态。

  • 扩展性差: 想给任务加一个新功能,得改动几十处 if-else

2. C 语言的 OOP 三要素

2.1 属性封装(封装)

利用 struct 把任务所需的资源句柄 (队列、信号量)、状态变量配置参数打包在一起。

2.2 行为模拟(多态/继承)

利用函数指针。不同的实例可以指向不同的处理函数。

2.3 任务实例(实例化)

osThreadNew 创建任务时,通过最后一个 void *argument 参数,将整个结构体对象的地址(即 this 指针)传进去。

3. 封装一个"电机控制对象"

假设我们要管理多个不同的传感器(如 LSM6DSL 和 温湿度传感器),它们都遵循"初始化 -> 采样 -> 报警"的逻辑。

3.1 对象类定义 (SensorObj.h)

cpp 复制代码
// 定义传感器状态
typedef enum { SENSOR_INIT, SENSOR_IDLE, SENSOR_SAMPLING, SENSOR_ERROR } SensorState_t;

// 定义对象结构体
struct SensorObj; // 前向声明

typedef struct SensorObj {
    char name[16];
    SensorState_t state;
    osMessageQueueId_t cmdQueue;

    // 虚拟方法:具体的硬件读写由外部实现
    int (*init_hw)(struct SensorObj *self);
    int (*read_data)(struct SensorObj *self, float *val);

    // 私有数据(模拟私有属性)
    void *hw_handle; 
    float last_value;
} SensorObj_t;

3.2 任务实体:状态机驱动 (SensorObj.c)

cpp 复制代码
void SensorTaskEntry(void *argument) {
    SensorObj_t *this = (SensorObj_t *)argument; // 获取"对象实例"
    uint32_t msg;

    for(;;) {
        // 1. 处理异步指令
        if (osMessageQueueGet(this->cmdQueue, &msg, NULL, 0) == osOK) {
            // 根据消息切换状态...
        }

        // 2. 状态机执行
        switch (this->state) {
            case SENSOR_INIT:
                if (this->init_hw(this) == 0) this->state = SENSOR_SAMPLING;
                else this->state = SENSOR_ERROR;
                break;

            case SENSOR_SAMPLING:
                this->read_data(this, &this->last_value);
                // 逻辑处理:如果超过阈值则报警...
                break;

            case SENSOR_ERROR:
                // 错误处理逻辑
                break;
        }
        osDelay(100); // 采样频率 10Hz
    }
}

3.3 实例化:如何复用同一套代码

cpp 复制代码
// 具体的硬件初始化实现
int LSM6DSL_Init(SensorObj_t *self) { /* 调用 HAL_I2C_... */ return 0; }
int LSM6DSL_Read(SensorObj_t *self, float *v) { /* ... */ return 0; }

void App_Init(void) {
    static SensorObj_t acc_sensor;

    // 像 C++ 一样"构造"对象
    strncpy(acc_sensor.name, "Accel", 16);
    acc_sensor.hw_handle = &hi2c1;
    acc_sensor.init_hw = LSM6DSL_Init;
    acc_sensor.read_data = LSM6DSL_Read;
    acc_sensor.cmdQueue = osMessageQueueNew(4, sizeof(uint32_t), NULL);
    acc_sensor.state = SENSOR_INIT;

    // 关键:创建任务时把对象地址传进去
    osThreadNew(SensorTaskEntry, &acc_sensor, NULL);
}

4. 为什么状态机(FSM)是 RTOS 的黄金搭档?

在 RTOS 任务中,如果你的业务逻辑是"同步"的(比如 Send -> WaitResp -> Process),一旦 WaitResp 时间太长,你必须使用阻塞 API(如信号量)。

但如果你使用了状态机 ,你可以把 WaitResp 变成一个状态。任务在 WaitResp 状态下每 10ms 检查一次标志位,不满足就立刻 osDelay 退出。这让你的任务变得极度可控,你甚至可以在同一个任务里跑多个状态机,实现极高的并发效率。

5. 一句话总结

用结构体封装属性,用函数指针定义行为,用 void *argument 实现实例化。这套"三合一"架构能让你的 RTOS 代码从"能跑"变成"工业级产品"。

相关推荐
青鱼293 天前
时间片在FreeRTOS中的含义解析
freertos·时间片轮转
济6173 天前
FreeRTOS 控制任务设计 (2)--- 运动学逆解 + PID 闭环 + PWM 驱动全流程实现
stm32·单片机·嵌入式·freertos
济6174 天前
FreeRTOS 控制任务设计 (1)--- 双模式闭环控制:IDLE/RUN 状态机与任务通知机制
stm32·单片机·嵌入式·freertos
W.W.H.7 天前
FreeRTOS移植(保姆级教程)
经验分享·单片机·操作系统·freertos·rtos
波特率11520011 天前
FreeRTOS当中的Mail Queue使用教程(CMSIS_v1)
单片机·操作系统·freertos
济61712 天前
FreeRTOS 通信任务设计(3)---基于状态机的串口协议帧解析
stm32·嵌入式·freertos
济61713 天前
FreeRTOS 通信任务设计(4终)----从字节流到有效帧的完美闭环
stm32·嵌入式·freertos
济61715 天前
FreeRTOS 通信任务设计(2)----UART+DMA 环形缓冲 + 空闲中断+ 流缓冲区--- 高效接收方案详解
stm32·单片机·嵌入式·freertos
济61715 天前
FreeRTOS 通信任务设计(1)---STM32 串口 DMA + 协议帧解析 + CRC 校验全流程详解
stm32·嵌入式·freertos
Wave84517 天前
FreeRTOS软件定时器详解
stm32·单片机·freertos