首页 / 资讯中心 / 文章详情

UART异步通信协议详解:从帧结构、波形分析到串口实战避坑

UART异步通信协议详解:从帧结构、波形分析到串口实战避坑 ★ FEATURED ARTICLE
第一天这三个字对我而言一直是嵌入式学习里最有分量的一个起点。很多朋友问我从哪学起我给的建议基本都是先把 UART 异步通信协议调通。开发板点灯只是看见一个高低电平串口通信才真正让板子和外界交换数据——调试日志、AT指令、传感器数据、上位机交互背后都是 UART。这篇文章是我整理的一份从零开始的 UART 异步通信协议笔记。你会弄懂异步到底怎么同步、一帧数据的位结构、用逻辑分析仪怎么看波形、最小驱动代码怎么写以及我实测中踩过的三个最常见的大坑。适合刚接触单片机、想做板卡与电脑之间通信、或者打算接下来学 SPI/I2C 的同学。按我的经验花一天把 UART 吃透后面看任何协议都会顺很多。1. 为什么要从UART开始学通信协议1.1 调试日志、传感器模块、上位机串口是万能口先说一个很直观的事实几乎所有单片机都带 UART 外设哪怕是没有 USB 口的芯片只要把 TX、RX 两根线引出来就能和电脑通信。日常开发里你总得往电脑上打印点调试信息吧打印日志靠的就是串口。很多外设模块——GPS、蓝牙、指纹识别、空气质量传感器、4G 模组——也都默认用 UART 输出数据。你拿一个 USB 转 TTL 小板接上 TX、RX、GND打开串口助手选好波特率就能开始收发指令和数据。我还见过一种情况有些开发板自带 USB 口用户以为用的是 USB 协议其实板子内部也只是把单片机的 UART 引脚通过一颗 USB 转串口芯片桥接出来。也就是说串口在嵌入式系统里的角色远比想象中普及。它同时承担监控口和控制口两种角色一方面把系统内部状态吐给开发者看另一方面接收上位机下发的配置指令。UART 不是最炫的协议但它是成本最低、最不容易缺位的通信通道。1.2 较早搞懂位、帧、时序意味着什么为什么我强烈建议把 UART 作为通信协议的第一课因为它的协议规则很少但麻雀虽小五脏俱全。学习任何通信协议核心就三件事数据怎么被切分成一帧一帧的帧里的每个位按什么顺序排列收发双方在哪个时间点去读这些位。UART 恰好把这三个要素全部覆盖。对比一下后面的协议SPI 有时钟线和片选线I2C 有地址和起始停止条件CAN 有仲裁和位填充。它们各自的规则更多但底层的位—帧—时序思维和 UART 完全一致。你如果在第一天就能理解一帧数据包含起始位、数据位、停止位这个概念后面学任何带时钟线的协议都会轻松很多。我见过一些同学用库函数调串口收发已经跑得很溜但问他为什么发送 0x55 时示波器上会看到那样一串波形他就答不上来了。这就是只学接口、没学协议。第一天我更希望你先成为后者至少心里有波形。2. 异步通信的底层规则没有时钟线靠什么对齐2.1 异步不是没有节奏而是节奏藏在数据线里UART 全称是 Universal Asynchronous Receiver/Transmitter关键词就是这个 Asynchronous。同步通信比如 SPI会单独拉一条时钟线 SCLK主机在时钟边沿告诉从机现在这个时刻数据有效。UART 只有 TX、RX 两根数据线没有时钟线。那收发双方怎么知道每一位什么时候结束、什么时候该采下一个位答案是双方提前约定一个节奏也就是波特率。更准确地说UART 的设计是让接收端通过数据线上的边沿变化来恢复时钟节拍。导线空闲时保持高电平一旦发送端想发数据先拉低一个位时间这个下降沿就是起跑令——接收端看到它就知道一帧开始了。之后双方按约定的速度沿着每个位的中间位置去采样数据。用一个生活例子辅助理解一个合唱团在没有节拍器的情况下合唱每个人必须用相同的演唱速度但需要一个指挥在开头喊一声预备。UART 的起始位就是这个预备波特率就是大家说好的演唱速度。节奏一旦不同后面必然唱到一块去。2.2 一帧数据长什么样起始位、数据位、校验位、停止位明确了异步的大思路接下来看帧结构。UART 的一帧数据由四个部分顺序组成起始位1 位低电平。它标志着帧的开始。数据位5 到 9 位不等最常见是 8 位顺序是先发最低位也就是 LSB first。校验位可选。奇校验或偶校验让数据位加上校验位里1的总数为奇数或偶数。停止位1 位或 2 位高电平。它标志着帧的结束。平时总听到的8N1表示 8 个数据位、无校验、1 个停止位。对应还有 8E1、8O1、7E1 这类说法。E 是偶校验O 是奇校验。偶校验的规则是数据位和校验位里的 1 加起来必须是偶数。举个例子0x55 有 4 个 1偶校验位就填 0如果数据是 0x01只有 1 个 1偶校验位就要填 1。校验位只能检测单比特翻转不能纠错但对链路噪声排查还是有用的。为什么不能没有起始位和停止位因为它们定义了帧边界。接收端需要知道从哪里开始算一帧、到哪里结束。停止位的高电平还有个作用给线路一个明确的空闲标志也给了接收端整理状态的时间。如果两个字节连续发送上一帧的停止位和下一帧的起始位会形成一个高→低的边沿方便接收端重新同步。2.3 波特率计算每秒多少位每位多长波特率的字面意思是每秒传输的符号数。因为 UART 里每个符号就是 1 个比特所以通常可以认为波特率就等于比特率。把这个定义倒过来就能算出每一位持续多长时间每位时间 1 / 波特率举个例子115200 bps 时每位时间约为 8.68 微秒。这个数字很有用后面用逻辑分析仪量波形时直接拿它跟实测位宽对比就能判断配置是否正确。波特率每位时间9600约 104.17 微秒19200约 52.08 微秒38400约 26.04 微秒57600约 17.36 微秒115200约 8.68 微秒那么 UART 的波特率能不能有一点误差实际是可以的。接收端通常用 16 倍波特率时钟对数据线采样起始位下降沿出现后它数到第 8 个采样时钟就落在数据位的中心附近之后每隔 16 个采样时钟读一次。只要采样点没有偏离位中心太远收发双方存在 2%~3% 的波特率误差通常还能正确通信。零界点大概在采样点滑出位中心的时候误差一旦超过这个范围就必然乱码或丢字节。所以工程上要注意单片机使用外部晶振时波特率误差通常很小使用内部 RC 振荡器且没有校准误差可能接近 2%在 115200 这样的高速率下就比较容易出现不定时乱发数据的情况。这也是为什么很多开发板调试时默认用 9600 反而比 115200 稳定——低速时每位更长同样的时钟误差对采样点的影响更小。2.4 UART协议、TTL电平、RS-232、RS-485别把四者混为一谈很多新手把UART和TTL 电平当成一回事其实它们不是同一层面的东西。UART 定义的是帧格式和时序逻辑高电平代表 1低电平代表 0。TTL 电平只是物理实现的一种3.3V 或 5V 表示逻辑 10V 表示逻辑 0。RS-232 是另一种物理层标准它用负逻辑逻辑 1 用 -3V 到 -15V逻辑 0 用 3V 到 15V传输距离比 TTL 远适合几十米内的设备互联。RS-485 则使用差分信号通过 A、B 两条线上的电压差判断电平抗干扰能力强支持几百米传输和多个设备挂同一条总线。它们传输的内容仍然是 UART 帧格式物理层变了而已。理解这一点对选型很重要。电脑没有原生 TTL 串口所以我们常用 USB 转 TTL 小板来做电平转换。如果单片机是 3.3V 逻辑而 USB 转 TTL 小板输出 5V直接连接就可能遇到信号识别不了或者损坏引脚的问题。后面讲坑的时候还会专门提这个。3. 用逻辑分析仪把一帧数据还原成看得见的波形3.1 先发一个0x55波形会非常规律纸上谈兵的帧结构终究抽象最直观的办法是把波形抓出来看。我强烈建议你准备一个逻辑分析仪几十块钱的就行不需要高档示波器。测试时让单片机循环发送 0x55。0x55 的二进制是 0101 0101按 LSB first 发送数据位依次是 1、0、1、0、1、0、1、0。整帧波形会呈现出规律的高低交替阶段电平空闲高起始位低数据位0高数据位1低数据位2高数据位3低数据位4高数据位5低数据位6高数据位7低停止位高从表格能看出来0x55 的波形几乎是完美的方波起始位拉低之后每个数据位都在翻转最后停止位回到高电平。用逻辑分析仪的测量工具量相邻边沿之间的时间差正好就是一个位宽度。反过来如果你想确认自己理解没理解 LSB first发 0x55 后数一数第一个数据位是高还是低——按 LSB first最低位是 1所以起始位之后第一个数据位应该是高。如果软件里配置成了 MSB first波形第一个数据位就是低对照下来马上露馅。3.2 逻辑分析仪接线和参数设置的注意点逻辑分析仪测量 UART 时接线很简单把探头的信号线接目标设备的 TX 引脚GND 接设备的地。注意 GND 必须共地否则信号参考点不同抓到的波形可能完全是乱的。采样率设置很关键。理论上最低需要几倍于波特率实际建议至少 16 倍以上。比如分析 115200 的串口采样率最好设到 2MHz 甚至更高。采样率太低波形边沿会变得很糊解码也可能出错。解码参数里协议选择 UART有些工具叫 Serial 或 Async Serial波特率必须和发送端一致数据位、停止位、校验位也要对应。常见的坑是只改了波特率忘了把停止位从 1 改成 2或者忘了打开校验位。如果波形非常干净但解码结果乱码优先检查这些设置项不要急着怀疑电路。3.3 从波形反推波特率比查代码快得多逻辑分析仪最有价值的一点是能直接从波形反推波特率省去一大堆盲猜。量出一个位的时间 T波特率就等于 1/T。比如你量到相邻边沿间隔大约是 104 微秒那实际波特率就是 9600如果量到约 8.68 微秒就是 115200。我遇到乱码时的标准操作是这样的先抓发送端的 TX 波形用测量工具拉出几个位间隔算平均位宽判断发送端实际波特率是多少。如果实测值和配置值差得不多那问题多半在接收端的帧格式如果差了一大截比如配置 115200 但波形位宽明显是 9600 的那多半是代码里波特率寄存器算错了或者时钟分频选择出了问题。波形不会骗人。学会这一步你排查串口问题就基本脱离猜谜模式了。4. 从零驱动一个串口初始化、发送、接收的完整链路4.1 初始化之前先把五个参数定死驱动 UART 的代码在不同平台上写法不同但核心步骤完全一致。我建议你初始化之前先把下面五件事想清楚再动手写使用哪个 UART 外设以及对应的引脚。外设时钟是多少这决定波特率分频怎么算。波特率是多少。帧格式是几数据位、什么校验、几位停止位。收发模式是轮询、中断还是 DMA。通用初始化代码框架大致是这样void uart_init(uint32_t baud) { // 1. 使能UART外设和对应GPIO端口的时钟 // 2. 配置TX引脚为复用推挽输出RX引脚为浮空输入或上拉输入 // 3. 设置数据位为8位1个停止位无校验8N1 // 4. 计算分频系数divider peripheral_clock / (16 * baud) // 5. 使能发送器和接收器 // 6. 如果使用中断配置中断优先级并使能接收中断 }波特率分频系数怎么算绝大多数 UART 的时钟模型是目标波特率 外设时钟 / (16 × 分频系数)。所以分频系数 外设时钟 / (16 × 波特率)。举例外设时钟 72MHz目标 115200算出来分频系数约 39.06。现在很多芯片的分频寄存器支持小数位就是为了把 0.06 这部分尽量凑准减小波特率误差。如果芯片没有小数分频只能取整那 39.06 取整成 39再反推实际波特率是 72000000 / (16 × 39) ≈ 115384误差只有约 0.16%完全没问题。有个容易忽略的点GPIO 复用模式没配好信号根本到不了引脚。有些初学者初始化了 UART 外设也写了数据寄存器但 TX 引脚实际不在复用模式和普通 GPIO 一样悬空结果上位机什么都收不到。所以引脚复用配置这一步不能省。4.2 发送一字节先看标志位再写数据寄存器发送侧有两个核心标志位。TXE 表示发送数据寄存器空可以写入下一个要发的数据TC 表示整个帧已经彻底发送完成包括停止位都发出去了。第一天写代码用 TXE 就够void uart_send_byte(uint8_t data) { while (!(uart-SR UART_FLAG_TXE)); // 等待上一个字节写入完成 uart-DR data; // 写入数据寄存器 }注意区分TXE 置位说明可以往数据寄存器里填充下一个字节但不代表引脚上已经把数据发完了TC 置位才是整帧发送结束。如果程序在连续发送大量数据时只等 TXE 不等 TC数据仍然能发出去因为发送移位寄存器会自动把数据搬出。但有少数场景需要电平完全稳定比如切换 RS-485 方向时就得查 TC确保最后一帧彻底发完再拉低发送使能否则最后一个字节会被截断。为什么第一天先从轮询开始因为轮询不依赖中断系统代码路径非常直白只要硬件接线没问题它几乎一定能跑通。它能帮你排除是硬件问题还是软件问题这个大类别。等轮询调通之后再升级成中断发送思路会清晰很多。4.3 接收一字节RXNE标志和溢出错误接收侧的核心标志是 RXNE收到一整个字节后硬件自动置位程序读取数据寄存器就会清除它。轮询接收的骨架如下uint8_t uart_receive_byte(void) { while (!(uart-SR UART_FLAG_RXNE)); // 等待收到一字节 return (uint8_t)uart-DR; }这里藏着一个新手容易踩的坑如果数据来得太快程序还没来得及读走上一字节下一字节又接收完成UART 硬件就会触发溢出错误 ORE。后续所有新数据都可能被丢弃甚至影响 RXNE 标志的判断。标准清 ORE 的动线是先读状态寄存器 SR再读数据寄存器 DR两步一起才能把错误标志彻底清掉。只读 DR 不清 SR错误状态可能一直挂着表现为串口明明有数据进来程序却收不到。我建议你在写完整接收逻辑时先检查 SR 里的错误标志再决定是读数据还是清错误。轮询代码里最简单的容错就是发现 ORE 后把状态清掉继续跑别让一个错误锁死整个循环。4.4 轮询、中断、DMA第一天的取舍轮询适合第一天验证硬件链路但如果程序要在等待串口数据的间隙去处理其他事情轮询会占满 CPU。中断方式让你在收到数据时被打断去处理平时该干嘛干嘛这是工程中最常用的方案。DMA 则是让数据在外设和内存之间自动搬运CPU 完全不参与适合大数据量的日志输出和固件升级。三种方式对比大致如下方式CPU占用实时性代码复杂度适用场景轮询高中等低演示、简单收发中断低高中交互式数据、按键/指令DMA极低高取决于配置高批量日志、固件升级、大数据第一天不要碰 DMA。DMA 会牵扯缓冲区管理、半传输中断、环形队列、错误处理状态多调试复杂度一下子拉满。先把轮询、中断各写一遍理解CPU 主动去读和硬件通知 CPU 来读的区别后面上 DMA 就是水到渠成的事。我自己也是做到日志系统时才认真研究 DMA当时发现只要把缓冲区索引处理好DMA 并没那么可怕但前提是基本功已经打牢了。5. 实测串口最容易翻车的三个坑乱码、接反、电平绑架5.1 乱码的本质双方对一帧的理解不一致乱码是串口调试里出现频率最高的问题但大多数时候不是芯片坏了而是通信双方对一帧数据的理解不一致。最常见的四个原因波特率不同、数据位/停止位/校验位配置不同、GND 没共地、USB 转 TTL 小板的电平不对。排查乱码时我的顺序很固定用逻辑分析仪抓发送端 TX 波形。测量位宽算出实际波特率。核对收发双方的帧格式是否都是 8N1 或对应的 8E1。检查 GND 线是否牢固连接。最后才改代码。有一个容易迷惑人的现象发送端明明配置 115200代码里也写的 115200但波形显示实际波特率是 9600。这种通常是分频寄存器被别处代码改掉了或者时钟初始化顺序不对导致 UART 用的时钟源和预期不一致。不看波形光靠双眼瞪代码很难发现这类问题。另外提一句如果发的是中文文本上位机用不同的编码解码看起来也会是乱码但这属于编码问题不是串口问题。排查阶段先发纯 ASCII 字符比如字符A对应的十六进制 0x41能显著减少干扰变量。5.2 TX接TX是怎么发生的UART 接线规则只有一句发送方 TX 接接收方 RX接收方 TX 接发送方 RXGND 共地。看起来不难但新手接线时经常把两块开发板的 TX 接 TXRX 接 RX结果机器静悄悄。原因也好理解大家都觉得串口就是那两根线照着名字接总不会错实际把两个输出口怼在一起两边都在等对方先说话自然没有数据。如果只是想先验证自己的板子能不能自发自收可以把 TX 引脚和 RX 引脚直接用杜邦线短接然后代码里调用发送函数看到接收寄存器里回收到同样的数据链路就通了。和电脑通信时一定要记得交叉开发板 TX 接 USB 转 TTL 板的 RX开发板 RX 接 USB 转 TTL 板的 TX。如果确认接线没问题但就是收不到把两条信号线对调再试一次。这个操作听起来很基础但我至少见过两次明明插反了却排查了半个小时的案例这里面也包括我自己。5.3 电平不匹配和共地问题不能靠运气TTL 电平分 5V 和 3.3V 两套。5V 单片机 TX 输出的高电平是 5V如果对方是 3.3V 单片机且引脚不耐 5V轻则逻辑判断出错重则直接损坏 IO。反过来 3.3V 输出到 5V 输入多数时候能识别成高电平但也不是稳定可靠的设计。规范做法是加一个双向电平转换模块几块钱的东西别在这上面省钱。还有共地问题。串口信号的0V参考点就是 GND两个设备必须要用地线连起来。如果 GND 不接两边参考点不一样接收端看到的不再是完整的高低电平可能是奇奇怪怪的中间电压表现就是完全解不出数据或者收到满屏 0xFF。我调试串口的第一步永远是检查 GND然后再看 TX、RX。此外插拔杜邦线时最好断电操作。带电插拔不仅容易接触不良还可能因为瞬间电平抖动把引脚打坏。USB 转 TTL 小板插在电脑上电脑侧的电一直存在改接线时先把小板的 USB 端拔下来更稳妥。6. 第一天的验收清单与往后的学习路线6.1 今天能完成这几件事就算真的入门我给你一份比较现实的验收清单不用贪多只求每项都真正跑通开发板 TX 和 RX 短接程序自发自收能收到完整数据。通过 USB 转 TTL 小板连接电脑串口助手板子发字符串能被上位机正确显示。电脑通过串口助手发一个 0x55开发板能收回来并确认内容正确。用逻辑分析仪抓 TX 波形能指出哪一段是起始位、哪一段是数据位、哪一段是停止位。从波形上量出位宽反推出实际波特率。第 2 条和第 4 条是最关键的。只要这两条通了说明你对 UART 的理解已经不只是调个库的层面而是真的看懂了这个协议。我自己判断一个初学者有没有入门就看他能不能拿着逻辑分析仪的截图把整个帧讲明白。6.2 下一步值得看的几个点第一天的内容可以做几个方向的扩展。如果对帧格式还有兴趣可以研究校验位和数据位 9 位模式多机通信里常通过第 9 位来区分地址帧和数据帧。硬件流控 RTS/CTS 也值得了解高速传输时它能避免接收端缓冲区满了还把数据往外面发。如果接触工业现场RS-485 是绕不开的。它的核心难点在于发送结束后要切换收发方向控制引脚不能多拖也不能提前。还有 DMA 加环形缓冲区这套组合做固件升级和日志系统时非常实用等你把中断收发写熟练之后再上。6.3 先看波形再改代码说实话我最早学 UART 时也没一步到位。乱码、接反、电平不匹配全都遇到过。后来养成一个习惯先看波形再改代码。这个习惯帮我解决了很多看似玄学的问题。第一天能自己画出 0x55 的波形你就已经超过了只会用库调串口的同学。后面不管学 I2C、SPI 还是 CAN只要记得位、帧、时序这三个关键词都会轻松不少。
阅读完成 · 觉得有帮助?
咨询建站