前言
在掌握了任务、通信、内存和时序之后,你已经拥有了所有的"部件"。但如何将这些零件组装成一辆能跑十年的"赛车",而不是一堆随时会散架的"部件"?
这需要我们从工程思维 跃迁到系统思维 。在 RTOS 中,最优雅的写法是:外层靠 RTOS 调度,内层靠状态机驱动,封装靠面向对象。利用 C 语言实现"面向对象"的封装,并在 Task 内部植入状态机(FSM),实现真正的模块化、可复用架构。
1. 为什么任务代码越来越难维护?
-
硬编码: 任务里直接操作
hspi1或huart2,导致代码无法复用到另一路外设。 -
状态混乱: 靠大量的
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 代码从"能跑"变成"工业级产品"。