SPI、I2C、UART串行总线选型指南:从物理层到实战调试 1. 三种串行总线的本质差异从物理层说起很多刚接触嵌入式开发的朋友第一次看到SPI、UART、I2C这三个词的时候脑子里基本是一团浆糊。它们都是串行通信都能传数据那到底什么时候用哪个我见过不少人做项目时随手选一个结果调试到怀疑人生——要么速度上不去要么设备挂多了地址冲突要么通信距离稍微拉长就丢包。其实这三个协议各有各的脾气选对了事半功倍选错了就是给自己挖坑。先把最核心的结论摆出来SPI拼的是速度I2C拼的是引脚效率UART拼的是简单和通用。这三句话你记住了后面所有的细节都是围绕这个核心展开的。从物理层来看三者的信号线结构完全不同。UART最少只需要两根线TX和RX一发一收全双工。SPI至少需要四根线SCLK、MOSI、MISO、CS也是全双工但每多挂一个从设备就要多一根CS片选线。I2C只需要两根线SCL和SDA半双工但理论上可以挂一百多个设备靠地址区分。这里有个容易被忽略的点UART和SPI的全双工是物理上独立的收发通道而I2C的半双工意味着同一时刻只能收或者发。这个差异直接影响了它们的应用场景。比如你要做一个音频数据采集麦克风数据要实时传给主控同时主控还要给麦克风发配置命令那SPI或者UART就更合适。但如果只是偶尔读一下温度传感器的值I2C两根线就够了省下来的引脚可以干别的事。再来说电气特性。UART通常是异步通信没有时钟线收发双方靠预先约定的波特率来同步。这就意味着波特率的精度直接影响通信可靠性。你用内部RC振荡器做时钟源波特率误差可能到3%以上通信就会间歇性出错。SPI和I2C都是同步通信有时钟线接收方跟着时钟采样对时钟精度的容忍度高得多。这也是为什么SPI能轻松跑到几十兆赫兹而UART一般到几兆就顶天了。还有一个关键差异是电平标准。UART最常见的是TTL电平3.3V或5V也有RS-232、RS-485这种差分或高压标准适合长距离传输。I2C和SPI基本都是板级总线TTL电平传输距离通常不超过几十厘米。你要是想用I2C拉一根两米长的线去接传感器大概率会失败除非加缓冲器或者降低速率。我刚开始做项目的时候觉得I2C最省线什么都想往上挂。后来发现I2C的总线电容有严格限制标准模式最大400pF快速模式也差不多。每挂一个设备引脚电容和走线电容都会累加挂到五六个设备以后波形就明显变形了上升沿变缓通信开始随机出错。这时候要么加I2C缓冲器要么就得换方案。SPI就没这个问题每个设备独立片选总线负载轻得多。所以选型的第一步不是看哪个协议“高级”而是看你的引脚预算、速度需求、设备数量、通信距离这四个硬约束。把这四个想清楚了答案基本就出来了。2. SPI的实战逻辑为什么它快以及快的代价SPI能跑得快根本原因在于它的协议极其简单。没有地址帧没有起始位停止位没有应答位主设备拉低片选时钟一打数据就从MOSI出去、从MISO进来。整个过程就是移位寄存器的操作硬件实现非常直接。我实测过在STM32F4上SPI时钟跑到42MHz配合DMA传输连续传几兆字节的数据几乎不占CPU。这种效率是I2C和UART很难做到的。但SPI的简单是有代价的。第一个代价是引脚数量。每增加一个从设备就要多一根CS线。你要挂八个SPI从设备加上SCLK、MOSI、MISO总共就是十一根线。如果主控引脚紧张这就很要命。有些方案用译码器来扩展片选比如3-8译码器用三根GPIO控制八根CS能省一些引脚但增加了硬件复杂度。第二个代价是没有硬件应答机制。SPI主设备根本不知道从设备有没有收到数据也不知道从设备是不是还活着。你发出去的数据就像扔进黑洞有没有响应全靠从设备自己的行为来判断。所以SPI协议里经常需要软件层做校验比如读一个设备ID寄存器或者用CRC校验数据。我见过一个项目SPI Flash偶尔写入失败查了半天发现是片选时序不对从设备还没准备好主设备就开始打时钟了。后来在CS拉低和第一个时钟沿之间加了几个微秒的延时问题就解决了。第三个代价是缺乏标准。SPI有四种模式由CPOL和CPHA组合决定。CPOL决定时钟空闲时是高还是低CPHA决定数据在第一个还是第二个时钟沿采样。这四个模式没有统一规定每个从设备的数据手册都会告诉你它支持哪种模式。我踩过的最大的坑就是主设备配了模式0从设备要求模式3结果数据完全错位读出来的全是0xFF。所以拿到一个SPI设备第一件事就是翻数据手册确认模式然后用逻辑分析仪抓波形验证。SPI的典型应用场景很明确需要高速数据传输、从设备数量不多、板内通信。比如SPI Flash、SPI屏幕、SD卡SPI模式、高速ADC、无线模块。这些场景要么数据量大要么实时性要求高I2C和UART都扛不住。在实际接线时有几个细节值得注意。MISO线在主设备端通常需要上拉或下拉因为当片选无效时从设备的MISO引脚是高阻态如果不处理主设备的MISO输入会浮空可能导致漏电流或者误触发。很多MCU内部有可配置的上拉但外部再加一个10kΩ的上拉更稳妥。另外SPI的走线尽量等长尤其是时钟频率高的时候SCLK和MOSI/MISO的长度差异会导致时序偏移。我在一个四层板上做过测试SCLK和MISO长度差超过两厘米在30MHz以上就开始出现偶发误码。还有一个经验SPI的片选信号最好用硬件控制不要用软件GPIO模拟。软件控制片选的话在片选切换的间隙时钟线可能会有毛刺某些敏感的从设备会误识别为有效时钟。用硬件SPI外设的NSS引脚或者至少用GPIO但保证切换时SCLK稳定。提示调试SPI通信时逻辑分析仪比示波器更好用。示波器只能看一两路信号逻辑分析仪可以同时抓SCLK、MOSI、MISO、CS四路直接解码出数据字节效率高很多。3. I2C的地址机制与总线仲裁两根线背后的复杂世界I2C用两根线挂一堆设备听起来很优雅但背后的机制比SPI复杂得多。核心在于地址机制和总线仲裁这两点决定了I2C的灵活性和局限性。每个I2C从设备都有一个7位地址理论上可以挂128个设备。但实际可用的地址空间要扣除保留地址比如0x00是通用呼叫地址0x01到0x07是CBUS兼容地址0x78到0x7F是10位地址的前缀。所以真正能用的7位地址大概有112个。这看起来很多但问题是很多传感器的地址是固定的或者只有两三个可选值。比如某个温度传感器地址是0x48另一个ADC地址也是0x48你就没法把它们挂在同一条I2C总线上。这时候要么用I2C多路复用器要么换一个地址不同的器件。地址冲突是I2C项目中最常见的问题之一。我做过一个环境监测板上面挂了温度、湿度、气压三个传感器结果发现温度和气压的地址都是0x76。解决办法是查数据手册看有没有地址配置引脚。有些器件提供一个ADDR引脚接GND是一个地址接VCC是另一个地址。如果两个器件都没有可配置地址那就只能分两条I2C总线或者用多路复用器。总线仲裁是I2C的另一个核心机制。当多个主设备同时想占用总线时I2C通过线与逻辑来实现仲裁。SDA和SCL都是开漏输出任何设备拉低都会把线拉低。主设备在发送数据的同时会回读SDA线如果发现自己发的是1但读回来是0说明有另一个主设备在拉低自己就退出仲裁。这个机制保证了多主设备场景下的数据完整性但也意味着I2C的速率受限于最慢的设备。如果总线上有一个只支持100kHz的器件那整条总线就只能跑100kHz。I2C的速率等级分为标准模式100kHz、快速模式400kHz、快速模式 1MHz、高速模式3.4MHz。但实际能跑多快取决于总线电容和上拉电阻。上拉电阻的选择是个技术活阻值太小功耗大低电平灌电流可能超过器件的驱动能力阻值太大上升沿变缓高速通信时波形还没恢复到高电平就被拉低了。标准做法是根据总线电容来计算公式是R(max) tr / (0.8473 × C)其中tr是允许的最大上升时间。比如总线电容200pF快速模式要求上升时间不超过300ns那上拉电阻最大约1.7kΩ。实际选1.5kΩ到2.2kΩ比较常见。I2C还有一个让人头疼的问题是时钟拉伸。从设备如果处理不过来可以把SCL线拉低强制主设备等待。这个机制本身是好的但有些主设备的硬件I2C外设不支持时钟拉伸或者支持得不好就会导致通信失败。我在一个项目里用某款MCU的硬件I2C读EEPROM偶尔会卡死后来发现是EEPROM在写入周期内会拉伸时钟而那个MCU的I2C外设没有正确处理。换成软件模拟I2C问题就消失了。所以如果你的I2C总线上有会拉伸时钟的器件一定要确认主设备是否支持。I2C的典型应用场景是低速传感器、EEPROM、RTC、IO扩展芯片。这些设备数据量不大但对引脚数量敏感。比如一个温湿度传感器每分钟读一次I2C两根线完全够用换成SPI就要四根线浪费。注意I2C总线上所有设备的地必须共地否则电平参考不一致通信必然失败。另外SDA和SCL的走线尽量短如果必须走长线考虑用I2C缓冲器或者降低速率。4. UART的异步世界没有时钟线怎么保证数据正确UART是三种协议里最古老、最简单、也最通用的。它没有时钟线收发双方靠约定的波特率来同步。这听起来很不靠谱但实际上只要双方时钟精度足够通信非常稳定。UART的核心在于起始位和停止位的帧结构空闲时线是高电平发送方拉低一个位时间作为起始位接收方检测到这个下降沿就开始按波特率采样数据位最后用停止位恢复高电平。波特率的误差容忍度是UART设计的关键参数。一般来说总误差不能超过±2%到±3%否则接收方会在错误的位置采样导致误码。误差来源有两部分发送方和接收方的时钟偏差。如果双方都用晶振精度可以做到±20ppm几乎可以忽略。但如果一方用内部RC振荡器精度可能只有±1%到±2%再加上波特率分频的取整误差总误差就可能超标。我见过一个案例某款低成本MCU用内部RC跑115200波特率常温下通信正常一到冬天就频繁出错。后来测了一下低温下RC频率漂移了3%超过了容忍范围。换成外部晶振就解决了。UART的帧结构可以配置数据位5到9位停止位1或2位校验位无、奇、偶、标记、空格。最常用的配置是8N18位数据、无校验、1位停止位。一个字节的传输需要10个位时间1起始8数据1停止所以115200波特率下每秒最多传11520字节。这个速率对于大多数控制命令和调试信息足够了但传大量数据就吃力了。UART的硬件流控是很多人忽略的功能。RTS和CTS两根线用来控制数据流防止接收方缓冲区溢出。当你用UART传大量数据时如果没有流控接收方处理不过来就会丢数据。我在一个项目里用UART传图像数据开始没接流控线结果每隔几百字节就丢一包。后来把RTS和CTS接上让接收方在缓冲区快满时拉高RTS发送方暂停问题就解决了。当然流控需要双方都支持而且多两根线。UART的最大优势是通用性。几乎所有的MCU都有UARTPC有串口或者USB转串口很多模块默认就是UART接口。调试信息输出、AT命令控制、GPS模块、蓝牙模块这些都是UART的天下。而且UART的协议简单用GPIO模拟也容易不像SPI和I2C对时序要求那么严格。但UART的局限性也很明显。第一是点对点一条UART只能连两个设备不能像I2C那样挂一堆。第二是速率上限虽然理论上可以跑到几兆但实际受限于线缆质量和时钟精度一般115200到921600是常见范围。第三是没有地址机制多设备场景需要额外的硬件切换或者多路UART。UART的接线有个基本原则TX接RXRX接TX地接地。这个看起来简单但我见过太多人把TX接TX然后纳闷为什么收不到数据。另外如果两个设备的电平标准不同比如一个是3.3V一个是5V需要电平转换否则可能损坏3.3V器件。最简单的电平转换用一个分压电阻或者专用电平转换芯片。提示调试UART时先确认波特率、数据位、停止位、校验位是否匹配。如果收到乱码大概率是波特率不对。如果完全没数据检查TX/RX是否接反地是否共地。5. 选型决策树拿到需求后怎么快速锁定协议前面把三个协议的脾气都摸了一遍现在回到最实际的问题拿到一个具体需求怎么快速决定用哪个。我总结了一个决策流程基本能覆盖大多数场景。第一步看设备数量和引脚预算。如果总线上要挂三个以上的设备而且引脚紧张优先考虑I2C。如果只挂一两个设备SPI和UART都可以。如果设备数量多但速度要求高可以考虑SPI加译码器扩展片选或者用多个SPI总线。第二步看速度需求。需要传图像、音频、大量传感器数据选SPI。只需要传控制命令、偶尔读传感器值I2C或UART都行。这里有个经验值超过1Mbps的持续传输基本只能选SPI。UART在921600以上对时钟精度要求很高I2C快速模式才400kHz快速模式也才1MHz而且实际很难跑满。第三步看通信距离。板内通信三个都可以。超过30厘米UART加RS-485或者RS-232更合适。I2C和SPI都不适合长距离除非加缓冲器或者降低速率。第四步看设备是否已有接口。很多传感器和模块出厂就固定了接口比如温湿度传感器大多是I2CSPI Flash只有SPIGPS模块只有UART。这种情况下没得选只能适配。第五步看主设备资源。有些MCU的SPI外设多有些I2C外设多有些UART多。如果硬件资源紧张可能需要用GPIO模拟。软件模拟SPI和I2C都比较容易UART稍微麻烦一点因为对时序要求更严格。下面这个表格可以帮你快速对照维度SPII2CUART线数4CS22全双工是否是最大速率几十MHz3.4MHz几MHz设备数量每设备一根CS理论上128点对点通信距离板内板内可达数十米硬件复杂度低中低协议开销极低中低典型场景Flash、屏幕、高速ADC传感器、EEPROM调试、模块、GPS在实际项目中经常是三种协议混用。比如一个数据采集板用SPI读高速ADC用I2C读温度传感器用UART输出调试信息和连接无线模块。这种组合很常见关键是每种协议用在它最擅长的地方。我个人的经验是不要为了省引脚而牺牲性能也不要为了性能而浪费引脚。先满足功能需求再优化成本和复杂度。有时候多花几毛钱用一个引脚更多的MCU比花几天时间调试I2C地址冲突要划算得多。6. 调试实战逻辑分析仪抓波形与常见故障定位理论说再多不如实际抓一次波形。调试这三种协议逻辑分析仪是必备工具。它比示波器便宜通道多而且自带协议解码功能能直接把波形翻译成字节。我用的是一个八通道的逻辑分析仪配合开源软件抓SPI、I2C、UART都没问题。SPI调试的典型流程先把逻辑分析仪的通道接到SCLK、MOSI、MISO、CS上设置SPI解码器配置正确的CPOL和CPHA。然后触发一次传输看解码出来的数据对不对。如果数据错位检查CPOL/CPHA设置。如果完全没有数据检查CS是否拉低SCLK是否有波形。如果MISO一直是高阻态检查从设备是否供电正常、片选是否接对。我遇到过一个SPI屏幕不亮的问题抓波形发现SCLK和MOSI都有信号但CS一直是高电平。查原理图发现CS接到了一个被复用的引脚上而那个引脚默认功能不是GPIO。配置成GPIO输出后屏幕就正常了。这种问题看代码很难发现抓波形一眼就能定位。I2C调试的典型流程逻辑分析仪接SCL和SDA设置I2C解码器。先看有没有起始条件SCL高时SDA下降沿然后看地址字节是否得到应答第9个时钟SDA被从设备拉低。如果地址没有应答说明从设备没响应检查地址是否正确、供电是否正常、上拉电阻是否接好。如果有应答但数据错误检查寄存器地址和数据是否符合预期。I2C最常见的故障是上拉电阻缺失或阻值不当。有些开发板自带上拉有些没有。如果你直接连一个裸传感器没有上拉电阻SDA和SCL永远是低电平通信完全无法进行。我刚开始就犯过这个错误以为I2C和UART一样直接连就行结果折腾了半天才发现要加上拉。标准做法是SDA和SCL各接一个4.7kΩ上拉到VCC如果总线电容大或者速率高适当减小到2.2kΩ或1.5kΩ。UART调试的典型流程逻辑分析仪接TX和RX设置UART解码器配置正确的波特率、数据位、停止位、校验位。然后看解码出来的字节。如果全是乱码检查波特率。如果收到部分正确部分错误检查时钟精度和线缆质量。如果完全没数据检查TX/RX是否接反。UART有一个隐蔽的坑某些MCU的UART引脚在复位后默认是GPIO需要软件配置成UART功能。如果你忘了配置TX引脚一直是高阻态发不出数据。这个在代码里很容易漏掉尤其是用寄存器直接操作的时候。还有一个通用技巧在通信线路上串联小电阻。比如在SPI的SCLK和MOSI上串22Ω到100Ω的电阻可以抑制反射和过冲改善信号完整性。尤其是走线较长或者频率较高的时候这个小电阻能解决很多偶发问题。I2C的SDA和SCL也可以串但要注意不能影响上拉电阻的分压。注意逻辑分析仪的采样率要足够高。SPI跑10MHz的话采样率至少100MHz才能看清波形。I2C和UART速率低采样率要求不高但也要保证每个位时间有足够的采样点。7. 进阶话题DMA、中断与多设备混合组网当你把基础通信跑通之后下一步就是优化CPU占用和系统实时性。DMA是提升串行通信效率的利器。SPI配合DMA可以在不占用CPU的情况下传输大量数据。比如刷SPI屏幕一帧图像几万字节用DMA传输CPU可以去处理其他任务。I2C的DMA支持因MCU而异有些支持有些不支持。UART的DMA收发很常见尤其是高速率或者大数据量的时候。中断的使用也有讲究。SPI和I2C通常用中断来处理传输完成事件但中断频率不能太高。如果每传一个字节就中断一次CPU会被频繁打断效率反而下降。更好的做法是用DMA传输一批数据传完一批再中断一次。UART的接收中断可以配合环形缓冲区把收到的数据先存起来主循环慢慢处理。多设备混合组网是实际项目中的常态。比如一个主控同时用SPI接Flash、用I2C接传感器、用UART接无线模块。这时候要注意引脚复用和中断优先级。有些MCU的SPI和I2C共用引脚配置的时候要小心。中断优先级方面高速通信的优先级应该高于低速通信避免低速中断阻塞高速数据。还有一个进阶技巧用GPIO模拟I2C或SPI。当硬件外设不够用或者需要更灵活的时序控制时软件模拟是很好的补充。软件I2C的速率通常只能到100kHz到400kHz但胜在灵活可以任意切换引脚。软件SPI可以跑到几MHz但会占用CPU时间。我一般只在硬件资源实在不够的时候才用软件模拟因为调试起来更麻烦时序容易受中断影响。电源管理也是串行通信中容易被忽略的一环。当系统进入低功耗模式时通信外设的时钟可能会被关闭导致通信中断。如果需要在低功耗下保持通信要确保外设时钟源不被关闭或者配置唤醒中断。另外I2C的上拉电阻在低功耗模式下会持续耗电如果总线空闲可以考虑用GPIO控制上拉电阻的供电进一步降低功耗。最后说一个实际项目中的教训通信协议要有超时和重试机制。无论SPI、I2C还是UART都可能因为干扰、时序问题或者设备异常而失败。如果代码里没有超时处理一个失败的传输可能让整个系统卡死。我的做法是每次通信都设一个超时计数器超时后重试两到三次如果还失败就报错并复位从设备。这个机制在实验室里可能看不出差别但在现场环境中能救命。提示在通信线上加TVS二极管可以防止静电和浪涌损坏接口。尤其是对外连接的UART或者I2C接口TVS是低成本高回报的保护措施。8. 我踩过的那些坑与最终选型心得做了这么多年嵌入式SPI、I2C、UART的坑我基本都踩过一遍。有些是粗心大意有些是理解不到位还有些是器件本身的特性。挑几个有代表性的说说希望能帮你少走弯路。第一个坑SPI模式搞错。前面提过但值得再强调。我买了一个SPI传感器数据手册上写支持模式0和模式3我随手配了模式0结果读出来的数据一直不对。后来用逻辑分析仪抓波形发现从设备在时钟下降沿输出数据而模式0是在上升沿采样完全错位。改成模式3就正常了。教训永远不要假设SPI模式一定要看数据手册并用波形验证。第二个坑I2C上拉电阻用了10kΩ。在一个3.3V系统里我用10kΩ上拉跑100kHz没问题。后来想提速到400kHz发现波形上升沿太慢数据经常出错。算了一下总线电容大概150pF10kΩ的上升时间约1.5微秒远超过快速模式要求的300纳秒。换成2.2kΩ后上升时间降到300纳秒左右400kHz稳定运行。教训I2C的上拉电阻要根据速率和总线电容计算不能随便选。第三个坑UART波特率用内部RC。一个低成本项目为了省晶振用内部RC跑115200。常温调试没问题小批量生产后有几块板子通信不稳定。测了一下RC频率偏差最大的到了2.5%加上分频误差总误差接近3%处于临界状态。后来改成9600波特率误差容忍度大了问题消失。教训高速UART一定要用晶振内部RC只适合低波特率。第四个坑SPI片选用了软件延时。在一个SPI Flash项目里我用GPIO控制CS拉低后立即开始传输。结果偶尔写入失败。抓波形发现CS拉低到第一个时钟沿之间只有几十纳秒而Flash要求至少100纳秒的建立时间。加了一个微秒级延时后问题解决。教训SPI的片选建立时间和保持时间要看数据手册不能想当然。第五个坑I2C总线上挂了不同速率的设备。一条总线上有一个400kHz的传感器和一个100kHz的EEPROM。我按400kHz配置主设备结果EEPROM通信失败。后来意识到I2C总线速率由最慢的设备决定改成100kHz后两个都正常了。教训混合速率设备共存时按最低速率跑。选型心得方面我现在的原则是先看器件手册推荐什么再看系统需求最后看主控资源。如果器件只支持一种接口那就没得选。如果器件支持多种接口比如有些传感器同时支持SPI和I2C那就看哪个更符合系统需求。速度要求高选SPI引脚紧张选I2C。UART主要用于调试和模块通信很少作为传感器的主要接口。还有一个经验在项目初期就把通信接口规划好不要等到PCB画完了才发现引脚不够或者协议冲突。我见过一个项目硬件工程师把SPI和I2C的引脚画反了软件工程师调了一周才发现是硬件问题。如果前期多花半小时核对原理图能省下一周的时间。最后保持对波形的敬畏。无论你觉得自己对协议多熟悉遇到通信问题第一件事就是抓波形。波形不会骗人它能告诉你时序对不对、电平对不对、数据对不对。很多问题看代码看不出来看波形一目了然。逻辑分析仪不贵但能帮你省下的时间远超它的价格。