当前位置: 首页 > news >正文

在 RTOS 里使用 UART——信号量 + DMA 回调框架

目录

  • 一、前言
  • 二、为什么 RTOS 需要特殊处理 UART
  • 三、核心模式:中断回调 + 信号量 + 任务
  • 四、完整代码走读
  • 五、从裸机到 RTOS 的改造清单
  • 六、常见坑
  • 七、结尾

一、前言

大家好,这里是Hello_Embed

前面几篇我们把 UART 从查询一路升级到了 DMA+IDLE——效率天花板。但这些都是"裸机思维":在主循环或单个任务里调 API。

本篇进入RTOS 多任务环境:一个任务负责 UART 收发,其他任务干别的事,彼此不阻塞。


二、为什么 RTOS 需要特殊处理 UART

2.1 裸机 vs RTOS 的本质区别

裸机RTOS
等待方式while(flag)死等xSemaphoreTake()阻塞(让出 CPU)
通知方式全局变量 + 轮询信号量/队列(OS 原生机制)
多任务大循环,时序耦合独立任务,各等各的信号量

2.2 一个典型场景

任务 A:等待串口指令 → 解析 → 控制 LED 任务 B:每 100ms 读取传感器 → 显示到 LCD 任务 C:按键检测 → 串口上报状态

如果没有 RTOS,你需要在while(1)里轮询三个条件,任意一个阻塞就全卡死。有了 RTOS,三个任务各自独立运行,串口任务在xSemaphoreTake上阻塞时,CPU 自动切给任务 B/C。


三、核心模式:中断回调 + 信号量 + 任务

3.1 三层架构

┌─────────────────────────────────────────┐ │ Task (任务层) │ │ xSemaphoreTake() → 阻塞等待 │ │ 醒来后处理数据、发 LCD 显示 │ └──────────────┬──────────────────────────┘ │ 信号量 ┌──────────────▼──────────────────────────┐ │ HAL Callback (中断回调层) │ │ HAL_UARTEx_RxEventCallback() │ │ → xSemaphoreGiveFromISR() │ └──────────────┬──────────────────────────┘ │ 中断触发 ┌──────────────▼──────────────────────────┐ │ Hardware ISR (硬件中断层) │ │ UART4_IRQHandler → HAL_UART_IRQHandler │ └─────────────────────────────────────────┘

关键规则

3.2 信号量的角色

信号量在这里是"通知机制",不是"互斥锁":

初始值 = 0(空) Task: ISR: Take(等) ──┐ │ 数据到达 → │ Give ────→ 信号量 = 1 └──→ 拿到信号量,继续执行 │ └── 超时 5 秒 → 返回超时,任务仍可执行其他逻辑

四、完整代码走读

以下代码来自工程,三个文件协同工作:

4.1 usart.c — 核心:回调 + 任务

/* USER CODE BEGIN 0 */#defineUART4_RX_BUF_SIZE256staticuint8_tg_uart4_rx_buf[UART4_RX_BUF_SIZE];staticuint16_tg_uart4_rx_len=0;SemaphoreHandle_t xUART4_IdleSemaphore;// ① 声明信号量句柄// ② IDLE 回调(ISR 上下文)voidHAL_UARTEx_RxEventCallback(UART_HandleTypeDef*huart,uint16_tSize){if(huart->Instance==UART4){g_uart4_rx_len=Size;// 保存长度xSemaphoreGiveFromISR(xUART4_IdleSemaphore,NULL);// 唤醒任务}}// ③ 接收任务voidCH2_UART4_RxTaskFunction(void*pvParameters){intcnt=0;charbuf[100];while(1){HAL_UARTEx_ReceiveToIdle_DMA(&huart4,g_uart4_rx_buf,UART4_RX_BUF_SIZE);if(xSemaphoreTake(xUART4_IdleSemaphore,pdMS_TO_TICKS(5000))==pdPASS){sprintf(buf,"Recv: %d %d Bytes",cnt++,g_uart4_rx_len);Draw_String(0,40,buf,0x0000ff00,0);}}}

4.2 app_freertos.c — 初始化:创建信号量 + 任务

/* USER CODE BEGIN FunctionPrototypes */externSemaphoreHandle_t xUART4_IdleSemaphore;externvoidCH2_UART4_RxTaskFunction(void*pvParameters);/* USER CODE END FunctionPrototypes */voidMX_FREERTOS_Init(void){// .../* USER CODE BEGIN RTOS_SEMAPHORES */xUART4_IdleSemaphore=xSemaphoreCreateBinary();// ④ 创建信号量/* USER CODE END RTOS_SEMAPHORES */// .../* USER CODE BEGIN RTOS_EVENTS */xTaskCreate(CH2_UART4_RxTaskFunction,"CH2_UART4_RxTaskFunction",512,// ⑤ 栈大小(words),512 = 2KBNULL,1,// ⑥ 优先级NULL);/* USER CODE END RTOS_EVENTS */}

4.3 执行时序

上电 → MX_FREERTOS_Init() ├─ xSemaphoreCreateBinary() // 信号量 = 0 └─ xTaskCreate(CH2...) │ ▼ while(1): HAL_UARTEx_ReceiveToIdle_DMA() // 启动 DMA+IDLE xSemaphoreTake(5000) // 阻塞在此,让出 CPU ← │ ┌──────────┘ │ DMA 搬运数据到 g_uart4_rx_buf │ RX 线空闲 > 1 字节时间 │ UART4 IDLE → ISR → Callback │ → xSemaphoreGiveFromISR() │ xSemaphoreTake 返回 pdPASS ←──┘ sprintf + Draw_String // 处理数据 循环回到 HAL_UARTEx_ReceiveToIdle_DMA

五、从裸机到 RTOS 的改造清单

如果你有一个裸机 UART 程序,迁到 RTOS 只需要改 4 处:

裸机写法RTOS 写法
while(!flag)死等xSemaphoreTake(sem, timeout)
flag = 1在中断里xSemaphoreGiveFromISR(sem, NULL)
全局变量放 main.c声明SemaphoreHandle_t
xSemaphoreCreateBinary()在任务创建前调用

记住两点就够

  1. ISR 里用GiveFromISR,任务里用Take
  2. 信号量必须先创建,再创建用到它的任务

六、常见坑

6.1 忘记创建信号量

// 只声明了 extern,没 create → xSemaphoreTake 永远超时externSemaphoreHandle_t xUART4_IdleSemaphore;// ← 光声明不够

解决:在MX_FREERTOS_InitRTOS_SEMAPHORES区域加xSemaphoreCreateBinary()

6.2 任务栈太小

xTaskCreate(...,128,...);// 128 words = 512 bytes// sprintf + Draw_String → 栈溢出 → HardFault

工程已经改了 512,没问题。经验值:用到sprintf的任务至少 256 words。

6.3 中断优先级

HAL_NVIC_SetPriority(UART4_IRQn,5,0);// 优先级 5

FreeRTOS 要求所有使用其 API 的中断优先级configMAX_SYSCALL_INTERRUPT_PRIORITY(STM32H5 上通常是 5)。UART4 的优先级 5 刚好满足。如果设成 0~4,xSemaphoreGiveFromISR会触发configASSERT失败。

6.4 在回调里做了太多事

// ❌ 错误:ISR 里调用 Draw_String(可能涉及 SPI,阻塞太久)voidHAL_UARTEx_RxEventCallback(...){Draw_String(0,0,buf,...);// 不要在 ISR 里做这个!}// ✅ 正确:ISR 只保存数据 + 发信号量,任务里再处理voidHAL_UARTEx_RxEventCallback(...){g_uart4_rx_len=Size;xSemaphoreGiveFromISR(sem,NULL);// 只做这些}

七、结尾

至此,从裸机 UART 查询到 RTOS + DMA + IDLE 的完整升级路径走完了:

Note 7: 硬件结构与三种方式(概念) Note 8: 查询 → 中断 → DMA(落地) Note 9: SPI LCD DMA(跨外设迁移) Note 10: DMA + IDLE(效率天花板) Note 11: RTOS 信号量框架(本篇 — 多任务就绪)

下一篇预告:用面向对象思想封装 UART 设备层(UART_Device),让 libmodbus 等上层协议栈不关心底层是哪个串口、用什么方式收发。

http://www.jsqmd.com/news/783475/

相关文章:

  • AdvancedTCA架构:电信与超算融合的技术解析
  • 基于主题建模的教育多模态与生成式AI研究全景分析
  • 初创公司如何借助 Taotoken 的按 token 计费模式控制 AI 实验成本
  • 范进人生轨迹
  • AI预测抗生素耐药性:从数据清洗到可解释模型的全流程实战
  • iOS 开发 事件响应链与手势识别原理
  • CANNOpsTransformer融合因果一维卷积
  • CANN/asc-devkit Asinh函数
  • 2026年山东沥青加温设备、沥青储存罐及筑路设备源头厂家完全选购指南 - 企业名录优选推荐
  • Excel AVERAGE函数底层逻辑与四大均值函数实战指南
  • 哔哩下载姬Downkyi完整指南:从入门到精通的高效B站视频管理方案
  • AArch64系统寄存器架构与Neoverse V3AE核心解析
  • CANN驱动获取设备DIE ID
  • 利用 Taotoken CLI 工具一键配置团队统一开发环境的教程
  • 从源码看本质:扒一扒Java LinkedList里poll()和remove()那点事儿
  • 总担心自己会偷拿别人的东西,原来是侵入性思维!
  • Windows驱动存储架构解析:DriverStore Explorer企业级驱动管理完整方案
  • CANN/cann-recipes-train: Qwen3-1.7B SFT训练示例
  • CANN/GE UDF接口列表
  • 实拍实测!兰州儿童摄影推荐TOP3,看完再选不踩雷 - 江湖评测
  • 诺基亚23亿美元收购英飞朗,昔日手机霸主借光通信转型AI算力时代
  • 2026 海口财税 Q2 季度:注册公司代办,代理记账,高新企业认证靠谱机构十大推荐排行 - 品牌优企推荐
  • 从开发者反馈看 Taotoken 在高峰时段的 API 响应稳定性
  • 量子计算在化学模拟中的应用与iQCC方法解析
  • 【计算机毕业设计】基于 Python + PyTorch 的神经点云压缩实验系统(源码+数据库+文档+部署)
  • MySQL数据库表结构设计最佳实践_规范化设计提升查询性能
  • 数据中台不是终点,数据治理才是起点——2026六大主流平台对比与选型框架
  • 能量阀工厂
  • 2026环氧地坪漆、地坪漆环氧地坪源头厂家的靠谱推荐 哪家好 - 奔跑123
  • CANN/Ascend C开发套件