单片机/C/C++八股:(三十)UART 专题 ---- 下集

单片机/C/C++八股:(三十)UART 专题 ---- 下集 上一篇下一篇UART 专题 ---- 上集目 录5UART 的轮询/中断/DMA 接收方式对比6UART 的环形缓冲区7UART、RS-232、RS-485 的关联与区别8USB 转 TTL 模块的 VCC TX8.1VCC 接不接8.2TX 电压倒灌9在 RTOS 中如何正确使用 UART5UART 的轮询/中断/DMA 接收方式对比轮询工作原理CPU 在主循环或专用函数中不停地读状态寄存器检查 RX 缓冲区是否有数据有就立刻读走优缺点实现最简单但会完全阻塞 CPU极易丢字节适用极低速、可靠性要求不高中断工作原理当 RX 寄存器收到 1 或多个字节时产生中断CPU 在 ISR 中快速读出数据并存入软件缓冲区主程序再进一步处理优缺点CPU 不用死等只在真正有数据时才响应但每个字节或几个字节都产生一次中断高速率下中断频率过高会占用大量 CPU 时间且可能影响其他实时任务适用中等及以下速率或者数据量不持续的场景DMA工作原理DMA 控制器替代 CPU 完成数据搬运。MCU 预先配置好 DMA 通道源地址 → UART 数据寄存器目标地址 → 内存缓冲区当 UART 收到数据时硬件自动把数据搬到内存整个过程 CPU 几乎不参与。DMA 传输完成后可通过中断通知 CPU 处理整段数据优缺点CPU 开销极低尤其适合高速、持续、大块数据传输比如 1 Mbps 以上但配置较复杂需要预先分配连续物理内存缓冲区且对不定长、断断续续的数据需要配合超时中断来判断帧结束否则只有 DMA 完成中断不够适用高速串口、持续大数据流如无线模块透传、固件 OTA 升级、低功耗模式下的数据接收总结轮询简单但阻塞严重中断省 CPU 但高速下中断太多DMA 是高速和低功耗的利器但需要缓冲区和超时机制配合。6UART 的环形缓冲区具体参考单片机开发进阶技巧中的“环形缓冲区”。定义环形缓冲区是一种固定大小的循环队列用于在中断服务程序与主循环或任务之间高效传递数据流。它包含一个线性数组和两个位置标记读与写实现异步、无锁的数据存取。实现传统方法用数组下标作为读指针tail和写指针head每次操作通过取模计算回绕位置并预留一个空位以区分“满”和“空”。改进的“计数器”法使用单调递增的无符号整型 in 和 out 累计读写字节物理下标通过in(size-1)和out(size-1)快速映射借助无符号减法直接判断空满无需预留空间并避免了取模开销特别适合中断内高速处理。和 FreeRTOS 内核源码里的队列的区别是什么自定义环形缓冲区用纯内存操作实现极致低延迟的无锁流式传输而 FreeRTOS 队列由内核管理提供任务阻塞唤醒机制但伴随调度与临界区开销适合传递离散消息而非高频字节流。7UART、RS-232、RS-485 的关联与区别UART 就是我们常说的单片机串口串行通信接口而 RS232 和 RS485 是 UART 在物理层的扩展本质上用的都是 UART 协议只是电气特性不一样了。特性UART (TTL/CMOS)RS-232RS-485逻辑方式正逻辑VCC1, GND0负逻辑-3~-15V1, 3~15V0差分信号A-B 电压差2~6V1, -2~-6V0信号类型单端单端平衡差分典型电平3.3V / 5V±12V转换芯片产生由收发器决定共模范围宽传输距离1米板级15米左右1200米以上低速节点数量点对点点对点多节点32/128/256 个从机双工方式全双工TXRX全双工半双工两线制或全双工四线制抗干扰能力弱一般强差分抑制共模干扰是否需要转换芯片否MCU 直接输出需要 (MAX232 等)需要 (MAX485 等)典型接口/场景MCU 引脚、调试串口DB9 接口、PC 串口、老式设备工业总线、Modbus RTU、远程控制核心结论同一个 UART 协议配上不同的物理层就变成了板级的 TTL、中距离的 RS-232、长距离多节点的 RS-485。8USB 转 TTL 模块的 VCC TX8.1VCC 接不接市面上绝大多数 USB 转 TTL 模块上的 VCC 引脚是输出脚不是输入脚。模块本身通过 USB 口供电不需要外部给它供电。板上的 VCC 引脚是用来给目标板比如没有外部供电的 Arduino Pro Mini、空板 MCU 等提供 3.3V 或 5V 电源的旁边通常会有个跳线帽让你选择输出电压。工作时模块从 USB 总线的 5V 取电经过内部 LDO 降压出 3.3V或者直通 5V然后从 VCC 引脚输出。使用时使用 USB 转 TTL 模块连接电脑和单片机时如果目标板自己已经有供电那么 USB 转 TTL 模块的 VCC 就不要接在板子的 VCC 引脚了只有目标板需要从工具取电时才接。另外两个串口设备互联只接 TX/RX/GNDVCC 绝对不能接否则电源输出对输出会倒灌甚至烧器件。8.2TX 电压倒灌其实这个更偏向于硬件知识不论是开发板还是 USB 转 TTL 模块上的 TX 都是强推挽输出模式只要供电了那么 TX 空闲时为高电平TTL 通常是 3.3V 或 5V但驱动能力极弱一般只有几 mA只能传信号不能当电源用。不过虽然不能当电源用但也是会输出电压的⟶ \longrightarrow⟶我们经常会用 USB 转 TTL 模块来查看 printf 打印的日志但是有时候在给板子上电的时候需要把这个模块从板子上拔掉不然模块的TX会给目标板灌电会导致上电失败。具体如下USB 转 TTL 模块的 TX 是强推挽输出空闲时固定输出 3.3V或 5V高电平这个 TX 连到目标板 MCU 的 RXD 引脚MCU 的每个 I/O 脚内部都有 ESD 保护二极管阳极接地、阴极接 VCC电源正极当目标板断电时VCC 电压为 0V但外部 TX 加在 RXD 上的 3.3V 会通过这个上钳位二极管直接倒灌到 VCC 上给整个板的电源轨“偷电”。结果就是目标板虽然关了电VCC 上却残留1.5V ~ 2.5V 左右的“鬼电压”。上电时由于 MCU 不是从 0V 干净起步内部 POR上电复位电路检测不到完整的上电沿复位不完全导致MCU 状态机跑飞、启动失败、甚至锁死。↑ \uparrow↑这就是 TX 电压倒灌。工程上的解决办法上电前先拔串口土办法但可靠。串口线上串 1kΩ 隔离电阻限制倒灌电流。用电平转换芯片如 TXB0104它具有自动方向检测和输出高阻特性。在串口 TX 与目标板 RX 之间加一个 MOS 管隔断目标板没电时 MOS 截止。目标板设计时在电源入口加一个 理想二极管电路防止外部倒灌。……当然如果不需要电脑给板子发指令的话不要接模块的 TX 就行了。9在 RTOS 中如何正确使用 UART数据接收中断 消息队列 处理任务生产者-消费者模型中断服务函数ISR只做最轻量的事比如从 UART 的 data 寄存器读出字节通过消息队列或环形缓冲区丢给任务。信号量/任务通知ISR 发信号量或直接唤醒一个阻塞在接收上的任务。接收任务一个专门的任务阻塞在消息队列或信号量上被唤醒后从环形缓冲里取出一帧数据做完整协议解析。图示┌───────────────┐ │ UART RX ISR │ └──────┬────────┘ │ ①读硬件接收寄存器 │ ②写入环形缓冲区 │ ③发送信号量(FromISR) │ (或任务通知) ▼ ┌───────────────┐ │ 接收任务 │ └──────┬────────┘ │ ④等待信号量(阻塞) │ ⑤从环形缓冲读数据 │ ⑥协议解析 │ ⑦通过消息队列 → 发送给应用任务 ▼ ┌───────────────┐ │ 应用任务 │ │ (处理指令/数据) │ └───────────────┘数据发送互斥量保护 非阻塞发送互斥量如果多个任务都可能发送数据比如 CLI 调试任务、指令上报任务必须用一把互斥锁保护 UART 发送资源防止多任务同时写寄存器导致数据串帧。轮询发送任务拿锁后逐个字节发送数据直到 TC 完成。简单但阻塞时间长适合少量数据。DMA发送。图示┌───────────┐ ┌───────────┐ │ 应用任务A │ │ 应用任务B │ └─────┬─────┘ └─────┬─────┘ │ │ └────────┬──────────┘ │ ①获取互斥锁阻塞 ▼ ┌─────────────────┐ │ 临界区发送 │ ← 可以开启临界区保护也可以不开启 ├─────────────────┤ │ ②循环写入 TX 寄存器│ │ 等待 TC 标志置位 │ │ 一个一个字节发 │ │ ③释放互斥锁 │ └─────────────────┘防止阻塞和死锁超时机制 合理优先级超时机制拿互斥锁、等待信号量、等待队列数据时全部带超时时间例如 100ms防止某个任务卡死导致整个系统串口功能失效。合理的任务优先级UART 处理任务要根据实时性需求分配优先级。指令响应类如串口控制命令优先级要高日志打印类优先级可适当降低避免干扰控制逻辑。优先级反转防护如果使用互斥量确保开启优先级继承功能FreeRTOS默认开启防止中优先级任务阻塞高优先级任务。———— 总结 ——————RTOS 中的 UART 设计核心就是串口中断轻量化、数据流用队列/信号量解耦、多任务发送用互斥锁串行化、所有阻塞调用带超时这样在保证实时性的同时避免死锁和数据竞争。