ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

UART传输时间计算详解:从帧格式到实际延迟测量

UART传输时间计算详解:从帧格式到实际延迟测量 1. 为什么突然纠结UART传输时间如果你在嵌入式或上位机开发里碰过串口肯定遇到过这个问题上位机发了一帧数据过去为什么设备偶尔回得慢或者你用串口助手给一个传感器发查询指令明明只有几十个字节可响应时间就是不稳定甚至调试超时。排除掉设备本身处理慢的因素后最容易被忽略的一个变量就是UART传输时间本身。很多人以为UART传输时间就是“数据量除以波特率”比如115200波特率传1000个字节大概1000 / 115200 8.7毫秒。这个算法方向没错但漏掉了非常关键的帧开销。UART是异步串口每个字节在线路上并不仅仅发送8个数据位它前面必须加起始位后面必须加停止位如果开了校验位还要再加一位。也就是说在8N1模式下一个字节实际要占10个bit位的时间而不是8个。这一篇就把UART传输时间彻底讲透。我会从帧格式和波特率的基本概念入手把115200、8N1、7位数据模式这些常见配置下的传输时间计算过程一步步拆开再结合我实际调试中遇到的FT231X、FT232R这一类USB转串口芯片带来的延迟问题聊聊为什么理论计算和实测数据经常对不上。适合刚入门串口通信的嵌入式开发、自动化设备调试工程师以及写上位机软件但一直被串口超时问题折磨的朋友。2. UART传输时间计算的两个基本功帧格式与波特率2.1 一个字节在线上到底占多少时间UART的全称是Universal Asynchronous Receiver/Transmitter通用异步收发器。它之所以叫“异步”是因为收发双方没有独立的时钟线发送端和接收端各自用自己的时钟去采样。要让两边采样节奏一致就必须提前约定两个参数波特率和帧格式。波特率决定了每秒钟发送多少个bit单位是bps。在UART场景下因为每个bit只携带一个二进制符号所以波特率就等于比特率。115200波特率的意思就是每秒发送115200个bit每个bit的持续时间是1 / 115200 ≈ 8.68微秒这一个数是所有UART传输时间计算的基石。但要注意一个字节在线上占用的时间不是8个bit时间而是完整帧的bit数乘以位时间。拿最常用的8N1来说8表示数据位8个bitN表示无校验位1表示1个停止位。再加上一个固定的起始位整个帧就是1个起始位 8个数据位 0个校验位 1个停止位 10个bit所以在115200波特率下发送一个8N1字节的时间是10 × 8.68微秒 ≈ 86.8微秒如果把这86.8微秒换算成每秒能传多少字节就是1000000 / 86.8 ≈ 11520字节每秒。注意这里得到的11520字节每秒并不是115200除以8而是115200除以10。这少掉的20%左右就是你为异步传输的握手机制付出的代价。2.2 8N1为什么是10bit而不是8bit很多初学者会问就传一个字节的数据为什么非要额外塞两位进去起始位和停止位到底有什么不可替代的作用我们可以把UART通信类比成两个人隔着马路喊话。喊话之前你总得先发出一个“听好了我开始说了”的信号这就是起始位。说完了又得喊一个“我说完了”让对面知道该停下来重新准备接收这就是停止位。如果没有这两个信号接收方完全不知道从哪个时刻开始采样更不知道一个数据帧在哪里结束。从电平上看UART线路空闲时保持高电平。发送起始位时发送端会把线路拉低一个bit时间。接收端正是通过检测这个下降沿来判断“数据来了”然后从起始位之后的数据位开始按照约定的波特率在每一位的中心点附近采样。停止位则负责把线路拉回高电平表示这一帧结束同时给下一次起始位下降沿检测留出一个明确的空闲边界。所以8N1模式下的这10个bit一个都不能少。当然如果你改用7N1或者8N2帧结构又会不同。8N1之所以成为行业默认配置是因为它在数据效率和可靠性之间取得了很好的平衡绝大多数串口设备出厂默认都是8N1。2.3 7位数据模式到底是怎么回事不少人第一次看到串口配置里有“7位数据模式”时会下意识认为它比8位模式快因为数据位少了1位。这个理解对了一半最终时间取决于校验位和停止位的配置。7位数据模式通常出现在两类场景里一类是传输纯ASCII字符的老式终端设备和金融终端ASCII码本身只用到0到1277个bit已经完全够用另一类是Modbus ASCII等协议为了兼容历史硬件数据帧按ASCII码传输并且常配成7位数据加偶校验。以7E1为例E表示偶校验帧结构是1个起始位 7个数据位 1个校验位 1个停止位 10个bit这个总位数和8N1完全一样所以115200波特率下7E1的每字节传输时间依然是86.8微秒左右并没有更快。只有在7N1这种“7位数据、无校验、1个停止位”的配置下帧总长才变成9个bit每字节时间缩短到78.1微秒理论吞吐量提升到12800字节每秒。2.4 记住这个公式所有帧格式都通用不管是什么帧格式UART一帧的bit数都可以归纳成帧总bit数 1个起始位 数据位数 校验位数 停止位数单个字节的传输时间则是字段传输时间 帧总bit数 ÷ 波特率批量传输时间的理论值就是总时间 单个字节传输时间 × 字节数这个公式虽然简单但足够应付绝大多数计算场景。只要搞清楚设备的串口参数不管是8N1、7E1、7O1还是8N2都能直接套用。后面我会在完整例子中演示怎么用。3. 从115200出发步进拆解计算过程3.1 先算位时间再算字节时间我在实际项目中很少直接把“总bit数除以波特率”一步算完因为这样容易出错。我习惯分成两步先算位时间再算一帧的字节时间最后乘字节数。前面已经算过115200的位时间是8.68微秒下面把常见的帧格式整理成一个表方便你在调试时直接对照。帧格式起始位数据位校验位停止位帧总bit数115200下每字节时间115200下理论极限吞吐8N118011086.81微秒11520字节/秒8N218021195.49微秒10473字节/秒8E118111195.49微秒10473字节/秒7N11701978.13微秒12800字节/秒7E117111086.81微秒11520字节/秒7E217121195.49微秒10473字节/秒从这张表可以看得很明白想让串口传得快不是光看数据位数量而是看整个帧总bit数。8N1和7E1的时间完全一样7E2或者8N2这种双停止位配置则会明显更慢。3.2 完整消息的时间怎么算假设我们要通过UART发送一条100字节的指令给下位机下位机收到后立即回一条200字节的数据。波特率115200串口参数8N1。100字节的发送时间100 × 10 / 115200 0.00868秒 8.68毫秒200字节的发送时间200 × 10 / 115200 0.01736秒 17.36毫秒整轮收发的纯线路时间大约是26.04毫秒。看到这个数你就应该明白如果上位机设置的接收超时是5毫秒那它肯定永远等不到响应因为光数据在线上跑的时间就超过了超时时间。常见的一个误区是直接用字节数除以波特率。比如100字节直接算100 / 115200得到0.868毫秒这比实际时间少了整整10倍。原因就是把“字节”当成了“bit”漏掉了每字节有10个bit、以及8bit数据之外还有2bit开销这件事。3.3 用手算核对上位机超时配置在实际工程里手算传输时间最有用的地方就是设置超时时间。我踩过不少坑今天把这个经验分享出来。上位机通过串口给设备发查询命令一般流程是发送请求等待设备处理接收响应。这里的等待时间由三部分构成发送时间 设备处理时间 接收时间发送和接收的线路时间能用公式算出来设备处理时间只能通过实测或者看设备手册确定。比如设备手册说“最大响应时间200毫秒”那上位机的接收超时时间至少要设成发送请求的线路时间 200毫秒 接收响应的线路时间再留出至少20%到30%的余量。举个例子请求30字节响应100字节8N1115200波特率。发送时间是30 × 10 / 115200 2.6毫秒接收时间是100 × 10 / 115200 8.68毫秒加上设备处理200毫秒总计约211.28毫秒。再加上30%余量超时时间建议设280到300毫秒左右。如果你图省事随便填一个50毫秒那这个上位机基本没法正常用。3.4 用代码快速替代手算手算适合单次估算但如果你经常需要做方案评估或者要同时比较不同波特率、不同帧格式下的时间那最好写个小工具。我这边的习惯是用Python写个函数把帧格式参数化def uart_transmit_time(baud, data_bits, parity, stop_bits, byte_count, gap_seconds0.0): parity_bits 1 if parity.upper() in (E, O) else 0 frame_bits 1 data_bits parity_bits stop_bits bit_time 1.0 / baud frame_time frame_bits * bit_time total_time frame_time * byte_count gap_seconds * max(0, byte_count - 1) return total_time # 115200, 8N1, 1024字节 t uart_transmit_time(115200, 8, N, 1, 1024) print(t * 1000) # 输出毫秒约88.889ms如果是在MCU里做方案估算可以用C写同样逻辑float bit_time_us 1000000.0f / baud; float frame_time_us (1 data_bits parity_bits stop_bits) * bit_time_us; float total_time_ms frame_time_us * byte_count / 1000.0f;把data_bits、parity_bits、stop_bits换成实际配置就能快速得到结果。4. 比理论更重要端到端延迟从哪里来4.1 USB转串口芯片带来的缓冲与USB帧延迟理论线路时间算完了但如果你用的是USB转串口适配器实测延时通常会比理论值大不少。最常见的情况是电脑端用FT232R、FT231X这类USB转UART芯片它们内部都带收发缓冲区而且数据要通过USB总线和上位机驱动交互。USB设备通信不是实时中断式的FTDI这类芯片通过批量端点传输数据批量传输的调度周期在USB全速模式下通常是1毫秒一帧高速模式是125微秒一帧但实际还要看操作系统底层驱动怎么处理。也就是说你从应用层调用写串口接口数据从应用层到驱动缓冲区再到USB控制器最后到芯片的发送FIFO中间每一级都可能引入1到几毫秒的额外延迟。举一个我实际遇到的问题单片机每50毫秒上传一包64字节的数据我用串口助手观察理论上64字节在115200 8N1下的线路时间只有5.56毫秒但上位机每包数据之间的时间戳间隔却经常是52到56毫秒偶尔还会跳到58毫秒。问题不是单片机发送变慢而是USB转串口芯片的缓冲和操作系统的驱动调度把数据攒了一批之后才让应用层读到。所以你在计算端到端时间时一定要分清“线路时间”和“系统响应时间”。线路时间不管用不用USB转串口都一样但系统响应时间必须把USB芯片和驱动的延迟算进去。4.2 驱动层与串口助手的缓冲问题FT232R和FT231X这两种芯片算是市面上最经典的USB转串口方案了。FT232R是多年前就很常见的型号很多开发板上的蓝色USB转串口小板用的就是它驱动装好后在系统里显示成一个COM口。FT231X则是相对后期的型号封装更小也支持更高的波特率但驱动模型和应用层接口基本兼容FTDI体系。无论哪一种默认驱动的接收缓冲都可能达到几百到上千字节。这意味着下位机快速发送的数据会先积压在驱动缓冲区里如果应用层读取不及时你看到的数据就是“一阵一阵”到达的。这会造成一个假象似乎数据不是均匀传输的。若用串口助手测量响应时间串口助手的定时刷新机制还会进一步引入误差。因此我建议凡是涉及严格时间测量或者实时控制的场景尽量别用普通串口助手做基准。要么用示波器或逻辑分析仪直接钩TX、RX引脚要么在单片机上用中断和定时器记录时间戳再通过另一条通道打印出来。4.3 实测和理论对不上的常见原因把理论时间和实测数据放在一起看你会发现偏差通常来自几个方向。首先发送方在两次发送之间可能插入了软件延时比如有人会在循环里加一个delay认为这样更“稳”结果每字节之间多出几个毫秒。其次RS485收发器存在方向切换时间发送完一帧后DE引脚拉低释放总线也需要时间如果这个延时没留够接收方甚至可能丢最后一个字节。再次接收方的处理时间被误算成了传输时间导致整体看起来“慢”。还有一个坑是流控。如果硬件流控RTS/CTS或者软件流控XON/XOFF被错误开启了发送端可能因为对方缓冲区满而暂停发送。暂停期间线路是空闲的但总耗时会被拉长。排查这类问题最好的办法是拿逻辑分析仪抓实际波形看相邻字节的间隔到底是多少而不是盯着串口助手的接收时间戳猜。5. 实测UART传输时间的方法5.1 用逻辑分析仪测起始位到停止位要获得一串数据的真实传输时间最直接的办法是逻辑分析仪钩在TX引脚上设置为触发下降沿然后抓取完整波特率波形。UART空闲时是高电平。发送端开始发一帧数据时会先拉低一个bit时间作为起始位随后从数据位的最低位LSB开始依次发送。8N1模式下一个字节的波形就是1个低电平起始位8个数据位最后1个回到高电平的停止位。在逻辑分析仪里你可以把协议解析器配成115200 8N1软件会自动标出帧起始和停止位置从而精确读出每字节耗时。如果你只有示波器也可以用示波器测第一个下降沿和最后一个停止位上升沿之间的时间再减去停止位之后到下一字节的空闲间隔同样能反推出单帧时间。5.2 从波形反推波特率和帧格式有时候你会碰到一个陌生设备不清楚它到底配的什么串口参数。只要能看到波形就能反推。先在示波器或逻辑分析仪上测量最短的电平持续时间。比如看到一个最短低电平持续8.68微秒那波特率就是115200。如果最短电平时间是104.17微秒那波特率就是9600。再数一帧数据里包含多少个bit。一个典型UART帧从下降沿开始数到停止位开始如果能看到起始位加8个数据位那就是8位数据模式如果起始位加7个数据位就是7位数据模式。在数据位之后如果还有一个比数据位宽度相同的电平才回到停止位说明有校验位如果停止位宽度是正常的1个bit自然对应1个停止位如果停止位之后保持高电平时间是两个bit宽度那就是2个停止位。我之前调试过一个老款仪表手册写的是9600 8N1但抓波形发现每个字节只有9个bit进一步数出来是1个起始位加7个数据位再加1个停止位实际是7N1。修改串口配置后通信立刻正常。所以别完全相信设备标签波形不会骗人。5.3 不同波特率下实际传输速率对照把前面公式套到几种常见波特率上能得出一个很直观的对照结果方便你选型时估算。波特率8N1每字节时间理论极限Bps96001.0417毫秒960字节/秒192000.5208毫秒1920字节/秒384000.2604毫秒3840字节/秒576000.1736毫秒5760字节/秒11520086.81微秒11520字节/秒92160010.85微秒92160字节/秒从9600升到115200传输速度快了12倍。这也是为什么很多需要批量数据交互的串口设备产品手册里默认波特率会写到115200甚至更高。但波特率不是越高越好线路距离、线材质量、电气干扰都会影响高速传输的稳定性。6. 常见问题与避坑清单6.1 波特率分频误差怎么避免UART传输时间计算的前提是波特率准确。如果MCU时钟分频算出的实际波特率和标称值偏差太大接收端采样就会错位轻则数据偶尔出错重则一帧都收不到。这里有笔典型账如果MCU使用16MHz晶振要产生115200波特率常用的16倍过采样分频值是16MHz / (16 × 115200) ≈ 8.68取整后误差可能达到3.5%左右。这个误差已经接近甚至超过常规UART容忍阈值在8N1一帧10bit的累计偏差下很容易出现误码。所以我做项目时会优先选择14.7456MHz晶振或者使用内部PLL支持小数分频的MCU确保115200这个波特率能整数分频。如果你在调试中遇到“偶尔乱码、速率越高越严重、示波器测频率和标称差好几个百分点”这类问题先查时钟源。把串口波特率降到9600试试如果9600完全正常、115200乱码多半就是分频误差或晶振偏差造成的不要急着怀疑对方设备。6.2 发送间隔和帧间隙怎么处理连续的UART字节理论上是一个接一个紧挨着发出去的字节之间没有额外空隙。但很多实际应用会故意插入帧间隙比如Modbus RTU要求一帧报文内部相邻字符的间隔不能超过1.5个字符时间整个报文结束之后要有3.5个字符时间的静止间隔才代表一帧结束。如果发送端在每字节之间插入了延时计算总时间时就要把这些间隔加进去。一个比较粗糙但可靠的估算是总时间 帧数据时间 帧间隔时间。例如Modbus RTU的3.5字符间隔在115200 8N1下大约是3.5 × 86.8微秒 ≈ 304微秒。如果一帧有8个字节空闲间隔是304微秒那么一帧实际占用时间就是8 × 86.8 304 ≈ 998微秒接近1毫秒。RS485方向切换是另一个必须考虑的“隐形时间”。很多RS485收发器的DE引脚从发送切到接收需要几百纳秒到几微秒但为了保险切换延时一般会设成一个字符时间以上。比如115200波特率下方向切换延时至少留86.8微秒。否则最后一个字节的停止位还没完全发完就把收发器切到接收模式总线被拉高或者被对方驱动导致误码。6.3 超时时间设置的经验值串口通信里的超时时间设置算是新手翻车重灾区。如果设得太短设备响应稍慢就报错如果设得太长故障检测速度又太慢。我一般按下面的公式设置接收超时超时时间 发送耗时 设备最大处理时间 接收最大数据耗时 裕量其中裕量至少是前几项总和的20%。如果通信链路里还有USB转串口那就把裕量再放大一些因为驱动调度和USB帧周期可能给你额外多出几个毫秒。对于短报文场景比如上位机发送一条8字节指令设备返回16字节115200 8N18字节发送耗时0.69毫秒16字节接收耗时1.39毫秒。如果设备处理时间是10毫秒那超时设为15到20毫秒是合理的。但如果你发现Windows上位机用FT232R转发时偶尔会在临界值附近超时那就要把超时加到30到50毫秒换取稳定性。6.4 7位数据模式下的三个注意点如果你要接触Modbus ASCII或老式终端设备7位数据模式会遇到几个特殊问题。一是校验位不要配错。7位数据模式下很多设备默认带奇偶校验最常见的是7E1和7O1。“无校验”要用7N1但并不是所有设备都支持7N1所以配置前一定确认清楚。二是ASCII码高位处理。7位数据模式只支持0到127的字符如果你发送了大于127的扩展ASCII码或二进制字节数据会被截断或直接出错。所以采用7位模式时应用层必须确保所有数据都落在ASCII可打印字符范围内。三是时间计算别想当然。7E1的帧总bit数是10跟8N1一样没有传输速度优势。真正比8N1快的是7N1但7N1少了校验功能在线路质量一般的环境里可靠性会下降。选型时要在速度和可靠性之间做权衡不能只看数据位数量。最后分享一个我的习惯调试串口算时间这件事我后来养成了一个习惯不论什么场景先把最保守的估算做出来。所谓最保守就是把每字节按11个bit算而不是按实际帧格式的10个bit算再把驱动和USB延迟按2到3毫秒估算进去。这样做出的超时配置往往不会因为边界情况翻车。比如115200波特率下传100字节按11bit每字节算时间是100 × 11 / 115200 ≈ 9.55毫秒再加上3毫秒驱动延迟留给对方响应的时间就按13到15毫秒起步。虽然看上去比理论值宽了不少但实际调试中很少因为超时误报去回头改参数。真等你要追求极限吞吐的时候再用逻辑分析仪精确测量把延迟抠到最小也不迟。
返回列表