4. RTOS 核心概念:任务、队列、信号量与事件标志组
在将 SECS/GEM 协议栈从裸机(Super-Loop)移植到 RTOS 时,首先需要理解 RTOS 提供的四大核心机制:任务(Task)、队列(Queue)、信号量(Semaphore)和事件标志组(Event Group)。它们是构建多任务并发、数据同步与事件通知的基石。
4.1 任务(Task)—— 并发执行的基本单元
任务是 RTOS 中独立调度、独立运行的最小执行单元。每个任务拥有自己的栈空间、程序计数器(PC)和寄存器上下文。在 SECS/GEM 协议栈中,我们可以将不同的协议处理阶段抽象为独立的任务。
4.1.1 任务状态与生命周期
RTOS 任务通常包含四种基本状态:就绪(Ready)、运行(Running)、阻塞(Blocked)和挂起(Suspended)。调度器根据优先级和时间片在这些状态间切换。
| 状态 | 说明 | SECS/GEM 场景示例 |
|---|---|---|
| 就绪 | 任务已具备运行条件,等待 CPU 使用权 | HSMS 消息接收任务等待被调度 |
| 运行 | 任务正在占用 CPU 执行代码 | 正在解析 SML 格式的 SECS-II 消息 |
| 阻塞 | 任务因等待资源(队列、信号量)而暂停 | 等待从 TCP Socket 读取数据 |
| 挂起 | 任务被强制暂停,不参与调度 | 调试模式下暂停 GEM 状态机任务 |
4.1.2 任务优先级与栈分配
在协议栈移植中,任务优先级的设计直接影响实时性。通常将时间敏感的任务(如 HSMS 心跳处理)设为高优先级,而将非关键任务(如日志记录)设为低优先级。
// FreeRTOS 任务创建示例(SECS 消息处理任务)
void vSECSMessageTask(void *pvParameters) {
SECSMessage_t msg;
for (;;) {
// 阻塞等待队列中的消息
if (xQueueReceive(xSECSQueue, &msg, portMAX_DELAY) == pdPASS) {
ProcessSECSMessage(&msg); // 解析并处理 SECS-II 消息
}
}
}
// 创建任务:优先级 3,栈大小 1024 字
xTaskCreate(vSECSMessageTask, "SECS_Task", 1024, NULL, 3, NULL);
4.2 队列(Queue)—— 任务间安全数据传递
队列是 RTOS 中用于任务间通信(Inter-Task Communication, ITC)的主要机制。它采用 FIFO(先进先出)原则,支持多个发送者和多个接收者。在 SECS/GEM 协议栈中,队列用于在不同协议层之间传递消息。
4.2.1 队列的工作模型
队列本质上是一个受保护的内存缓冲区。发送任务将数据复制到队列尾部,接收任务从队列头部取出数据。这种机制天然解决了临界区资源竞争问题。
| 操作 | API 示例 (FreeRTOS) | SECS/GEM 应用场景 |
|---|---|---|
| 创建队列 | xQueueCreate(5, sizeof(SECSMessage_t)) |
创建可容纳 5 条 SECS 消息的队列 |
| 发送消息 | xQueueSend(xQueue, &msg, 0) |
HSMS 驱动层将接收到的消息入队 |
| 接收消息 | xQueueReceive(xQueue, &msg, portMAX_DELAY) |
SECS 处理任务阻塞等待消息 |
| 查询队列 | uxQueueMessagesWaiting(xQueue) |
检查是否有待处理的 GEM 报警消息 |
// 队列在 SECS/GEM 协议栈中的典型用法
// 定义消息结构体
typedef struct {
uint8_t stream;
uint8_t function;
uint32_t dataLength;
uint8_t* pData;
} SECSMessage_t;
// 创建队列(在初始化时调用)
QueueHandle_t xSECSQueue = xQueueCreate(10, sizeof(SECSMessage_t));
// 任务 A:HSMS 接收任务(发送者)
void vHSMSReceiveTask(void *pvParameters) {
SECSMessage_t rxMsg;
for (;;) {
if (ReceiveFromSocket(&rxMsg) == SUCCESS) {
// 将消息发送到队列,超时时间 100ms
if (xQueueSend(xSECSQueue, &rxMsg, pdMS_TO_TICKS(100)) != pdPASS) {
LogError("SECS Queue Full!");
}
}
}
}
// 任务 B:SECS 处理任务(接收者)
void vSECSProcessTask(void *pvParameters) {
SECSMessage_t msg;
for (;;) {
// 阻塞等待,直到队列中有消息
if (xQueueReceive(xSECSQueue, &msg, portMAX_DELAY) == pdPASS) {
HandleSECSMessage(&msg); // 解析 SxFy 并执行回调
}
}
}
4.3 信号量(Semaphore)—— 资源管理与同步
信号量是 RTOS 中用于资源计数和任务同步的机制。在 SECS/GEM 协议栈中,信号量主要用于保护共享资源(如硬件 SPI 总线)和实现事件发生的同步通知。
4.3.1 二值信号量与计数信号量
| 类型 | 初始值 | 典型用途 | SECS/GEM 场景 |
|---|---|---|---|
| 二值信号量 | 0 | 任务同步、中断与任务通信 | 通知 SECS 任务:HSMS 连接已建立 |
| 计数信号量 | N | 管理有限数量的资源 | 控制 3 个并发 GEM 远程命令处理槽位 |
| 互斥量 (Mutex) | 1 | 保护共享资源(含优先级继承) | 保护共享的 EEPROM 参数存储区 |
// 二值信号量:中断与任务同步(HSMS 连接事件)
SemaphoreHandle_t xHSMSConnectSemaphore;
void vHSMSConnectTask(void *pvParameters) {
for (;;) {
// 阻塞等待信号量
if (xSemaphoreTake(xHSMSConnectSemaphore, portMAX_DELAY) == pdPASS) {
// 连接已建立,启动 GEM 状态机
GEM_EstablishCommunications();
}
}
}
// 中断服务例程中给出信号量
void HSMS_Connect_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xHSMSConnectSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 计数信号量:控制并发远程命令
SemaphoreHandle_t xRCDSlotSemaphore = xSemaphoreCreateCounting(3, 3);
void vGEM_RCTask(void *pvParameters) {
for (;;) {
// 获取一个处理槽位
if (xSemaphoreTake(xRCDSlotSemaphore, pdMS_TO_TICKS(5000)) == pdPASS) {
ProcessRemoteCommand();
xSemaphoreGive(xRCDSlotSemaphore); // 释放槽位
} else {
LogWarning("No available RC slot, command rejected");
}
}
}
FromISR 后缀的 API 函数。
4.4 事件标志组(Event Group)—— 多条件事件驱动
事件标志组允许任务等待多个事件的任意组合(逻辑或)或全部组合(逻辑与)。相比信号量的单一通知,事件标志组更适合复杂的协议状态机触发条件。
4.4.1 事件位与等待模式
每个事件标志组包含多个独立的事件位(通常为 8、16 或 24 位)。任务可以设置等待掩码,指定需要等待哪些位,以及是等待“任一”还是“全部”。
| 参数 | 说明 | SECS/GEM 应用 |
|---|---|---|
| 事件位 | 每个 bit 代表一个独立事件 | Bit0: HSMS 连接就绪, Bit1: 收到 S1F13, Bit2: 定时器超时 |
| 逻辑或 (OR) | 任一指定事件位被置位即唤醒 | 等待“连接建立”或“收到数据”任一事件 |
| 逻辑与 (AND) | 所有指定事件位都被置位才唤醒 | 等待“连接建立”且“身份验证完成”且“配置加载完成” |
| 清除标志 | 退出等待时自动或手动清除事件位 | 处理完事件后清除对应位,准备下一次触发 |
// 定义事件位掩码
#define EVENT_HSMS_CONNECTED (1 << 0)
#define EVENT_S1F13_RECEIVED (1 << 1)
#define EVENT_TIMER_EXPIRED (1 << 2)
#define EVENT_ALL_READY (EVENT_HSMS_CONNECTED | EVENT_S1F13_RECEIVED)
EventGroupHandle_t xGEMEventGroup;
void vGEM_MainTask(void *pvParameters) {
EventBits_t uxBits;
for (;;) {
// 等待 HSMS 连接且收到 S1F13(逻辑与),不清除标志
uxBits = xEventGroupWaitBits(
xGEMEventGroup,
EVENT_ALL_READY, // 等待的位
pdFALSE, // 退出时不自动清除
pdTRUE, // 逻辑与(全部等待)
portMAX_DELAY // 无限等待
);
if ((uxBits & EVENT_ALL_READY) == EVENT_ALL_READY) {
// 条件满足,手动清除事件位
xEventGroupClearBits(xGEMEventGroup, EVENT_ALL_READY);
GEM_StartCommunication();
}
}
}
// 在其他任务或中断中设置事件位
void vHSMSTask(void *pvParameters) {
// ... 建立连接后
xEventGroupSetBits(xGEMEventGroup, EVENT_HSMS_CONNECTED);
}
4.5 四大机制在协议栈中的协同关系
在 SECS/GEM 协议栈的 RTOS 移植中,这四大机制并非孤立使用,而是相互配合形成完整的并发处理框架:
- 任务:将协议栈拆分为 HSMS 驱动、SECS 解析、GEM 状态机、日志管理等独立执行单元。
- 队列:在任务之间传递 SECS 消息、远程命令和响应数据,实现生产者-消费者模型。
- 信号量:保护共享硬件资源(如 SPI Flash),同步中断与任务的事件通知。
- 事件标志组:驱动 GEM 状态机迁移,管理多条件复合触发逻辑。