4. RTOS 核心概念:任务、队列、信号量与事件标志组

在将 SECS/GEM 协议栈从裸机(Super-Loop)移植到 RTOS 时,首先需要理解 RTOS 提供的四大核心机制:任务(Task)、队列(Queue)、信号量(Semaphore)和事件标志组(Event Group)。它们是构建多任务并发、数据同步与事件通知的基石。

核心观点:裸机编程是“时间片轮转”的被动响应,而 RTOS 编程是“事件驱动”的主动调度。理解这四大概念,是协议栈从裸机思维向 RTOS 思维转变的关键一步。

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);
移植要点:裸机中的主循环(while(1))将被拆分为多个独立任务。每个任务必须是一个无限循环,且不能使用阻塞式延时(如 HAL_Delay),应使用 RTOS 的延时函数(如 vTaskDelay)。
RTOS 任务状态迁移图 就绪 (Ready) 运行 (Running) 阻塞 (Blocked) 挂起 (Suspended) 调度器选择 时间片耗尽/抢占 等待资源 资源就绪 vTaskSuspend() vTaskResume()

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 并执行回调
        }
    }
}
避坑指南:队列传递的是数据的副本而非指针。如果消息体较大(如包含大数据块),建议传递指向堆内存的指针,并建立明确的内存所有权转移规则,避免内存泄漏或野指针。
队列在 SECS/GEM 协议栈中的通信模型 HSMS 接收任务 SECS 消息队列 (FIFO, 长度 10) SECS 处理任务 xQueueSend xQueueReceive 数据通过队列副本传递,实现任务间解耦 发送者不等待接收者,接收者阻塞等待数据

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");
        }
    }
}
关键区别:二值信号量用于“事件发生”的通知(类似裸机中的标志位),而计数信号量用于“资源可用数量”的管理。在中断服务例程(ISR)中,必须使用带 FromISR 后缀的 API 函数。
二值信号量:中断同步 SECS 任务 HSMS 连接中断 二值信号量 初始值: 0 GEM 状态机任务 xSemaphoreGiveFromISR xSemaphoreTake 中断给出信号量 → 任务从阻塞态变为就绪态 实现 ISR 到任务的安全、低延迟数据流

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);
}
设计模式:在 SECS/GEM 协议栈中,事件标志组非常适合实现状态机迁移条件。例如,GEM 的“通信建立”状态需要同时满足“TCP 连接就绪”和“S1F13/S1F14 握手完成”,使用事件标志组的逻辑与模式可以优雅地表达这种多条件依赖。
事件标志组驱动 GEM 状态迁移 HSMS 连接 S1F13 收到 定时器超时 事件标志组 Bit0: 连接 Bit1: S1F13 Bit2: 超时 掩码: 0x03 (AND) GEM 主任务 xEventGroupWaitBits 逻辑与模式:所有条件满足后,任务才被唤醒

4.5 四大机制在协议栈中的协同关系

在 SECS/GEM 协议栈的 RTOS 移植中,这四大机制并非孤立使用,而是相互配合形成完整的并发处理框架:

  • 任务:将协议栈拆分为 HSMS 驱动、SECS 解析、GEM 状态机、日志管理等独立执行单元。
  • 队列:在任务之间传递 SECS 消息、远程命令和响应数据,实现生产者-消费者模型。
  • 信号量:保护共享硬件资源(如 SPI Flash),同步中断与任务的事件通知。
  • 事件标志组:驱动 GEM 状态机迁移,管理多条件复合触发逻辑。
移植总结:裸机中的全局标志位 + while 轮询,在 RTOS 中被替换为信号量/事件标志组 + 阻塞等待。这种变化不仅降低了 CPU 占用率,还使代码逻辑更清晰、更模块化。下一章将基于这些核心机制,具体设计 SECS/GEM 协议栈的任务划分与优先级分配方案。