STM32单线串口通讯实战(五):RTOS架构 —— 线程安全与零拷贝设计

如果说裸机开发是在“走钢丝”,小心翼翼地平衡各个模块的时间片;那么 RTOS(实时操作系统)就是给了你“分身术”。

在 STM32G0 这种资源受限(RAM 可能只有 8KB-36KB)的 MCU 上跑 RTOS,处理高速单线串口,我们必须解决三大难题:

  1. 资源竞争:两个任务同时想发串口,总线谁说了算?

  2. 实时响应:数据来了,如何最快唤醒处理任务?

  3. 内存效率:不要在队列里反复拷贝数据(Memcpy is evil),要用“零拷贝”。

本章将基于 CMSIS-RTOS2 标准接口(底层通常是 FreeRTOS),带你构建一个企业级的单线通讯架构。

1. 架构总览:生产者-消费者模型

在 RTOS 下,我们不再写一个巨大的 while(1)。我们将系统拆分为三个核心角色:

  • ISR (中断服务):生产者 A。负责接收硬件原始数据,不做逻辑,只发信号。

  • Protocol Task (协议任务):消费者。高优先级,被 ISR 唤醒,解析数据包。

  • App Task (业务任务):生产者 B。低优先级,负责发起发送请求。


2. 核心机制 I:同步与快速唤醒 (Thread Flags)

在裸机中,我们轮询标志位。在 RTOS 中,我们使用 事件标志 (Thread Flags)信号量 (Semaphore)推荐:使用 osThreadFlagsSet。比信号量更轻量,且可以直接指定唤醒哪个任务。

代码实战:ISR 唤醒协议任务

/* 定义接收任务 ID */
osThreadId_t tid_Protocol;

/* 串口中断服务函数 (ISR) */
void USART1_IRQHandler(void)
{
    // ... 前面的 IDLE 判断逻辑 (参考第二章) ...

    if (is_idle_detected)
    {
        // 1. 停止 DMA (或记录当前位置)
        // 2. 发送信号给协议任务,参数 0x01 是自定义标志位
        osThreadFlagsSet(tid_Protocol, 0x01); 
        
        // 3. 强制上下文切换 (让高优先级任务立刻执行)
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

3. 核心机制 II:资源保护 (Mutex)

单线串口是 半双工 的。这就意味着“总线”是一个 临界资源 (Critical Resource)。 如果 任务 A 正在发送“开灯”指令的前半段,任务 B 突然切进来发送“关窗”指令,总线上的波形就变成了乱码,且 GPIO 方向切换会冲突。

解决方案:必须使用 互斥锁 (Mutex)

为什么不用二值信号量?

  • 优先级翻转 (Priority Inversion):Mutex 具有“优先级继承”机制,能防止低优先级任务持有锁时被中优先级任务卡死,导致高优先级任务一直等不到锁。

代码实战:线程安全的发送函数

/* 定义互斥锁 ID */
osMutexId_t SingleWire_Mutex_ID;

/* 初始化 */
void Bus_Init(void) {
    SingleWire_Mutex_ID = osMutexNew(NULL); // 创建互斥锁
}

/* 线程安全的发送接口 (任意任务均可调用) */
int32_t SingleWire_Send_Safe(uint8_t *data, uint16_t len)
{
    osStatus_t status;
    
    // 1. 申请锁 (等待 100ms,拿不到就放弃)
    status = osMutexAcquire(SingleWire_Mutex_ID, 100);
    if (status != osOK) return -1; // 总线忙
    
    // ---------------- 临界区开始 ----------------
    
    // 2. 硬件切发送
    UART_ENTER_TX_MODE();
    
    // 3. DMA 发送
    HAL_UART_Transmit_DMA(&huart1, data, len);
    
    // 4. 等待发送完成 (使用信号量或标志位挂起等待,不要死等!)
    // 这里假设我们在 TC 中断里发了一个 flag
    osThreadFlagsWait(0x02, osFlagsWaitAny, 50); 
    
    // 5. 硬件切接收 (防止 Echo)
    UART_ENTER_RX_MODE();
    
    // ---------------- 临界区结束 ----------------
    
    // 6. 释放锁
    osMutexRelease(SingleWire_Mutex_ID);
    
    return 0;
}

4. 核心机制 III:零拷贝 (Zero-Copy) 与内存池

STM32G0 的 RAM 很宝贵。如果 ISR 收到了 256 字节数据:

  1. Copy 到 Queue。

  2. Task 从 Queue Copy 出来。 这是极大的浪费!

解决方案Memory Pool (内存池) + Message Queue (指针队列)。 ISR 只把数据的 指针 扔进队列,Task 读指针直接访问 DMA 缓冲区。

方案设计

由于我们使用的是 DMA Circular Buffer,数据已经在 RAM 里了。

  • 简单做法:直接传递 RingBuffer 的 Start_IndexLength

  • 消息结构体

typedef struct {
    uint16_t start_idx; // 数据在环形缓冲区的起始位置
    uint16_t len;       // 数据长度
    uint32_t timestamp; // 接收时间戳
} UART_Event_t;

/* 定义消息队列 */
osMessageQueueId_t UART_Queue_ID;

/* 协议处理任务 */
void Task_Protocol(void *argument)
{
    UART_Event_t event;
    uint8_t *pRealData;
    
    while (1)
    {
        // 1. 挂起等待消息 (从 ISR 发来的)
        // 这里的 &event 只是接收了几个字节的结构体,而不是 Payload
        osMessageQueueGet(UART_Queue_ID, &event, NULL, osWaitForever);
        
        // 2. 核心:通过索引直接定位 DMA Buffer,无需拷贝
        // 注意处理环形回卷 (Wrap-around)
        Process_RingBuffer_Data(event.start_idx, event.len);
    }
}

5. RTOS 常见问题

5.1 栈溢出 (Stack Overflow)

  • printf 和 DMA 回调是栈溢出的重灾区。

  • 建议:协议任务分配 256-512 Bytes 栈空间(根据 Parse 函数深度);osMutex 相关操作不占栈。

5.2 低功耗 (Tickless Mode)

如果你的设备是电池供电,RTOS 的 Tick 会让 CPU 无法深度休眠。

  • 在 STM32G0 上,配合 LPUART(低功耗串口),可以在 Stop Mode 下通过地址匹配唤醒。

  • 策略:当 Task_Protocol 没事干挂起时,RTOS 进入 IDLE 任务,调用 __WFI()。但要注意 DMA 传输期间不能进入 Stop Mode(DMA 需要时钟),可以使用 osKernelLock() 或特定的电源管理锁。


本专栏系列总结

通过这五章内容,我们完成了一次从“电线”到“操作系统”的完整技术栈构建:

  1. 物理层:看懂了原理图,学会了用 STM32G0 的 HDSELOpen-Drain 避坑硬件连接。

  2. 链路层:掌握了 DMA Circular + IDLE 的黄金接收方案,解决了收发切换的时序痛点。

  3. 协议层:设计了包含 Header/Len/CRC 的 Mini-Frame,并利用 9-bit 模式实现了硬件地址过滤。

  4. 裸机架构:用状态机 (FSM) 实现了非阻塞的前后台系统。

  5. RTOS 架构:用 Mutex 保护总线,用 Thread Flags 和 Queue 实现了高效的线程通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值