1. 三种串行总线的本质差异与选型逻辑
1.1 为什么面试官总爱问这三兄弟
嵌入式岗位的面试里,UART、I2C、SPI 几乎是必考题。原因很直接:它们是芯片之间"说话"的最基础方式,任何一个嵌入式项目都绕不开。面试官问这三个,不是想考你背不背得出定义,而是想看你能不能在实际项目里做出正确的选型判断。
我面过不少人,简历上写着"精通UART、I2C、SPI",结果一问"你的传感器为什么用I2C不用SPI",就卡住了。这说明他只会在现成的代码里调用库函数,没有真正理解三种总线各自的适用边界。
这三种总线本质上解决的是同一个问题:两块芯片之间怎么用最少的引脚、最可靠的方式传输数据。但它们的解法完全不同,就像从A地到B地可以走路、骑车、开车,各有各的适用场景。
1.2 一张表看清三者的核心区别
先上一张对比表,把最关键的参数拉出来。这张表建议你面试前默写一遍,基本覆盖80%的考点。
| 对比维度 | UART | I2C | SPI |
|---|---|---|---|
| 信号线数量 | 2根(TX/RX) | 2根(SCL/SDA) | 4根(SCLK/MOSI/MISO/CS) |
| 通信方式 | 异步 | 同步 | 同步 |
| 时钟来源 | 双方各自约定波特率 | 主机提供SCL | 主机提供SCLK |
| 拓扑结构 | 点对点 | 多主多从(总线型) | 一主多从(星型) |
| 寻址方式 | 无地址,靠物理连线区分 | 7位或10位地址 | 片选信号CS选中 |
| 典型速率 | 9600bps~1Mbps | 100k/400k/1M/3.4M | 几M~几十M |
| 引脚开销 | 低 | 低 | 高(每个从机多一根CS) |
| 硬件复杂度 | 低 | 中(需要上拉电阻) | 低 |
| 典型应用 | 调试串口、GPS、蓝牙模块 | 传感器、EEPROM、RTC | Flash、显示屏、ADC |
这张表里最容易被追问的是"为什么I2C需要上拉电阻"和"SPI为什么速率可以做到很高"。这两个问题后面会展开讲。
1.3 选型时我实际考虑的几个维度
面试里被问到选型,不要只背"UART点对点、I2C多从机、SPI高速"这种教科书答案。实际项目中,我一般按下面这个顺序来决策:
第一看速率需求。如果数据量小、实时性要求不高,比如读个温度传感器,I2C足够了。如果要刷屏幕或者读写Flash,SPI是唯一选择。UART的速率上限比较尴尬,一般超过1Mbps就不太稳了。
第二看引脚预算。PCB板子小、MCU引脚紧张的时候,I2C的优势就出来了。两个引脚挂一堆设备,这是I2C最核心的卖点。SPI每多一个从机就多一根CS线,设备多了引脚吃不消。
第三看距离。板内通信三种都行,板间通信超过几十厘米,UART反而更常见,因为它是异步的,对时钟线要求低。I2C和SPI都是同步总线,长距离传输时钟线容易受干扰。
第四看从机数量和种类。如果总线上要挂很多不同厂家的传感器,I2C的标准化程度最高,地址冲突也容易排查。SPI虽然快,但不同厂家的时序模式(CPOL/CPHA)经常对不上,调试起来头疼。
实操心得:我做过一个项目,MCU要同时接温湿度传感器、EEPROM、OLED屏和一个无线模块。最后方案是传感器和EEPROM走I2C,OLED走SPI,无线模块走UART。三种总线各司其职,引脚刚好够用。如果全用SPI,光CS线就要4根,MCU根本不够。
2. UART的核心考点与实操细节
2.1 异步通信的"约定"到底约定了什么
UART最核心的特点是异步。什么叫异步?就是通信双方没有共享的时钟线,全靠事先约定好的波特率来对时。这就像两个人约好每天早上8点见面,没有电话确认,全靠各自的表准不准。
UART的一帧数据由这几部分组成:
- 起始位:1位,低电平,告诉对方"我要开始发了"
- 数据位:5~9位,通常是8位,低位先发
- 校验位:可选,奇校验、偶校验或无校验
- 停止位:1位、1.5位或2位,高电平,表示一帧结束
面试常问:"波特率和比特率是一回事吗?"严格来说不是。波特率是每秒信号变化的次数,比特率是每秒传输的比特数。但在UART里,一个信号变化就对应一个比特,所以两者数值相等。这个细节能答出来,面试官会觉得你基础扎实。
2.2 波特率误差能容忍多少
这是UART最容易被追问的深水区。异步通信没有时钟线,双方波特率如果有偏差,累积到一帧的末尾就可能采样错位。
UART接收端一般在每个比特的中间采样。一帧10位(1起始+8数据+1停止),如果双方波特率偏差太大,到第10位的时候采样点可能已经偏出这个比特的范围了。
经验公式是这样的:假设接收端在第N位采样,允许的最大偏差大约是1 / (2 × N)。对于10位的帧,最坏情况是第10位,允许偏差约5%。但实际工程中,我一般要求双方波特率误差控制在2%以内,留足余量。
常见的波特率误差来源:
- 晶振精度不够(比如用内部RC振荡器)
- 分频系数除不尽(比如系统时钟除出来不是整数)
踩过的坑:有一次用内部RC振荡器跑115200波特率,通信时好时坏。后来换成外部晶振,问题立刻消失。内部RC振荡器精度可能只有±2%,加上对方也有误差,累积起来就超了。所以高速UART通信,外部晶振几乎是必须的。
2.3 流控:什么时候需要RTS/CTS
UART还有两根可选的流控线:RTS(请求发送)和CTS(清除发送)。当接收方缓冲区快满了,就拉高RTS告诉对方"先别发了"。
什么时候需要流控?两种情况:
- 速率高、数据量大:比如115200以上连续传输,接收方CPU来不及处理
- 接收方处理能力弱:比如MCU要同时处理其他任务,串口中断优先级低
大部分简单应用不需要流控,两根线TX/RX就够了。但如果你的项目里串口丢数据,先检查是不是没开流控。
2.4 UART调试的实战技巧
调试UART,我一般按这个顺序排查:
第一步,确认波特率。这是最常见的错误来源。双方波特率不一致,收到的就是乱码。用示波器量一下TX线的位宽,1/位宽就是波特率。
第二步,确认电平标准。TTL电平(0~3.3V或0~5V)和RS232电平(负逻辑,±12V)不能直接对接,需要电平转换芯片。我见过有人把TTL的TX直接接到RS232的RX上,结果芯片烧了。
第三步,确认TX/RX交叉。A的TX接B的RX,A的RX接B的TX。这个错误新手经常犯,而且症状是"完全没反应",很容易误判为硬件坏了。
第四步,看数据格式。数据位、停止位、校验位是否一致。特别是校验位,一方开了另一方没开,收到的数据会错位。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全无数据 | TX/RX接反、电平不匹配 | 示波器看TX线有无波形 |
| 收到乱码 | 波特率不一致 | 测量位宽反推波特率 |
| 偶尔丢数据 | 无流控、中断被抢占 | 开启流控或提高中断优先级 |
| 数据错位 | 数据位/停止位/校验位不一致 | 核对双方配置 |
3. I2C的核心考点与实操细节
3.1 开漏输出与上拉电阻的必然性
I2C最经典的面试题:"为什么SCL和SDA需要上拉电阻?"答案要从它的电气结构说起。
I2C的引脚是开漏输出(Open-Drain)。开漏意味着引脚只能主动拉低,不能主动拉高。要输出高电平,只能靠外部上拉电阻把线拉高。
为什么这么设计?因为I2C是总线型拓扑,多个设备挂在同一根线上。如果两个设备同时输出,一个想拉高一个想拉低,就会短路。开漏结构天然实现了"线与"逻辑:只要有一个设备拉低,线就是低;所有设备都释放,线才被上拉电阻拉高。这样就避免了总线冲突时的短路问题。
上拉电阻的阻值怎么选?这是个计算题:
- 阻值太小:功耗大,且可能超过设备的灌电流能力
- 阻值太大:上升沿变缓,高速通信时波形来不及拉高
经验公式:Rp(max) = tr / (0.8473 × Cb),其中tr是允许的最大上升时间,Cb是总线电容。标准模式(100kHz)tr=1000ns,快速模式(400kHz)tr=300ns。
实际选型:3.3V系统常用4.7kΩ,5V系统常用10kΩ。总线电容大或者速率高的时候,要减小阻值,比如2.2kΩ。
实操心得:我曾经遇到一个I2C通信不稳定的问题,波形上看SDA的上升沿特别缓。后来发现是总线上挂了8个设备,电容太大,4.7kΩ的上拉电阻不够。换成2.2kΩ后波形立刻变陡,通信稳定了。所以设备多的时候,上拉电阻要相应减小。
3.2 起始、停止、应答:I2C的"握手礼仪"
I2C的通信过程有一套严格的时序规范,面试经常要求手画时序图。
起始条件(Start):SCL为高时,SDA由高变低。这个特殊的跳变告诉所有设备"通信要开始了"。
停止条件(Stop):SCL为高时,SDA由低变高。表示通信结束。
应答(ACK/NACK):每传输8位数据后,接收方要拉低SDA一个时钟周期表示ACK,或者保持高电平表示NACK。
这里有个细节:起始和停止条件都发生在SCL为高的时候,而数据位的改变必须发生在SCL为低的时候。这样保证数据在SCL高电平期间是稳定的,接收方可以可靠采样。
3.3 时钟拉伸:从机的"慢动作"权利
时钟拉伸(Clock Stretching)是I2C一个很巧妙的设计。从机如果处理不过来,可以在ACK阶段把SCL线拉低,强制主机等待。主机检测到SCL没有被释放,就知道从机还没准备好,会一直等下去。
这个机制让慢速从机也能挂在快速总线上。比如一个低速传感器挂在400kHz的I2C总线上,它处理不过来的时候就可以拉伸时钟。
但时钟拉伸也是坑。有些主机不支持时钟拉伸,或者支持得不好。我遇到过主机的I2C控制器在时钟拉伸时超时退出,导致通信失败。所以选型时要确认主从双方对时钟拉伸的支持情况。
3.4 I2C地址冲突与排查
I2C的7位地址理论上可以挂128个设备,但实际可用的地址范围有限(有些地址被保留),而且很多传感器的地址是固定的或者只有两三个可选值。
地址冲突的症状是:总线上某个设备不响应,或者响应错误。排查方法:
- 用I2C扫描工具:很多MCU有现成的I2C扫描代码,遍历所有地址,看哪些地址有ACK
- 查数据手册:确认每个设备的地址,以及地址是否可以通过引脚配置
- 用逻辑分析仪抓波形:看主机发出的地址和从机的响应
如果两个设备地址确实冲突,解决方案有:
- 用I2C多路复用器(如TCA9548A),把冲突的设备分到不同通道
- 换用地址可配置的器件
- 改用SPI接口的同类器件
| 常见I2C问题 | 症状 | 解决方案 |
|---|---|---|
| 地址冲突 | 某设备不响应 | 用多路复用器或换器件 |
| 上拉电阻过大 | 高速时通信失败 | 减小阻值至2.2k~4.7k |
| 总线电容过大 | 上升沿缓、波形畸变 | 减小上拉电阻、减少设备数 |
| 时钟拉伸不支持 | 通信超时 | 确认主从支持情况 |
| 电源域不同 | 电平不匹配 | 用电平转换芯片 |
4. SPI的核心考点与实操细节
4.1 四根线的分工与全双工原理
SPI用四根线完成通信:
- SCLK:时钟,主机产生
- MOSI:Master Out Slave In,主机发、从机收
- MISO:Master In Slave Out,主机收、从机发
- CS:片选,低电平有效,每个从机一根
SPI是全双工的:主机在MOSI上发数据的同时,从机在MISO上发数据。这是因为SPI内部是一个移位寄存器,主机和从机的寄存器首尾相连,每个时钟周期双方各移出一位、移入一位。8个时钟周期后,双方交换了一个字节。
这个全双工特性是SPI比I2C快的重要原因之一。I2C是半双工,同一时刻只能单向传输。
4.2 CPOL和CPHA:SPI最容易被问倒的地方
SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)组合而成:
| 模式 | CPOL | CPHA | 空闲时SCLK | 采样沿 | 移出沿 |
|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低 | 上升沿 | 下降沿 |
| Mode 1 | 0 | 1 | 低 | 下降沿 | 上升沿 |
| Mode 2 | 1 | 0 | 高 | 下降沿 | 上升沿 |
| Mode 3 | 1 | 1 | 高 | 上升沿 | 下降沿 |
记忆方法:CPOL决定空闲电平,CPHA决定第一个边沿是采样还是移出。CPHA=0时第一个边沿采样,CPHA=1时第二个边沿采样。
面试常问:"你的Flash用哪个模式?"大部分Flash支持Mode 0和Mode 3,因为这两个模式在空闲时SCLK都是低或高,采样沿都是上升沿,兼容性好。但有些器件只支持特定模式,配置错了就读不出数据。
踩过的坑:有一次调试一个SPI传感器,怎么都读不出正确数据。查了半天,发现传感器要求Mode 3,我配的是Mode 0。改过来立刻正常。所以拿到新器件,第一件事就是查数据手册确认SPI模式。
4.3 片选信号的时序要求
CS信号看似简单,其实也有讲究:
- 建立时间:CS拉低到第一个SCLK边沿之间要有足够的时间,让从机准备好
- 保持时间:最后一个SCLK边沿到CS拉高之间也要有时间,让从机完成内部处理
- 多从机切换:切换CS时,要确保前一个从机的CS已经拉高,再拉低下一个
有些器件对CS的时序要求很严格,比如某些ADC在CS拉低后需要等待几个时钟周期才能开始转换。这些细节都要查数据手册。
4.4 SPI速率能跑多高
SPI的速率上限主要受三个因素限制:
- 主机的SPI控制器能力:有些MCU的SPI最高只能到系统时钟的1/2或1/4
- 从机的最大时钟频率:查数据手册,比如某些Flash支持50MHz,某些传感器只支持10MHz
- PCB走线质量:速率越高,对走线的阻抗匹配、长度控制要求越高
实际项目中,我一般先按从机手册的最大频率配置,然后逐步降频测试稳定性。如果高速下不稳定,先检查PCB走线,再考虑降频。
| SPI速率问题 | 可能原因 | 排查方法 |
|---|---|---|
| 高速下数据错 | 走线太长、阻抗不匹配 | 缩短走线、加串联电阻 |
| 完全无数据 | CS接错、模式不对 | 检查CS连线、确认CPOL/CPHA |
| 数据偶尔错 | 时序余量不足 | 降低速率、检查建立保持时间 |
| 多从机互相干扰 | CS切换时序问题 | 确保CS切换间隔足够 |
5. 面试高频问题与实战排查速查
5.1 那些年被问过的经典问题
整理一下我遇到过的、以及我面试别人时问过的经典问题:
UART相关:
- 波特率误差怎么计算?能容忍多少?
- 起始位和停止位的作用是什么?
- 奇偶校验能检测出什么错误?检测不出什么错误?
- 流控什么时候需要?RTS/CTS怎么工作?
I2C相关:
- 为什么需要上拉电阻?阻值怎么选?
- 起始条件和停止条件怎么定义?
- 时钟拉伸是什么?有什么作用?
- 7位地址和10位地址有什么区别?
- 总线仲裁是怎么实现的?
SPI相关:
- 四种模式怎么区分?你常用哪种?
- 全双工是怎么实现的?
- 片选信号的作用是什么?可以省掉吗?
- SPI为什么比I2C快?
综合对比:
- 你的项目里为什么选这个总线?
- 如果总线通信失败,你怎么排查?
- 三种总线各自的优缺点是什么?
5.2 总线排查的通用思路
不管是哪种总线,排查思路都是相通的:
第一步,确认硬件连接。用万用表量通断,确认没有虚焊、短路。特别是地线,一定要共地。
第二步,看电源和电平。双方供电是否正常?电平标准是否匹配?3.3V的器件和5V的器件直接对接可能损坏。
第三步,用示波器或逻辑分析仪抓波形。这是最直接的方法。看时钟有没有、数据线有没有跳变、时序是否符合预期。
第四步,对照数据手册。确认时序参数、模式配置、地址设置是否正确。
第五步,简化测试。如果总线上挂了多个设备,先只留一个,排除干扰。
5.3 逻辑分析仪的使用技巧
逻辑分析仪是调试串行总线的利器。我用的是某款8通道的逻辑分析仪,配合开源软件,能直接解码UART、I2C、SPI协议。
使用技巧:
- 采样率要足够:至少是被测信号频率的4倍以上,一般设10倍比较稳
- 触发条件设好:比如I2C可以设起始条件触发,UART可以设特定字节触发
- 解码器配置要对:波特率、数据位、校验位、SPI模式都要和实际配置一致
- 看时序余量:不光看数据对不对,还要看建立时间、保持时间够不够
实操心得:逻辑分析仪抓I2C的时候,如果发现SDA和SCL的上升沿很缓,说明上拉电阻太大或者总线电容太大。这时候即使数据能解码,实际产品里也可能因为温度变化、电压波动而通信失败。所以波形质量比数据正确性更重要。
5.4 常见问题速查表
| 总线 | 问题现象 | 最可能原因 | 快速验证 |
|---|---|---|---|
| UART | 完全无数据 | TX/RX接反 | 交换两根线试试 |
| UART | 乱码 | 波特率不匹配 | 测量位宽反推 |
| UART | 偶尔丢字节 | 无流控、中断延迟 | 开流控或提高优先级 |
| I2C | 无ACK | 地址错、设备没供电 | 用扫描工具遍历地址 |
| I2C | 波形上升沿缓 | 上拉电阻大、电容大 | 减小上拉电阻 |
| I2C | 通信超时 | 从机时钟拉伸 | 确认主机支持情况 |
| SPI | 无数据 | CS没拉低、模式错 | 检查CS和CPOL/CPHA |
| SPI | 数据错位 | 采样沿不对 | 换一种SPI模式 |
| SPI | 高速不稳定 | 走线问题 | 降频测试 |
6. 从面试到实战的几点体会
面试里问总线,表面考的是知识点,实际考的是你有没有真正调过、踩过坑。我见过能把时序图背得滚瓜烂熟但没接过示波器的人,也见过说不出理论但一上手就能定位问题的人。前者面试可能过,但实际项目里会吃亏。
我的建议是,学这三种总线,一定要动手。找一块开发板,接一个传感器,用逻辑分析仪抓波形,把数据手册里的时序图和实际波形对照着看。这样学一遍,比看十遍书都管用。
另外,面试的时候如果被问到不会的,不要硬编。可以说"这个细节我记不太清了,但我的排查思路是……",展示你的方法论比背答案更重要。总线调试的核心能力不是记住所有参数,而是遇到问题时知道从哪里入手。
最后分享一个我常用的调试习惯:每次接手一个新器件,先写一个最简单的读写测试,只读一个寄存器,用逻辑分析仪确认波形正确,再往上叠功能。这样出问题的时候,能快速定位是新功能的问题还是底层通信的问题。这个习惯帮我省了大量调试时间。