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

USART波特率与SPI速率:嵌入式通信速度概念辨析与实战

USART波特率与SPI速率:嵌入式通信速度概念辨析与实战 ★ FEATURED ARTICLE
1. 串行通信里最容易被混淆的两个速度刚接触嵌入式开发那会儿我对波特率和速率这两个词是完全混着用的。配置USART的时候填个9600配置SPI的时候写个分频系数代码能跑通就完事从来没深究过这两个数字背后到底代表什么。直到有一次做一个双机通信的项目USART那边数据丢得莫名其妙SPI那边时序又对不上我才被迫把这两个概念彻底拆开来看。结果发现很多人对这两个速度的理解都存在一个共同的误区以为它们是一回事只是叫法不同。实际上USART的波特率和SPI的时钟速率从物理含义、计算方式、误差来源到实际能达到的吞吐量几乎每一个环节都不一样。你如果拿配置USART的思路去理解SPI或者反过来迟早会在某个项目里栽跟头。这篇内容就是把我自己踩过的坑和后来梳理清楚的知识点整理出来。不管你是刚学单片机串口通信的新手还是已经用过不少外设但没仔细想过底层原理的开发者看完之后应该能对这两个概念有一个清晰的分界线。我会从物理层的信号形态讲起把波特率的计算和误差来源拆开再把SPI的时钟速率和实际吞吐量的关系讲透最后给出几个实际项目中选型和配置的判断依据。2. 从信号形态看本质为什么USART叫波特率而SPI叫速率2.1 USART的波特率每秒多少个码元USART的全称是通用同步/异步收发器实际项目里绝大多数场景用的是它的异步模式也就是我们常说的串口。异步通信最大的特点是没有单独的时钟线收发双方靠事先约定好的时间基准来对齐数据位。这个时间基准就是波特率。波特率的单位是bpsbits per second字面意思是每秒传输的位数。但在USART的语境下它更准确的含义是每秒传输的码元数。在标准的UART帧格式里一个码元就是一个bit所以波特率和比特率在数值上是相等的。但这两个概念在通信原理层面是有区别的只是在USART这种二进制调制方式下恰好重合了。一个完整的UART帧包含起始位、数据位、校验位可选和停止位。假设配置为最常见的9600-8-N-1也就是波特率9600、8位数据位、无校验、1位停止位那么传输一个字节实际需要发送10个bit1个起始位 8个数据位 1个停止位。这意味着每秒钟实际能传输的有效字节数是9600除以10等于960字节。这个换算关系在实际项目里非常重要很多人算通信耗时的时候忘了把起始位和停止位算进去导致估算的传输时间和实际差了一大截。波特率的时钟来源通常是MCU的系统时钟经过分频得到的。以常见的16倍过采样为例USART外设会用波特率发生器对系统时钟进行分频产生一个频率为波特率16倍的采样时钟然后在每个bit周期的中间位置进行采样。为什么要16倍过采样因为异步通信没有时钟线收发双方的时钟不可能完全一致16倍过采样可以在一定程度上容忍时钟偏差同时在噪声抑制和采样精度之间取得平衡。2.2 SPI的速率时钟线跑多快SPI的全称是串行外设接口它是一种同步通信协议。和USART异步模式最大的区别在于SPI有一根专门的时钟线SCK由主机产生并发送给从机。收发双方共用同一个时钟信号所以不存在时钟偏差的问题。SPI的速率通常用时钟频率来表示单位是Hz赫兹或者MHz。比如你配置SPI时钟为10MHz意思就是SCK这根线上的时钟信号每秒振荡1000万次。每一个时钟周期传输1个bit所以理论上10MHz的SPI时钟对应10Mbps的比特率。但这里有一个容易忽略的点SPI的时钟频率并不等于数据吞吐率。因为SPI通信需要片选信号CS/SS的拉低和拉高来框定一次传输两次传输之间还有间隔。而且在实际编程中你通过SPI发送数据时CPU需要把数据写入发送寄存器等待传输完成再写下一个数据。这个软件开销在高频SPI下会成为瓶颈。所以你在数据手册上看到SPI支持50MHz实际用起来可能连10Mbps的有效吞吐量都跑不到原因就在这里。2.3 两者的本质差异对比把这两个概念放在一起对比差异就很明显了对比维度USART异步模式SPI时钟线无靠双方约定有主机提供SCK速率单位bps波特率Hz时钟频率速率上限通常几十kbps到几Mbps通常几MHz到几十MHz时钟偏差容忍有要求一般不超过2%-3%无要求共用时钟每字节额外开销起始位停止位至少2bit无固定开销但CS有间隔全双工支持独立收发线支持独立收发线多设备支持点对点为主一主多从靠CS选择这张表里的每一行背后都有实际项目中的坑。比如时钟偏差容忍这一项我在一个项目里用过内部RC振荡器作为USART时钟源常温下通信正常一到冬天户外设备就频繁丢包。后来换成外部晶振才解决。而SPI因为共用时钟线从来不用担心这个问题。3. 波特率计算中的误差陷阱为什么你的串口偶尔丢数据3.1 波特率发生器的分频计算USART的波特率不是随便设的它由系统时钟经过分频得到。以STM32系列常见的公式为例波特率 fCK / (16 * USARTDIV)其中fCK是给USART外设的时钟频率USARTDIV是一个分频系数包含整数部分和小数部分。比如系统时钟72MHz想要9600的波特率那么USARTDIV 72000000 / (16 * 9600) 468.75整数部分是468小数部分是0.75。小数部分会被映射到波特率寄存器的低4位假设是4位小数0.75乘以16等于12所以小数部分寄存器值写12。实际分频系数就是468 12/16 468.75反算回来的波特率是72000000 / (16 * 468.75) 9600刚好整除没有误差。但如果你要配一个115200的波特率呢USARTDIV 72000000 / (16 * 115200) 39.0625整数部分39小数部分0.0625乘以16等于1所以小数寄存器写1。实际分频系数39 1/16 39.0625反算波特率 72000000 / (16 * 39.0625) 115200也没有误差。但不是所有波特率都能这么巧。比如你要配76800USARTDIV 72000000 / (16 * 76800) 58.59375小数部分0.59375乘以16等于9.5但寄存器只能写整数所以只能写9或者10。写9的话实际分频系数是58 9/16 58.5625实际波特率 72000000 / (16 * 58.5625) 76853.6误差约0.07%。写10的话实际分频系数58.625实际波特率 72000000 / (16 * 58.625) 76757.6误差约0.56%。这个误差看起来不大但异步通信对时钟偏差是有容忍极限的。一般来说收发双方累计误差不能超过半个bit周期也就是在10位帧格式下总误差要控制在5%以内。单边误差最好控制在2%以内。0.56%的误差虽然还在范围内但如果对方也有类似量级的误差累计起来就危险了。3.2 误差累积与采样点偏移异步通信的采样机制是这样的接收方检测到起始位的下降沿后延迟1.5个bit周期开始采样第一个数据位之后每隔1个bit周期采样一次。理想情况下采样点正好落在每个bit的中间位置这时候信号最稳定。但如果收发双方的波特率有偏差采样点会逐渐偏离中心。偏差越大偏离越快。当采样点偏移到bit边界附近时就可能采到错误的值。这就是为什么波特率误差必须控制在一定范围内。我遇到过最典型的情况是两块板子单独测试都没问题连在一起通信就偶尔丢字节。用逻辑分析仪抓波形才发现一块板子的实际波特率偏高0.8%另一块偏低0.6%累计偏差1.4%。在10位帧格式下最后一个停止位的采样点已经偏移了超过0.14个bit周期虽然还没到边界但余量已经很小了。后来把两块板子的时钟源统一换成同型号的外部晶振问题就消失了。3.3 实际项目中的波特率选择建议基于这些经验我在实际项目中选波特率会遵循几个原则优先选择能被系统时钟整除的波特率。比如系统时钟72MHz时9600、19200、38400、57600、115200这些标准波特率都能整除误差为零。如果必须用非标准波特率先算一下误差超过1%就要慎重。高波特率对时钟精度的要求更高。同样是1%的误差9600波特率下每个bit周期偏差约1微秒115200波特率下偏差只有0.087微秒但累计到第10个bit时绝对偏差是一样的比例。所以高波特率并不会因为周期短而更容易出错关键是相对误差。如果通信距离较长或者环境噪声较大适当降低波特率可以显著提高可靠性。我一般会在9600和115200之间根据实际场景做取舍。提示用内部RC振荡器做USART时钟源时务必查阅芯片手册中RC振荡器的精度指标。很多MCU的内部RC精度只有±1%到±2%全温度范围内可能更差。如果通信对方用的是晶振累计误差可能超出容忍范围。4. SPI时钟速率的真实含义分频系数背后的取舍4.1 SPI时钟从哪来SPI的时钟由主机产生来源是MCU的系统时钟或者外设总线时钟。以常见的APB2总线为例如果APB2时钟是72MHzSPI1挂载在APB2上那么SPI1的输入时钟就是72MHz。然后通过一个预分频器产生SCK时钟分频系数通常是2的幂次2、4、8、16、32、64、128、256。比如分频系数设为8SCK频率就是72MHz除以8等于9MHz。这个9MHz就是SPI的时钟速率。每一个SCK周期传输1个bit所以理论比特率是9Mbps。但这里有一个关键点SPI的时钟速率不能超过从设备支持的最大时钟频率。你主机能跑50MHz但从设备的数据手册写着最大SCK频率10MHz那你就必须把主机分频到10MHz以下。我见过有人忽略这一点主机配置20MHz从设备勉强能工作但数据偶尔出错查了半天以为是软件问题最后才发现是时钟超规格了。4.2 时钟极性和相位对有效速率的影响SPI有四种工作模式由时钟极性CPOL和时钟相位CPHA组合而成。这四种模式决定了数据在时钟的哪个边沿采样、哪个边沿切换。虽然理论上四种模式的传输速率是一样的但在实际使用中某些从设备在特定模式下的建立时间和保持时间余量更小可能需要降低时钟频率才能稳定工作。比如某个SPI Flash芯片在模式0CPOL0CPHA0下支持50MHz但在模式3CPOL1CPHA1下只保证30MHz。如果你没注意这个细节按50MHz配置读数据时就会间歇性出错。4.3 软件开销为什么SPI跑不到理论速率前面提到SPI的理论比特率等于SCK频率但实际有效吞吐量往往远低于这个值。原因在于软件层面的开销。以查询方式发送数据为例典型的流程是检查发送缓冲区是否为空把数据写入发送寄存器等待传输完成标志置位读取接收寄存器如果有接收重复上述步骤每一步都需要CPU执行若干条指令。假设每条指令1个时钟周期CPU主频72MHz那么处理一个字节可能需要几十个时钟周期。如果SPI时钟是9MHz传输一个字节8个bit需要约0.89微秒而CPU处理开销可能就要1到2微秒。这时候SPI的实际吞吐量就被CPU速度限制住了。用DMA可以大幅缓解这个问题。DMA可以在不占用CPU的情况下自动搬运数据CPU只需要配置好DMA通道和传输长度剩下的交给硬件。我用DMA驱动SPI屏幕刷新时有效吞吐量能从查询方式的不到2Mbps提升到接近7Mbps。4.4 SPI速率配置的实操建议在实际项目中配置SPI速率我会按这个顺序来查从设备数据手册确认最大SCK频率和支持的工作模式。根据主机时钟算出可选的分频系数选一个不超过从设备上限的最大值。如果通信不稳定先降一档分频系数试试而不是急着改代码逻辑。高速SPI通信时注意PCB走线。SCK线太长或者没有做好阻抗匹配信号反射会导致误码。我一般会把SCK和MOSI走线尽量短且等长。如果从设备有多个注意每个设备的CS信号要独立控制避免总线冲突。注意SPI是全双工通信主机在发送数据的同时也在接收数据。如果你只关心发送不关心接收可以忽略接收寄存器的读取。但有些SPI外设如果接收寄存器满了没有及时读走会导致溢出错误进而影响后续传输。具体行为要看芯片手册。5. 从实际吞吐量反推什么时候该用USART什么时候该用SPI5.1 有效吞吐量的计算方法要比较USART和SPI的实际能力不能只看标称速率要算有效吞吐量。USART的有效吞吐量计算公式有效字节率 波特率 / (1 数据位数 校验位数 停止位数)以115200-8-N-1为例有效字节率 115200 / 10 11520字节/秒约11.5KB/s。SPI的有效吞吐量计算更复杂一些要考虑CS间隔和软件开销。假设SCK频率10MHz每次传输16字节CS拉低到第一个时钟沿有0.5微秒延迟最后一个时钟沿到CS拉高有0.5微秒延迟软件处理开销忽略不计用DMA那么单次传输时间 0.5us 16*8/10MHz 0.5us 0.5 12.8 0.5 13.8us 有效字节率 16 / 13.8us 1.16MB/s如果每次只传1个字节单次传输时间 0.5us 8/10MHz 0.5us 0.5 0.8 0.5 1.8us 有效字节率 1 / 1.8us 555KB/s可以看到SPI的有效吞吐量受传输块大小影响很大。小块传输时CS间隔的固定开销占比很高。5.2 选型判断的实际依据基于这些计算我在项目中的选型逻辑是这样的如果数据量小、速率要求不高比如传感器数据每秒钟几十个字节USART和SPI都能胜任选哪个主要看外设接口和引脚资源。如果需要连接多个外设SPI的一主多从架构更省引脚。USART通常只能点对点。如果数据量大、速率要求高比如屏幕刷新、SD卡读写、高速ADC采样SPI是更好的选择。如果通信距离超过几十厘米USART的差分版本如RS-485比SPI更适合长距离传输。SPI的时钟线在长距离下容易受干扰。如果系统对时钟精度要求高但成本敏感SPI不需要精确的时钟匹配可以用内部RC振荡器而USART最好用晶振。5.3 一个实际项目的对比测试我之前做过一个数据采集项目需要从两个传感器读取数据一个通过USART输出一个通过SPI输出。USART传感器波特率115200每次输出32字节数据输出频率100Hz。SPI传感器SCK频率5MHz每次读取16字节读取频率1kHz。USART这边的数据率是32字节乘以100Hz等于3200字节/秒远低于115200波特率的理论上限11.5KB/s所以很轻松。SPI这边是16字节乘以1000Hz等于16000字节/秒SCK频率5MHz的理论上限是625KB/s也完全够用。但如果我把SPI传感器的读取频率提高到10kHz数据率就变成160KB/s接近理论上限的四分之一。这时候如果用查询方式CPU开销就会很明显。后来改用DMACPU占用率从30%降到了不到5%。这个例子说明选型时不能只看标称速率要结合数据量、传输频率和CPU负载综合判断。6. 调试工具与排查思路当通信出问题时先看什么6.1 逻辑分析仪是最直接的武器通信出问题的时候光看代码是看不出所以然的。我现在的习惯是先用逻辑分析仪抓波形看实际信号和预期是否一致。对于USART重点看起始位的下降沿是否干净每个bit的宽度是否一致停止位是否在预期位置实际波特率和配置值差多少对于SPI重点看SCK频率是否和配置一致数据在哪个边沿变化、哪个边沿采样CS信号的时序是否符合从设备要求MOSI和MISO的数据是否对齐逻辑分析仪的好处是能直接看到物理层的信号排除软件层面的干扰。很多时候问题一眼就能看出来比如SCK频率比预期高了一倍或者数据在错误的边沿被采样。6.2 常见问题排查表现象可能原因排查方法USART偶尔丢字节波特率误差偏大用示波器测实际bit宽度计算误差USART完全无数据收发线接反交换TX和RX试试USART数据乱码波特率不匹配确认双方配置一致SPI读回全0或全1MISO未连接或CS未拉低检查硬件连接和CS信号SPI数据错位时钟模式不匹配尝试四种模式组合SPI高速时出错时钟超规格或走线问题降低SCK频率测试多从设备SPI冲突CS信号未独立控制检查每个CS的GPIO配置这张表里的每一行都是我实际遇到过的。特别是SPI数据错位这一项我至少踩过三次坑每次都是时钟模式设错了。后来养成了一个习惯拿到一个新的SPI从设备先把四种模式都试一遍找到能正常工作的那个再继续开发。6.3 用示波器测量实际波特率逻辑分析仪虽然方便但有些细节还是需要示波器来看。比如测量USART的实际波特率可以用示波器抓一个字节的波形测量起始位下降沿到第一个数据位中间的时间或者测量整个字节的宽度反算出实际波特率。具体操作是让发送方连续发送0x55二进制01010101这样波形是方波每个bit宽度容易测量。用示波器测量一个完整bit的宽度取倒数就是实际波特率。比如测得bit宽度为104.1微秒实际波特率就是9600.6和标称9600的误差约0.006%非常小。如果测得bit宽度为108微秒实际波特率就是9259误差约3.5%这就超出容忍范围了需要检查时钟配置。7. 高速通信下的PCB布局与信号完整性7.1 SPI高速走线的注意事项SPI时钟频率超过10MHz以后PCB走线就开始变得重要了。我遇到过一块板子SPI配置成18MHz功能测试都正常但批量生产后有5%的板子通信不稳定。后来用示波器看SCK信号发现上升沿有明显的振铃过冲超过了电源电压的20%。原因是SCK走线太长没有做阻抗匹配。解决方法是SCK走线尽量短最好不超过5厘米如果必须走长线在源端串联一个22欧姆到33欧姆的电阻做阻抗匹配SCK和MOSI尽量走等长线减少时序偏差避免SCK线靠近其他高速信号线减少串扰7.2 USART长距离通信的防护USART在长距离通信时主要问题是信号衰减和噪声干扰。TTL电平的USART通常只能可靠传输几十厘米超过这个距离就需要转成差分信号比如RS-485。即使用RS-485也要注意使用双绞线A和B线绞在一起总线两端加终端电阻通常120欧姆避免星型拓扑尽量用菊花链在噪声环境中降低波特率可以提高可靠性我在一个工业现场的项目中RS-485总线长度约200米波特率从115200降到19200之后通信误码率从每天几次降到了几乎为零。7.3 地线处理与共模干扰无论是USART还是SPI地线处理都很关键。两块板子之间通信时如果地电位不一致共模电压可能超出收发器的容忍范围。我的做法是板间通信时确保有低阻抗的地线连接如果距离较远考虑使用隔离器件SPI通信时地线要和SCK、MOSI一起走减少回流路径这些细节在实验室里可能看不出问题但到了现场环境往往就是稳定性的关键。8. 几个容易被忽略的配置细节8.1 USART的过采样模式很多MCU的USART支持8倍过采样和16倍过采样两种模式。16倍过采样是默认的抗噪能力更强。8倍过采样可以提高波特率的上限但对时钟精度的要求更高。我在一个需要高波特率的项目里用过8倍过采样波特率配到3Mbps。这时候系统时钟必须非常准内部RC完全不能用必须用外部晶振。而且通信距离要短线要屏蔽好。8.2 SPI的CRC校验有些SPI外设支持硬件CRC校验比如STM32的SPI。开启CRC之后每个数据帧会自动附加CRC值接收方可以校验数据完整性。这在高速通信或者噪声环境中很有用。但CRC会增加额外的传输开销。比如8位CRC意味着每传输N个字节要多传1个字节的CRC。如果数据量小这个开销占比就很高。我一般只在数据完整性要求高的场景才开CRC。8.3 中断优先级与DMA通道冲突用中断方式处理USART接收时如果系统里还有其他高优先级中断可能会导致USART接收中断被延迟进而丢失数据。这时候要么提高USART中断优先级要么改用DMA接收。DMA也有通道冲突的问题。如果两个外设共用同一个DMA通道同时传输时会冲突。配置时要查手册确认DMA通道的分配。8.4 引脚复用与重映射很多MCU的USART和SPI引脚支持重映射。重映射之后原来的引脚可能就不能用了或者需要重新配置GPIO。我在一个项目里因为重映射没配对SPI死活不出数据查了半天才发现是引脚映射寄存器没设置。提示配置外设之前先确认引脚复用表把GPIO模式、复用功能、重映射寄存器都检查一遍。这个步骤花不了几分钟但能省掉很多调试时间。9. 从波特率和速率延伸出去的一些思考回过头来看USART的波特率和SPI的速率之所以容易混淆是因为它们都描述了通信有多快这件事。但快的方式不一样快的代价也不一样。USART用时间基准来对齐数据省了一根时钟线但引入了时钟偏差的问题。SPI用时钟线来同步数据省了时钟匹配的麻烦但多了一根线而且在多设备场景下需要额外的片选线。这其实反映了嵌入式系统设计中的一个基本权衡用资源换可靠性或者用复杂度换简单性。没有哪种方案是绝对好的关键看你的项目约束是什么。我现在拿到一个新的通信需求会先问几个问题数据量多大实时性要求多高通信距离多远有几个设备环境噪声如何成本敏感吗把这些问题回答清楚选型基本就定了。最后分享一个我常用的调试技巧如果USART通信不稳定先把波特率降到9600试试。如果9600稳定但115200不稳定那基本可以确定是时钟精度或者信号完整性问题。如果9600也不稳定那就要查硬件连接和配置了。这个简单的二分法能快速缩小问题范围比盲目改代码高效得多。
阅读完成 · 觉得有帮助?
咨询建站