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 代码从"能跑"变成"工业级产品"。

相关推荐
济6178 小时前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
嵌入式×边缘AI:打怪升级日志8 小时前
基于ESP32S3的智能终端项目--5.显示时间和天气功能
笔记·esp32·freertos·天气·日历
嵌入式×边缘AI:打怪升级日志9 小时前
基于ESP32S3的智能终端项目--4.1 FreeRTOS 任务调度&&设置屏幕亮度
freertos·屏幕亮度
炸膛坦客1 天前
FreeRTOS 学习:(二十九)任务切换的底层逻辑(了解)
单片机·操作系统·freertos
qq_401700411 天前
FreeRtos——1、多任务与“上下文切换”的代价
freertos
螺丝钉的扭矩一瞬间产生高能蛋白1 天前
深入剖析FreeRTOS优先级继承机制:vTaskPriorityInherit与xTaskPriorityDisinherit源码解析
stm32·freertos·嵌入式软件·优先级反转
济6171 天前
FreeRTOS基础知识---为什么使用FreeRTOS以及其核心功能
嵌入式·freertos
炸膛坦客2 天前
FreeRTOS 学习:(二十八)任务调度器 + 启动第一个任务(了解)
stm32·单片机·操作系统·freertos
炸膛坦客2 天前
FreeRTOS 学习:(二十七)死等延时函数会对任务调度产生什么影响
stm32·操作系统·freertos