做嵌入式开发这些年,从单片机裸机到 Linux 驱动再到物联网终端,跟人聊过、也模拟面过不少候选人,发现一个规律:不管简历上写的是哪个方向,只要问到通信接口,UART、I2C、SPI 这三个协议几乎是必考题。它们被戏称为“串行三剑客”,覆盖了板卡内部器件互联、芯片间通信、传感器数据采集这些最常见的场景。面试官问这些,表面上是在考察你懂不懂协议本身,实际想看的是你有没有真正拿示波器看过波形、有没有在调试中处理过数据错乱和总线卡死这类现场问题。这篇文章就把这三个协议的面试高频考点整理成一份完整的复习地图,从帧格式、时序细节、仲裁机制到实际调试中的典型坑,一次讲透。适合正在准备嵌入式软硬件岗位面试的同学,也适合刚入行想系统梳理串行通信基础、想把手头代码和芯片手册真正对应起来的工程师。
1. 为什么面试官死磕这三个协议
1.1 串行通信的江湖地位
你很难找到一块 MCU 开发板能绕开这三套协议。单片机内置的外设清单里,标配几乎永远是几路 UART、一路 I2C、一两个 SPI 控制器;外设芯片这边,各类传感器、存储器、屏幕驱动、网络芯片的接口也基本扎堆在这三个协议里。串行通信的核心就是“一条线一个 bit 一个 bit 地传”,相比并行总线省引脚、省 PCB 面积、抗干扰设计也更灵活,代价是要靠协议来协商起始、结束、数据位和校验。
UART、I2C、SPI 恰恰代表了三种完全不同的同步思路:UART 靠双方约定波特率,自己从引脚电平里恢复时钟,属于典型的异步通信;I2C 和 SPI 都靠主机输出时钟信号,从机被动跟随,属于同步通信。但 I2C 只有两根线,数据还得在时钟的低电平期间变化;SPI 直接拉出独立的时钟加双数据线,主从之间可以双向同时传。这三个协议刚好串起了“时序、电平、仲裁、速率、可靠性”这几大嵌入式基本功,所以面试官爱从它们下手。
1.2 三剑客各有各的脾气
- UART:点对点异步串口,收发各占一根线,全双工跑起来一支 TX 一支 RX。没有专门的时钟线,全靠收发双方提前约定波特率。
- I2C:两线制半双工总线,一条串行数据线 SDA、一条串行时钟线 SCL,属于开漏结构,多个设备直接挂同一根总线上,通过地址区分谁跟谁说话。
- SPI:典型四线制同步全双工总线,主机负责提供时钟和片选,从机被选中后就在时钟边沿收发数据,通信速度可以拉得很高。
明白这三者“一个异步、一个两线制多主总线、一个四线制高速交换”,后面所有细节都围绕着这几个根本差异展开。面试题问得再刁钻,最后落脚点也逃不出这些性质的衍生问题,比如“I2C 为什么要上拉”“SPI 为什么不用地址”“UART 波特率误差多少算超标”。
2. UART 考点全拆解:异步时序里的高频陷阱
2.1 帧格式与时序,一张图记牢
UART 的帧结构是所有协议里最经典的。默认空闲时 TX 线保持高电平,要发送一字节数据时,先拉低一个位时间,这是起始位;接着按 LSB 在前、MSB 在后的顺序输出数据位;数据位之后是可选的校验位;最后至少拉高一到两个位时间的停止位。一个典型 8N1(8 数据位、无校验、1 停止位)的字节格式按时间顺序展开就是:
空闲(高) | 起始(0) | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 停止(1) | 空闲(高)面试里经常出现“画一下 UART 发送 0x55 的波形”这种题。0x55 的二进制是 01010101,按 LSB 先发,数据位就是 1、0、1、0、1、0、1、0,配合起始位和停止位,时序就是高低交替的方波,这也是为什么调试时很多人拿 0x55 做测试数据——波形规则,肉眼就能判断电平翻转是否正常。
UART 只有起始位和停止位做对齐,没有独立的时钟线,所以收发双方必须把波特率配置得足够一致。常见的 9600、115200 都是标称速率,实际由时钟源分频得到,两个设备即使都写 115200,因为晶振误差和分频取整的原因,真正的波特率也会有一点偏差。接收端通常用波特率时钟的 16 倍频去采样,在每个 bit 中间做多次采样取多数表决,这样可以容忍一定误差,一般收发两端波特率误差合计控制在 2%~3% 以内比较稳妥。超过这个范围就容易出现“数据偶发错位、每几个字节里跳出一个乱码”的典型症状。
2.2 物理层、流控与常见扩展
基础的 UART 是 TTL 电平,适合板内短距离通信。工业现场常把 TTL 串口转成 RS-232 或者 RS-485,前者的电平摆幅更大、距离能到十几米;后者使用差分信号,配 120Ω 终端匹配电阻,能拉到几百米甚至上公里,而且 RS-485 是半双工共线结构,多个节点靠地址轮询来通信,所以 MCU 的 UART 外设往往支持 9 位数据模式,其中第 9 位用来标记地址帧,配合收发切换的使能脚实现多机通信。
流控方面要分清两种:硬件流控用 RTS/CTS 两根脚,接收缓冲区快满时拉低 RTS 通知对端暂停;软件流控 XON/XOFF 在数据里插控制字符,应用层处理,麻烦且容易和普通数据冲突。我在实际项目里更倾向硬件流控,只要线没接错,基本不用管;但很多人第一次做透传模块时把 RTS/CTS 悬空,对端等流控信号导致卡住,调试半天才发现是线序问题。
2.3 高频考点清单
面试官针对 UART 最常问这几类问题,按我的经验挨个归纳一下:
- UART 是同步还是异步?为什么?答:异步,没有共享时钟线,接收端用本地采样时钟恢复数据,依赖起始位对齐。
- 1 秒能传多少字节?比如 115200、8N1,每个字节 10 个 bit 时间,理想情况下每秒约 11520 字节。要注意这个“约”,因为开头结尾还有起始停止位开销。
- 校验位的用途和局限。奇偶校验只能发现奇数个 bit 翻转,无法纠错,一来一回在协议栈里加校验才是常态。
- 波特率不匹配会有什么现象?数据整体错位、帧错误、乱码。
- 9 位数据模式是干嘛用的?RS-485 多机通信的地址标记。
2.4 实调经验与避坑
实际调板子时,我最推荐的排查顺序是:先发连续 0x55,用示波器看 TX 脚有没有方波;再用逻辑分析仪抓一帧,数起始位、数据位、停止位是否和配置一致。如果波形正常但对端收到的数据不对,优先怀疑波特率误差和线路电平转换电路方向。接线类模块 TX/RX 交叉是最常见低级错误,逻辑分析仪一抓就原形毕露。
还要注意,部分 MCU 的 UART 外设内部有 FIFO,发送 API 写完寄存器并不代表数据已经出引脚,尤其 DMA 模式下要等发送完成标志位再关外设。我在项目里就遇到过 sleep 前关掉串口、结果最后一个字节被截断的事,就是这个原因。
3. I2C 考点全拆解:两根线上的多主江湖
3.1 物理层:为什么必须开漏加外部上拉
I2C 让人最容易懵的地方就在物理层。SDA 和 SCL 都是开漏结构,芯片内部不主动输出高电平,只能拉低或释放,高电平全靠外部上拉电阻提供。这样设计的最大好处是多个设备可以同时挂在这两根线上,任何设备想拉低就拉低,不会出现一个设备输出高、另一个设备输出低导致的短路灌流。总线上的电平逻辑可以理解成“线与”:任何一个设备拉低,总线就是低,想表达高电平的设备只需要不管它。
上拉电阻的取值直接影响上升沿时间和通信速率。阻值太小,灌电流大、功耗高甚至拉伤端口;阻值太大,总线电容充电太慢,高速模式下波形爬坡不到位,会造成时序不合格。I2C 标准里规定上升时间,实际工程上常用 4.7kΩ 配 100kHz、2.2kΩ 配 400kHz,具体要结合总线上挂的设备数量和走线长度估算总线电容。有经验的面试官会追问“上拉电阻怎么算”,这时可以答出 RC 充电时间常数、查器件手册的总线电容参数、再按目标频率反算阻值这个思路,比死记某个数值有价值得多。
3.2 时序细节:起始、停止、ACK、重复起始
I2C 的时序规约是面试里最容易被抠细节的部分,必须掌握这几个动作:
- 起始条件 START:SCL 为高时,SDA 发生高到低跳变。
- 停止条件 STOP:SCL 为高时,SDA 发生低到高跳变。
- 数据位规则:SDA 只能在 SCL 为低电平期间变化,SCL 为高时 SDA 必须稳定。
- 应答 ACK/NACK:每收完一个字节,接收方在第 9 个 SCL 周期的 SDA 上拉低表示 ACK,保持高表示 NACK。
- 重复起始 REPEATED START:不拉停止,直接在一个操作中间再次发出起始条件,用于读 EEPROM、传感器切换寄存器地址等场景。
这段用文字描述很绕,但用波形画出来就非常直观:
起始: SCL ____/‾‾‾‾‾‾‾‾ SDA ____/‾‾‾‾‾‾‾‾ (先SDA从高变低,SCL保持高) 停止: SCL ‾‾‾‾‾‾‾‾\____ SDA ‾‾‾‾‾‾‾‾\____ (SDA从低变高,SCL保持高) 字节: SDA 稳定期只在SCL低电平时改变,SCL高时取值面试题常出“为什么读寄存器要先发设备地址再发寄存器地址,中间不加 STOP”?答案就是重复起始保证了总线占用不释放,避免其他主机插入操作,提高读操作的事务完整性。
3.3 仲裁与时钟拉伸
I2C 支持多主机同一条总线,这是它的独门绝技。当两个主机同时发起传输,各自输出自己的 SDA 电平,总线开漏结构和线与特性会让其中一个发现“我想发高电平,但总线上是低电平”,说明自己失去了仲裁,立刻停止发送,等对方操作完再重试,整个过程不破坏数据传输。背后的原理就是 SDA 的电平比较:谁先想拉高却发现拉不上去,谁就输。
时钟拉伸则是从机端的能力,从机处理不过来时,可以在第 9 个时钟周期把 SCL 拉低不动,主机检测到 SCL 一直低就知道从机在“喊暂停”,等从机处理完再释放 SCL,时钟自然恢复继续。主机的 I2C 控制器必须支持超时判断,否则从机因为故障一直拉低 SCL,整条总线就会卡死。这个点我见过不少面试官设计成场景题:“总线 SDA 一直为高、SCL 一直在翻转,这个系统大概是什么问题?”答案里如果带着排除时钟拉伸和仲裁冲突的思路,基本能过。
3.4 读操作时序:EEPROM 读怎么写才不出乱码
I2C 读写操作是纯考手写能力的题。以常见的 EEPROM 读取为例,标准流程是:
- 发出 START;
- 发送设备地址字节与写标志(如 0xA0);
- 等待从机 ACK;
- 发送要读取的存储地址;
- 等待 ACK;
- 发送 REPEATED START;
- 发送设备地址字节与读标志(如 0xA1);
- 读回数据,最后一个字节回 NACK 表示结束;
- 发送 STOP。
这里最容易写错的点是第 8 步。很多人习惯统一对每个字节都回 ACK,结果最后多读一个字节才结束,或者主机一直发 ACK 把从机数据读不完;正确做法是连续读取时除了最后一个字节都回 ACK,最后一字节回 NACK 再拉 STOP。逻辑分析仪上能明显看到最后一个字节后 SDA 不再被拉低。
3.5 调试经验:总线卡死怎么救
I2C 调试的最大噩梦是总线被某设备拉低导致通信完全瘫痪。最常见原因是某个从机上电时序不对,先抢占了 SDA 低电平;或者从机在等待主机时钟时主机提前放弃,从机一直保持 SDA 低。现场急救手段是给 SCL 连打 9 个以上的脉冲,让从机完成当前字节的周期,然后发一个 STOP 条件恢复空闲态,这是原始但有效的“总线解锁”操作。
另一个经验是初始化外设时别急着配置成多主机模式,能单主机就单主机,先把时序跑通再加复杂度。写驱动时每次传输前检查总线忙标志,必要时加入启动前的超时重试,我从那之后就没再遇到过一上电就卡死的情况。
4. SPI 考点全拆解:四线高速全双工的优雅和陷阱
4.1 物理层与基本拓扑
SPI 的物理层比 I2C 好懂,因为它把同步要素拆得很清楚:主机输出时钟 SCLK,主机输出数据线 MOSI(Master Out Slave In),从机输出数据线 MISO(Master In Slave Out),每个从机还有一条片选线 SS/CS,低电平有效。典型拓扑是一主多从,从机不共享片选,主机几条 CS 线拉出来即可;也可以把从机串成菊花链,前一个的 MISO 接到后一个的 MOSI,减少主机引脚,但层级多、延迟大,用得少。
因为没有地址概念,SPI 天然是“片选即点名”,哪个从机的 CS 被拉低,哪个就跟主机对话。从机在 CS 未被选中时,MISO 一般处于高阻态,避免多个从机同时驱动数据线相互打架。面试题“为什么 SPI 从机的 MISO 要求高阻输出”这是标准答案。
SPI 是全双工,主机发一个 bit 的同时就能收到从机一个 bit。这个特性经常出成一个心机题:“主机发出一个字节的同时收到了什么?”答案是同时收到从机的一个字节,数据交换是同步双向的。
4.2 CPOL、CPHA 与四种模式
SPI 最容易把人绕晕的就是四种工作模式,核心是 CPOL(时钟极性)和 CPHA(时钟相位)。CPOL 决定空闲时 SCLK 的电平:CPOL=0 时空闲低,CPOL=1 时空闲高;CPHA 决定数据采样点是第一个跳变沿还是第二个跳变沿:CPHA=0 时在第一个边沿采样,CPHA=1 时在第二个边沿采样。
组合起来就是:
模式0: CPOL=0, CPHA=0 空闲低,第一个边沿(上升沿)采样 模式1: CPOL=0, CPHA=1 空闲低,第二个边沿(下降沿)采样 模式2: CPOL=1, CPHA=0 空闲高,第一个边沿(下降沿)采样 模式3: CPOL=1, CPHA=1 空闲高,第二个边沿(上升沿)采样实际项目里模式 0 用得最多,大多数 Flash、SD 卡、传感器默认就是模式 0/模式 3 中之一。面试里常给一张时序图让你判断这是模式几,判断方法就是先看空闲电平对应 CPOL,再看数据在哪条边沿被采样对应 CPHA,先定极性再数边沿,不会有错。
最坑的地方是主从模式配置不一致,主从两边一个用模式 0 一个用模式 3,数据总是错位,抓波形又看不出来明显异常,因为边沿都对上了,只是采样的时刻不对。我调 WiFi 模块 SPI 通信时遇到过这个问题,最后是对着芯片手册的时序图逐个边沿核对,才发现采样沿差了一个相位。
4.3 数据传输与速率上限
SPI 没有 ACK 机制,主从之间没有任何应答信号。主机发完一帧,根本不知道从机是否成功接收,所有可靠性都得靠协议上层自己解决,比如帧尾加校验、应答字段、超时重传。答这道题时顺便把“为什么 SPI 比 I2C 简单但容错弱”讲清楚,面试官会认可你对协议本质的理解。
SPI 速率上限由从机支持能力、PCB 走线、负载电容和主机时钟源共同决定。上百兆比特率的 SPI 在板级短走线场景并不罕见,很多存储芯片的读取时钟可以跑到几十兆甚至上百兆。不过一提高速,波形质量就成了问题,反射、串扰、边沿变缓都会在高速抓拍时变成随机误码。所以高速 SPI 设计时要注意串阻匹配、少打过孔、从机尽量靠近主机,调试时先降低分频把通信跑通,再逐步提高时钟找极限。
4.4 高频考点清单
- 简述 SPI 四种模式,能画出时序图最好。
- SPI 和 I2C 的时钟线有什么区别?I2C 时钟从机也能拉低(时钟拉伸),SPI 时钟完全由主机掌控。
- SPI 为什么不用仲裁?单主机结构由片选决定通信对象,多个从机之间通过片选互斥,没有电平竞争的仲裁需求。
- 从机如何知道什么时候发送数据?靠 CS 拉低和时钟边沿,在使能期每个边沿同步移位。
- 如果从机 RX 没有及时读,数据会怎样?FIFO 溢出导致丢数据,SPI 没有流控机制,驱动层必须保证时序。
4.5 调试经验:波形对却读不对,先查哪几处
SPI 调试第一步永远是抓 SCLK 和 CS,确认时钟确实在翻转、片选确实拉低。第二步抓一个完整字节,数边沿核对模式号。第三步看 MISO,如果 MISO 一直是高或一直是低,多半是片选没选对或从机没上电;如果波形有翻转但数值不对,优先怀疑线序、复用引脚配置、以及从机进入掉电模式。
代码层面还有一个容易忽略的坑:很多 MCU 的 SPI 发送接口“发送完成”标志指的是移位寄存器发完,不代表从机已经采样完毕;如果紧接着切换片选或模式,可能在最后一个边沿上少了一个时钟周期的“尾巴”,最后一字节丢位。稳妥做法是确认 SPI 忙标志清掉后再操作片选。
5. 三协议横向对比与选型答题逻辑
5.1 一张表看清性格差异
面试到了对比题,先别急着背参数,我把它们放到一张表里,作为答题的骨架:
| 维度 | UART | I2C | SPI |
|---|---|---|---|
| 时钟来源 | 无,双方自定波特率 | 主机时钟,从机可拉伸 | 主机时钟 |
| 典型线数 | 2~4(TX/RX/流控) | 2(SDA/SCL) | 3~4(SCLK/MOSI/MISO/CS) |
| 双工模式 | 全双工(典型) | 半双工 | 全双工 |
| 多设备能力 | 点对点 | 多主多从,需地址 | 一主多从,片选区分 |
| 应答机制 | 无内建 ACK | 每字节 ACK | 无 |
| 速率范围 | 低到中 | 低到中(默认 100k/400k 级别) | 可拉很高 |
| 抗干扰/复杂度 | 中等 | 开漏弱上拉,实现复杂 | 实现简单,信号质量要求高 |
5.2 面试选型题的三个切入角度
如果面试官问“三个协议你怎么选”,我的答题思路永远是先抛约束条件,再讲协议特性匹配,分三层展开:
第一层看速率需求。传感器读数每秒就更新几十次,I2C 足够;音频流、刷屏这种连续大批量数据,SPI 更合适;远程低速透传和数据记录,就得靠 UART 转出到 RS-485 等物理层。第二层看拓扑结构。多设备且允许半双工,I2C 只用两根线就能串一长串芯片,省引脚优势明显;设备少但要求双向同时传,SPI 结构简洁性能好;严格点对点、对实时性不敏感的老式模块,UART 通用性和生态最好。第三层看可靠性和流控细节。I2C 有 ACK 和时钟拉伸,天然能做慢速设备的流控;SPI 没这些,适合主从都能保证时序的场景;UART 需要收发双方预先协商,简单但协议层错误检测要靠自己加。
答题时能把这三个层次说清楚,比单纯报参数更能体现工程判断力。
6. 面试实战:高频题速查与避坑经验
6.1 高频题速查表
我整理了一张自用的速查表,按“问题—答点—展开方向”列出来,平时复习按这个过一遍效率较高:
| 高频问题 | 核心答点 | 加分展开方向 |
|---|---|---|
| UART 和 I2C 区别是什么? | 一异步一同步,一线对一数据一总线 | 结合波特率误差与时钟拉伸展开 |
| I2C 数据线为什么要上拉? | 开漏线与结构,外部高电平来源 | 上拉阻值对上升时间的影响 |
| 什么是重复起始? | 避免总线释放的二次起始 | 结合 EEPROM 随机读流程讲 |
| 什么是时钟拉伸? | 从机拉低 SCL 暂停主机 | 控制器超时要开,否则总线卡死 |
| 仲裁怎么发生? | 多主机同时发,SDA 电平角逐 | 与 CSMA 机制对比,强调无损仲裁 |
| SPI 模式怎么确定? | 先看空闲电平再看采样沿 | 用示波器抓波形实测最靠谱 |
| SPI 为什么不带 ACK? | 协议简单、速率优先 | 上层自定义校验与重传 |
| 三个协议怎么选? | 速率、拓扑、可靠性和引脚成本综合权衡 | 结合具体器件举例 |
6.2 独家避坑清单
最后分享几个我在面试和技术支持里反复见到的坑,提前避掉能少走一周弯路。
- 写 I2C 驱动时,起始条件一定不能让主机同时拉低 SDA 和 SCL,标准是先 SDA 后 SCL,有些控制器库函数封装得不好,直接造成非法起始,从机不认。
- I2C 读多字节时,倒数第二个字节之后就该准备 NACK 语义,等最后一个字节再回 NACK,顺序反了会多收数据。
- UART 板间通信一定要共地,飞线调试时两根信号线一根电源线一根地线是基本功,板间电平参考点不同,波形上看起来是好的,数据就是乱。
- SPI 高速模式下,如果发现偶发误码,先在 SCLK 上串一个几十欧姆的电阻,别一上来就调分频或改模式,往往就是反射在作怪。
- 所有串口打印调试信息,务必加缓冲区满保护和阻塞发送标志判断,DMA 模式下调用发送 API 后不是立刻完成的,提前关串口会截断尾巴。
- 面试被问到“你调过最棘手的通信问题”时,别只讲现象,把“抓到波形—对手册—怀疑时序—修改—验证”这套完整 debug 路径说出来,比背十个协议概念都有说服力。
我个人在实际干活里最大的体会是:协议这东西,背十遍不如拿逻辑分析仪抓一次真实波形。UART 的起始位跳变、I2C 的 ACK 脉冲、SPI 的边沿采样,每一个细节都在波形上写得很明白。面试前如果有条件,拿一块开发板把这三个协议各写一遍收发程序,再故意制造波特率不匹配、SPI 模式错位、I2C 无上拉这三种故障看波形,比什么速成资料都管用。考试过了之后,这些波形记忆还会在真正的项目调试中继续帮你省时间。