上个星期我作为面试官面了一个做嵌入式Linux三年的候选人,前面聊项目都好好的,我问了一句“UART、I2C、SPI,你平时用哪个最多?”对方说I2C。然后我追问“那I2C为什么要开漏?”他愣了一下,说“因为协议规定要开漏”。这种回答其实比完全不会更危险——它说明你会用这个外设,但并没有理解它在物理层上的本质。嵌入式面试中,UART、I2C、SPI这三个串行协议之所以年年必考,不是因为面试官无聊,而是因为这三个协议直接反映一个人从驱动到硬件设计的综合素养。
这篇文章面向正在准备嵌入式软件或嵌入式硬件岗位面试的同学,也适用于那些在工作里把外设调通就算完事、想回头补基础概念的工程师。我会站在面试官角度,把这些年在面试和实际项目中高频出现的知识点、易错点、追问逻辑一层层剥开。你会发现,这三个协议并不难,难的是把“为什么会这样设计”和“出故障怎么查”这两件事说清楚。下面直接进入正题。
1. 先搞懂面试官的出题逻辑:三大协议的底层差异决定了哪些坑
1.1 为什么面试总是绕不开这三件套
如果你翻过几十份嵌入式岗位的面经,会发现不管公司做单片机、Linux驱动还是FPGA,UART、I2C、SPI这三样东西必被问到。原因很简单:它们是嵌入式世界里最常用的三种串行通信方式,几乎每一块芯片、每一个传感器、每一片Flash都跑着其中之一。面试官通过你对这三个协议的理解,能快速判断你是在背八股,还是真的理解数据是怎么从一个引脚飞到另一个引脚的。
更微妙的是,这三个协议之间有着非常强的对照关系:UART是异步的,I2C和SPI是同步的;UART和SPI是全双工,I2C是半双工;I2C和SPI支持多设备挂载,UART本质上只是点对点。把这些差异放在一起看,就能看出它们各自的设计取舍。面试官问它们,其实是在考察你是否具备从物理层看协议的眼光。
1.2 一张表看透物理层本质
先把最核心的差异整理成一张表,这张表是面试现场“开头三分钟”的定海神针。
| 特性 | UART | I2C | SPI |
|---|---|---|---|
| 信号线数量 | 2(TX/RX) | 2(SDA/SCL) | 4(MOSI/MISO/SCLK/CS) |
| 同步/异步 | 异步 | 同步 | 同步 |
| 通信方式 | 全双工 | 半双工 | 全双工 |
| 寻址方式 | 点对点无需寻址 | 7位/10位地址 | 片选信号 |
| 常见速率 | 一般到数Mbps | 100kHz/400kHz/1MHz/3.4MHz | 可达数十Mbps |
| 主要拓扑 | 点对点 | 多主多从总线 | 一主多从 |
| 是否硬件应答 | 无 | ACK/NACK | 无 |
这张表里藏着第一个面试陷阱:很多人会把I2C的“两线”和UART的“两线”混淆。UART的两根线是数据收发各占一根,所以能全双工;I2C的两根线一根时钟一根数据,数据线是双向的,所以同一时刻只能有一个方向在发送,本质是半双工。这个区别如果能在讲解时主动点破,面试官对你的印象会立刻不一样。
1.3 面试官最喜欢的三层追问
我观察到的面试追问,几乎总是沿着三层递进:第一层是“协议是什么”,考察你会不会用;第二层是“为什么这样设计”,考察你是否理解物理和成本约束;第三层是“如果出了故障你怎么调试”,考察实战能力。很多人卡在第一层和第二层之间的衔接上,比如知道I2C有ACK,却说不清ACK在开漏总线上是怎么产生的。后面每个协议我都会按这三个层次给你拆解,你准备的时候也可以拿这套逻辑给自己来一场模拟面试。
2. UART:最“随缘”也最要命的串口协议,面试必问的波特率与帧结构
2.1 从起始位到停止位:UART收发一帧到底发生了什么
UART是异步通信,没有时钟线,收发双方必须事先约定波特率。一帧的标准结构从空闲的高电平开始,先拉低一个位宽作为起始位,接着是5到8位数据(低位在前),然后是校验位(可选),最后至少1个停止位(保持高电平)。接收方就是靠这个起始位的下降沿来同步时钟的。面试时我会让候选人画出这个波形,画不出来的人说明只是调过API,没有真正从寄存器层面理解。
举个具体例子,8N1格式:8个数据位、无校验、1个停止位。如果波特率是115200,那么每一位的时长就是1/115200秒,约8.68微秒。起始位和停止位就是两个特殊边沿,真正要传的数据包在这两个边沿之间。注意,很多人面试时说“UART是TTL电平”,这其实不准确。UART本身是一种协议,电平可以是TTL,也可以是RS232的正负12V、RS485的差分信号,这一点经常被混淆。你应该说“UART协议工作在什么物理电平上”,而不是把协议和电平混为一谈。
2.2 波特率误差:为什么你收发会出乱码
面试常问“为什么115200的波特率,用9600去收会乱码?”这其实是在考采样和容差。UART接收端通常以16倍波特率采样,也就是说每个数据位内部采16次,取中间那几次作为稳定值。如果发送方和接收方的波特率误差在一定范围内(一般要求小于2%到3%),还能正确接收;超过这个范围,采样点就会逐渐偏出数据位的稳定区域,导致乱码。
实际项目中,我遇到过晶振偏差导致两片STM32互相通信乱码的情况。用逻辑分析仪抓到波形,测了一下实际位宽,发现误差达到了4%。后来把其中一个芯片的波特率寄存器改成在理论值附近能补偿误差的整数后,通信就正常了。面试时如果能主动提到“需要根据实际晶振误差计算BRR寄存器重装值”,面试官会认为你真正调过板子,而不是只会调用HAL库的UART_SendData。
2.3 硬件流控和软件流控:什么时候需要RTS/CTS
接着面试官会问“如果串口接收方处理不过来怎么办?”答案有三个方向:软件上减少发送速率或加协议应答;普通UART没有流控的话,就得靠接收缓冲溢出中断来避免丢数据;硬件上可以使用RTS/CTS流控,让接收方通过拉低RTS告诉发送方暂停。很多工程师以为RTS/CTS只有RS232才有,其实TTL串口上也常用,尤其在蓝牙模块和WiFi模块之间。
我还遇到过半双工RS485的问题。RS485本质是基于UART的物理层差分传输,但它的物理层和UART协议本身没关系。RS485只有1对差分线,同一时刻只能一个方向发送,所以需要方向控制引脚来切换收发状态。面试中如果提到“485和UART的区别”,你最好说出来UART是协议层概念,RS485是物理层标准,两者是独立维度。这个点在很多面经里都是高频考点,能主动讲清楚,显得你基础很扎实。
2.4 Linux下的串口编程:面试里的加分项
现在嵌入式Linux岗位非常普遍,面试官可能会问你Linux下怎么操作UART。核心几个点:设备节点是/dev/ttyS0、/dev/ttyUSB0(USB转串口),用termios结构体设置波特率、数据位、停止位、流控;open之后要设置raw模式,其中VMIN和VTIME两个参数直接影响read阻塞行为。FT231X这类USB转UART驱动其实也是普通串口驱动,只是枚举成了USB CDC设备。能把“设置termios、用select或poll处理多路串口、避免read被信号打断”这几个点说清楚,就已经胜出很多人了。
3. I2C:两根线承载的总线智慧,开漏电路是理解一切的钥匙
3.1 从SDA/SCL到地址编码:I2C的数据帧格式
I2C的帧结构是我在面试时最喜欢问的内容。它一帧从SCL高电平下SDA产生一个下降沿(START)开始,后面跟着7位从机地址加1位读写位,然后从机在第9个时钟周期拉低SDA作为ACK。如果是主机读从机,地址字节后跟的是从机发送的数据,主机要回应ACK或NACK。最后主机产生STOP,也就是SCL高电平下SDA的上升沿,一帧结束。
这里有个细节,很多候选人搞混:I2C的地址是7位,但很多芯片手册写的是0x68这样的8位地址,那是“7位地址左移1位后加上读写位”。比如0.9寸OLED用的SSD1306,7位地址常见是0x3C,但实际往总线上发地址字节时,写操作发的是0x78,读操作发的是0x79。如果不小心把7位当成8位直接写,设备会找不到。这种细节在我们调试RDA5807软件I2C的时候也踩过,寄存器地址和写入字节的顺序一错,读出来的数据全是垃圾。所以面试时提到“设备地址”,最好能主动说明你清楚7位地址和8位地址的区别。
3.2 为什么必须开漏加上拉:电气特性与面试陷阱
I2C最核心的考点就是开漏。为什么SDA和SCL必须是开漏结构?因为I2C总线允许多个设备挂在一起,如果每个设备都用推挽输出,那么一个设备输出高、另一个输出低就会导致总线短路。开漏输出的意思是单片机只控制管子导通到地或者截止浮空,高电平由外部上拉电阻提供。任何设备都可以把总线拉低,但只有上拉电阻能把总线拉高,这样就实现了“线与”。
面试官通常还会接着问“上拉电阻选多大?”一般来说,100kΩ不可用,选太小电流会过大,选太大会导致上升沿变慢,影响高速模式。对于100kHz标准模式,典型值4.7kΩ到10kΩ;400kHz快速模式,典型值2kΩ到4.7kΩ。更严谨的做法是根据总线等效电容估算上升时间,保证RC常数远小于时钟周期的一半。如果芯片或模块内部已经带了上拉电阻,外部再接一个并联的话,等效阻值会变小,灌电流可能超规格,在低功耗设计里尤其要注意。OLED模块大多有板载电阻,这点值得在面试中提一嘴。
3.3 时钟同步与仲裁:多主机共存时的两个经典难题
放宽到多主机场景,面试官会问I2C的仲裁机制。因为开漏的特性,两个主机可以同时发送数据,如果主机A发低电平,主机B发高电平,此时总线被A拉低,B如果读回SDA发现和自己发送的不一致,就知道自己输了,主动退出仲裁。所以仲裁发生在高电平到低电平的转换上,赢家是无意间先发送低电平的一方。
时钟同步则是SCL线上的现象。多主机各自产生时钟,由于开漏“线与”,最快的主机在低电平期间会被其他主机拉低,从而延长低电平时间,结果就是大家互相取慢,所有主机同步到最慢的那一个。这个机制可以保证多个主机不会在时钟上错位。面试时把“线与”“仲裁”“时钟同步”连起来讲,逻辑链条就完整了。很多候选人单独背零碎概念,一追问就串不起来,这是最可惜的。
3.4 调试I2C:波形特征与常见故障排查
实际调试I2C,我强烈建议你搞一个USB逻辑分析仪,几十块钱那种就行。抓波形时先看START是不是在SCL高时SDA拉低,再看ACK位是不是低。常见故障有这么几种:第一种是地址不对导致设备无ACK,波形里找不到第九个低电平;第二种是上拉电阻缺失导致波形上升沿很缓慢,可能体现成短小的毛刺,低速时看不出,高速时就拉不上去;第三种是没有共地,SDA波形乱飞,各个设备电平基准不一样。
我调试0.9寸OLED兼容性问题时遇到过一个很隐蔽的坑:屏幕模块自带上拉电阻,但同时还板载了一个电平转换电路,导致I2C引脚呈现很强的上拉,SCL上沿被拉得很快,可是某些MCU的引脚配置成开漏时,如果内部有弱的上下拉,会和外部产生分压,导致低电平不够低。后来我把MCU引脚配置成“开漏且关闭内部上下拉”,问题就消失了。这种经验在面试时说给面试官听,比背概念有说服力得多。
4. SPI:四条线的高速特权通道,但片选与CPOL/CPHA才是拉开差距的地方
4.1 SPI四线职责与典型连接,硬件片选与软件片选的取舍
SPI的全称是串行外设接口,它用四条线:SCLK时钟、MOSI主出从入、MISO主入从出、CS片选。它的本质是一个环形移位寄存器,主从双方在时钟边沿移入移出,所以是全双工。面试时我会让候选人画一个典型连接图,看他是不是理解为什么要共地、为什么要加片选。如果连“从机的MISO要接主机的MISO”这种最基础的一一对应关系都画反,那后面就没法聊了。
片选有两种实现方式:硬件片选由SPI控制器自动拉低CS再自动拉高,软件片选就是你拿一个通用GPIO手动控制。硬件片选的好处是CPU介入少,尤其在DMA传输时效率高;缺点是如果你有多个从机,控制器固定的CS引脚不够用,必须要软件参与。软件片选的好处是你可以随意接任意GPIO,灵活性高,还能做“CS拉低但多个从机同时听”这种特殊应用,缺点是时序控制要仔细,特别是CS拉低到第一个时钟沿之间的建立时间,必须在代码里预留。面试官经常挖一个坑:SPI总线上如果CS由硬件自动控制,但从机数量很多,比如一个MCU接8个SPI Flash怎么办?答案是用GPIO扩展CS,硬件模块只提供时钟和数据,CS完全由用户GPIO控制。很多人以为硬件SPI必须用固定的CS引脚,这个误解在实战中经常导致画板困难。
4.2 CPOL/CPHA极性相位图,快速判断配置的高效办法
CPOL决定时钟空闲电平,CPOL=0表示空闲低,CPOL=1表示空闲高;CPHA决定采样沿,CPHA=0表示在第1个边沿采样,CPHA=1表示在第2个边沿采样。把两者组合,共4种模式。面试时你不需要背模式号,但一定要能画波形,或至少能说清楚“采样沿必须避开数据变化沿”。
我这里有一个现场判断法:打开数据手册里的时序图,看SCLK的第一个边沿和MOSI数据变化的关系。如果数据在SCLK第一个边沿之前已经稳定,且是在第一个边沿被采样,那多半是CPHA=0;如果数据是在第二个边沿采样,那多半是CPHA=1。再把空闲电平一看,CPOL就出来了。只要和从机的时序图上标注的采样边沿对上,就能一次配好。面试官如果把四种模式画出来让你配对,你按这个方法一步步分析,比他期待的死记硬背要高明得多。
4.3 SPI的高速率与极限应用:Flash、屏幕、ADC
SPI区别于前两者最大的优势是速度,可轻松跑到几十Mbps。比如常见的W25Q64 SPI Flash,支持最高133MHz时钟,四线QSPI模式可以达到更夸张的吞吐。面试官问“为什么SPI比UART快那么多?”你如果只答“因为有时钟线”是不完整的。更本质的原因是SPI的采样边沿由主机主动控制,不需要像UART那样花额外的位时间去做同步;也没有I2C的开漏和应答,信号摆幅变化更快,可以大幅提升时钟。
实际项目中用SPI驱动TFT屏幕,或者用DMA刷屏时,要注意一个坑:如果你的屏幕数据线只需要写不需要读,MISO可以悬空,但SPI控制器仍然会去采样MISO,并不会影响传输。另外像J-Link烧录SPI Flash这种操作,本质上就是通过SPI接口对Flash做编程,和MCU通信没有区别,只要时序正确,工具就是主机,Flash就是从机。这个经验在面试中能证明你理解SPI的本质,而不只是会调用封装好的库函数。
4.4 SPI启动与Linux下的应用:面试加分场景
有些高密度考题会问:处理器用SPI启动启动,比如TI的C6678的SPI启动流程。一般会经历:芯片复位后Boot ROM从SPI主设备读取启动头,根据启动头里的配置进入烧写模式或引导加载。整个过程涉及合成烧写文件——把程序镜像、CRC、启动参数排成一个二进制镜像,由烧写工具一次写入SPI Flash。面试时能把这个流程说出来,再提到“要注意SPI的时序配置必须和Boot ROM的默认极性一致,否则根本读不到头”,就很出彩。
Linux下SPI应用则集中在spidev接口。它在设备树里声明一个SPI外设节点,应用层通过open(“/dev/spidevX.Y”)配合ioctl设置SPI_MODE_0、SPI_IOC_WR_MAX_SPEED_HZ,再用read/write或SPI_IOC_MESSAGE发送数据。很多工程师第一次用Linux SPI都被片选搞晕,实际spidev的片选由内核控制,用户态只要配置好模式就行。把“硬件片选与软件片选的取舍”放在这里讲,面试官会觉得你既有单片机底层经验,又懂Linux框架。
5. 面试中必被追问的“进阶三连”:DMA、中断与低功耗中的串行通信
5.1 DMA搬运数据时,UART/I2C/SPI之间有什么差异
外设通信一旦涉及大数据量,比如UART刷日志、SPI刷屏、I2C读大容量的EEPROM,面试官就会追问DMA。你需要理解,DMA控制器通过硬件自动搬运外设FIFO和内存之间的数据,CPU只在传输完成时收到中断。如果你的项目里有明确的数据流场景,一定要主动提到DMA,这样才能展示你有处理高吞吐量的经验。
三者的DMA配置细节差异在于触发条件:UART一般有TX完成和RX非空触发;I2C的DMA要特别关注方向切换时的总线状态,因为在重复起始或者读最后几字节时,必须手动控制主从机产生NACK和STOP;SPI则很简单,只要CS有效,时钟一直跑,DMA可以连续搬移。最容易出问题的反而是I2C,很多工程师在调I2C DMA时发现要么读不到数据,要么读多了长度,原因是没有正确设置NBYTES寄存器,或在接收缓冲区长度上多算了一遍。能把这个坑讲出来,面试官会认为你是有过真实量产级调试经验的人。
5.2 握手与背压:I2C时钟延展、SPI无应答而UART全靠流控
面试官很喜欢对比三者的“后手”机制。I2C有ACK机制,从机接收完毕会拉低SCL,主机读数据时从机还能通过拉低SCL延展时钟来请求主机等待,这叫时钟延展;SPI没有应答机制,主机认为从机总能在规定时间内准备好,所以需要靠片选和延时来控制节奏;UART没有任何闭环反馈,只能靠流控线和协议层。
这个现象能引导出一个很有意思的类比:UART像对讲机,喊完了就等回复,没人告诉你听到没有;I2C像开会,每个人说话后都要举一下手确认;SPI像点对点的电话专线,约定好了说话节奏就不会出错。把这个类比用上,面试官会觉得你比其他人讲得更生动。尤其是“时钟延展”这个概念,很多工作了两年的人都不一定知道,你能主动提出来会有很大的差异化优势。
5.3 低功耗场景下的串口外设:休眠唤醒谁负责
嵌入式产品越注重功耗,面试也会特意问低功耗模式下外设的状态。典型问题是:进入休眠前要不要关闭UART/I2C/SPI外设时钟?要不要把引脚配成带上拉?唤醒后要不要重新初始化?这三种情况里,UART最简单,因为它是异步的,收发都有外部边沿可以唤醒;I2C在休眠时引脚必须保持上拉,否则总线被悬空拉低,唤醒时会先产生一个错误起始条件;SPI则依赖主机的CS唤醒,但SPI从机如果没在供电,MOSI和SCLK的漏电流可能通过引脚灌进去。
我在ESP32上实际遇到过I2C复位问题:休眠后重新唤醒,I2C外设虽然时钟恢复了,但内部状态机仍然停在中间状态,直接执行I2C读写会超时。解决办法是先把I2C外设完全复位,再重新初始化,必要时还要让总线先产生一个STOP条件。这类经验极其符合“面试官想听真实工程细节”的期待,你可以在面试中直接用“我当时在ESP32上遇到……”这种开头,立刻让人相信你不是纸上谈兵。
6. 一场模拟面试:从“你给我讲讲I2C”到“说说你怎么调试”的完整应答示范
6.1 一个60秒的I2C自述:结构化表达模板
很多人一听到“你给我讲讲I2C”就从头背协议。其实面试官想听到的是有重点、有层次的自述。你可以这样组织:首先一句话定义——I2C是Philips发明的两线制串行总线,通过SDA和SCL连接多个设备;第二层讲物理层特点——开漏加外部上拉,支持线与和时钟同步;第三层讲传输帧——START、地址、读写位、ACK、数据、STOP;第四层讲应用场景——速率较低但接线简单,适合传感器、EEPROM、OLED等。
我面试时特别欣赏能“用一分钟做结构化学术报告”的候选人,这反映了他的总结能力。如果时间富裕,可以再补充一句“I2C速率虽然低于SPI,但它的总线寻址能力使其在多设备场景优势明显”,然后无缝衔接到你做过的一个I2C项目。面试官立刻会觉得你有技术宽度又有落地经验。同样方法也适用于UART和SPI,你可以提前给自己写一个60秒版、120秒版的逐稿。
6.2 面试官爱问的实际异常:用逻辑分析仪和示波器看三大协议
面试官从项目切入时,往往喜欢问“你调试时遇到过什么有意思的问题”。这其实是在考察你的方法论。对于UART,最常见的问题是乱码。你可以回答:先用示波器看每个位的时间宽度,计算实际波特率偏差,再用逻辑分析仪解码出当前配置,如果解码正确那就是软件配置问题;如果解码乱码,就是采样点偏移。对于I2C,最典型的是总线卡死,SDA一直为低。我遇到过一次,原因是有个从机在应答阶段拉住了SDA不放,主机收到错误后没有发STOP,导致总线一直挂着。处理方法是给主机加一个总线超时复位逻辑,在等待ACK超时后主动发出9个时钟脉冲让从机释放总线。回答时加上“我还会用逻辑分析仪确认是哪个设备拉低的”会更显严谨。对于SPI,问题往往是CS拉下后从机没有回复数据,或数据全是0xFF,这时先检查时钟极性和相位是不是匹配,再检查MISO引脚是否配置成上拉输入。
6.3 面试前的自检清单:三个协议必须能手绘的东西
最后我给你列一个自检清单,面试前不要光背文字,一定要能够白板手绘下面几个图:第一,UART的一帧波形图,标出起始位、数据位、校验位、停止位;第二,I2C的时序图,标出START、地址、ACK、数据、STOP,并说明开漏结构;第三,SPI四种模式的时序图,标出CPOL、CPHA和采样沿。能亲手画出这三张图,遇到什么追问都能应对。再深一级,你还能画出I2C的时钟同步和仲裁波形,或者SPI用GPIO做软件片选时的CS时序(CS低电平提前量),你的水平已经超出一般面试者了。
我自己在面试别人时,其实很怕碰到那种把UART、I2C、SPI背得滚瓜烂熟但一句话都离不开手册的人。基础协议不是用来背的,是用来当工具去理解数据如何在引脚间流动的。如果你能带着“为什么这么设计”的疑问去学习,再亲自用逻辑分析仪抓几波波形,这三个协议会成为你嵌入式路上的好帮手。祝你面试顺利。