嵌入式工程师笔试和面试里,通讯协议几乎是必考项。不管是校招还是社招,面试官都会通过 UART、I2C、SPI、CAN、RS485、Modbus 这几个协议来考察你对硬件时序、数据帧结构、通信稳定性和项目排错能力的理解。
很多人能把协议名字背出来,但一问到“I2C 为什么需要上拉电阻”“CAN 的仲裁机制是怎么实现的”“Modbus RTU 的 CRC 校验怎么算”,就直接卡住。这篇把嵌入式面试里最高频的通讯协议考点整理成一套“八股手册”,每个协议都拆解成:基础原理、帧格式、面试高频问题、代码要点、项目选型建议。
内容定位是嵌入式软件工程师、嵌入式硬件工程师、单片机开发、Linux 驱动开发方向的面试准备。如果你正在准备蓝桥杯嵌入式、计算机三级嵌入式,或者日常做单片机项目需要一套通讯协议的知识清单,这篇文章可以直接对照着用。
1. 嵌入式通讯协议核心能力速览
在进入细节之前,先把嵌入式领域最常见的通讯协议做一张对比表。这张表也是面试中“协议选型”类问题的主要答题框架。
| 协议 | 通信方式 | 传输方向 | 速率范围 | 线数 | 传输距离 | 典型应用 | 面试频率 |
|---|---|---|---|---|---|---|---|
| UART | 异步串行 | 全双工 | 通常 9600 ~ 4Mbps | 2(TX/RX) | 短距离(TTL 电平小于 1m,RS232 约 15m) | 调试串口、GPS 模块、蓝牙模块 | 极高 |
| I2C | 同步串行 | 半双工 | 标准 100kbps,快速 400kbps,高速 3.4Mbps | 2(SCL/SDA) | 短距离,板级通信小于 1m | 传感器、EEPROM、RTC | 极高 |
| SPI | 同步串行 | 全双工 | 通常 10Mbps ~ 几十 Mbps | 4(SCK/MOSI/MISO/CS) | 短距离,板级通信 | Flash、SD 卡、显示屏、ADC | 极高 |
| CAN | 异步串行 | 半双工 | 通常 125kbps ~ 1Mbps | 2(CAN_H/CAN_L) | 最长可达 1km 以上(低速) | 汽车电子、工业控制 | 高 |
| RS485 | 异步串行 | 半双工 | 通常 9600bps ~ 12Mbps | 2(A/B) | 最长 1200m | 工业总线、Modbus | 高 |
| Modbus RTU | 基于串口的应用层协议 | 主从半双工 | 依托于 UART/RS485 速率 | 依托于物理层 | 依托于物理层 | PLC、工业网关、传感器采集 | 高 |
| USB | 异步串行 | 半双工/全双工 | 低速 1.5Mbps,全速 12Mbps,高速 480Mbps | 4(D+/D-/VBUS/GND) | 短距离 | 键鼠、U盘、通信接口 | 中 |
| 以太网 | 串行 | 全双工 | 10Mbps / 100Mbps / 1000Mbps | 4 对差分线 | 100m | 嵌入式 Linux、工业网关 | 中 |
从这张表能看出,面试最常考的其实是 UART、I2C、SPI、CAN 这四个基础协议,再加上 RS485 和 Modbus 这对工业组合。下面逐个拆解。
2. 通讯协议的基础概念:先分清这几组关键词
面试时,很多问题并不是直接问“UART 是什么”,而是从基础概念切入。第一层是串行和并行,第二层是同步和异步,第三层是单工、半双工、全双工。
串行通信是一位一位发送数据,只需要 1 根数据线(或一组差分线),优点是占用引脚少、抗干扰能力强、传输距离可以更远;并行通信一次发多个 bit,速率看起来高,但引脚多、线间干扰大、距离短,在现代嵌入式系统里主要用于片内总线或短距离高速存储接口。
同步通信和异步通信的区分在于“时钟信号是否单独传输”。同步协议(I2C、SPI)有单独的时钟线,发送端和接收端共用同一个时钟,数据按“时钟边沿采样”,所以双方不需要约定精确的波特率。异步协议(UART、CAN)没有单独的时钟线,接收端必须通过双方约定好的波特率从数据信号里恢复时钟,因此波特率误差、电平跳变质量对通信可靠性影响很大。
单工、半双工、全双工的区别,一句话就能讲清楚:
- 单工:只能单向传输,比如遥控器发给电视的红外信号。
- 半双工:双方都能发,但同一时刻只能一方发送,比如 I2C、RS485、CAN。
- 全双工:双方可以同时发送和接收,比如 UART 的 TX/RX 是独立两条线,SPI 的 MOSI/MISO 也是独立两条线。
这里有个容易搞混的点:串口调试助手显示“全双工”通常是指 UART,但 UART 在全双工的同时却是异步的。CAN 虽然也是异步,但它在电气上用了差分线,并且是半双工。所以“全双工”和“异步”是两个维度,不要混在一起答。
另外,电平标准也需要区分。单片机引脚直接输出的叫 TTL 电平,0V 表示逻辑 0,3.3V 或 5V 表示逻辑 1,只适合板内短距离通信。RS232 电平是负逻辑,-3V ~ -15V 表示逻辑 1,+3V ~ +15V 表示逻辑 0,抗干扰能力比 TTL 强,但距离也就 15 米左右。RS485 用差分电平,A/B 两线之间的电压差决定逻辑状态,抗共模干扰能力强,最远能到 1200 米。面试问“TTL、RS232、RS485 有什么区别”,就是从电平标准、传输距离、抗干扰能力三个维度回答。
3. UART 详解:最基础的异步串行通讯协议
UART 是 Universal Asynchronous Receiver/Transmitter 的缩写,通用异步收发器。它只需要 TX 和 RX 两根线,没有时钟线。两个设备通信时,A 的 TX 接 B 的 RX,A 的 RX 接 B 的 TX,还要共地。
UART 的帧格式是面试必考。一个完整的数据帧由 4 部分组成:
- 起始位:1 bit,低电平有效,标志一帧数据的开始。
- 数据位:通常 5 到 8 bit,常见的是 8 bit,低位先发。
- 校验位:可选,可以是奇校验、偶校验或无校验。
- 停止位:1 bit 或 2 bit,高电平,标志一帧结束。
当总线空闲时,TX 线保持高电平。发送方要发数据时,先拉低 TX 一个 bit 时间作为起始位,接收方从高到低的下降沿检测到起始位,然后按波特率在每一位的中间时刻采样数据位,最后确认停止位为高电平,完成一帧接收。
波特率是 UART 通信的第二个考点。波特率表示每秒传输的码元个数,常见配置是 9600、115200、460800。波特率误差直接影响采样点位置。如果发送端波特率是 115200,接收端实际是 115300,累计到第 10 位(8 数据位 + 起始位 + 停止位),采样点会偏移,导致乱码。通常 UART 波特率误差需要控制在 2% 以内,最好是 1% 以内。所以面试题“为什么串口会乱码”的排查思路就包括:波特率是否一致、电平是否适配、共地是否可靠、TX/RX 是否接反。
UART 编程有两个层次。低层次是轮询收发,直接读寄存器,适合数据量小、逻辑简单的场景;高层次是中断 + 环形队列,推荐在实际项目中使用。环形队列能避免高频数据打断主循环导致丢数据,也能配合 DMA 做到“收到一定长度自动进中断、其余时间 CPU 不参与搬运”。面试官如果问“UART 接收一长串不定长数据怎么做”,推荐回答是“空闲中断 + DMA + 环形缓冲区”,而不是死等接收完成中断。
下面是一段典型的 UART 初始化伪代码,实际寄存器地址需要按目标芯片手册调整:
// UART 初始化伪代码,寄存器名依据具体 MCU 数据手册调整 void uart_init(uint32_t baudrate) { uint32_t usart_clock = get_peripheral_clock(); // 获取外设时钟,具体函数因芯片而异 uint16_t mantissa; uint16_t fraction; // 使能 GPIO 时钟和 USART 时钟(具体寄存器名以芯片手册为准) enable_gpio_clock(GPIOA); enable_usart_clock(USART1); // 配置 TX/RX 引脚为复用功能 gpio_init(GPIOA, PIN9, MODE_AF); gpio_init(GPIOA, PIN10, MODE_AF); // 计算波特率寄存器值,常见公式:USARTDIV = clock / (16 * baudrate) uint32_t usartdiv = usart_clock / (16 * baudrate); // 设置数据格式:8 数据位、无校验、1 停止位 set_data_bits(USART1, 8); set_stop_bits(USART1, 1); set_parity(USART1, PARITY_NONE); // 使能发送、接收、接收中断 enable_tx(USART1); enable_rx(USART1); enable_rx_interrupt(USART1); // 使能 UART enable_usart(USART1); }UART 面试还有一个进阶问题:数据量大的时候怎么保证不丢?答题思路分成三层:
- 第一层,用环形缓冲区接收,主循环定期取数据,避免每来一个字节都阻塞处理。
- 第二层,用 DMA + 空闲中断接收一串不定长数据,CPU 只在一次传输完成后搬运数据。
- 第三层,如果数据量超过缓冲区,必须做流控。硬件流控用 RTS/CTS 引脚,软件流控用 XON/XOFF,或者应用层自己做分包、应答和重传。
4. I2C 详解:两线制同步通讯协议
I2C 是 Inter-Integrated Circuit 的缩写,由飞利浦公司提出,只需要 SCL(时钟线)和 SDA(数据线)两根线。I2C 的电气特性是开漏输出 + 上拉电阻,这是最核心的考点。
I2C 为什么用开漏?两个原因。第一,开漏输出可以让多个设备并联在同一根总线上,任何一个设备都可以把 SDA 或 SCL 拉低;如果设备是推挽输出,一个设备输出高电平、另一个设备输出低电平,会直接短路。第二,开漏输出配合上拉电阻可以灵活选择总线上拉电压,比如 3.3V 系统接 3.3V 上拉、5V 系统接 5V 上拉,实现电平转换。
上拉电阻的取值需要权衡。阻值太小,灌电流大,低电平可能拉不到标准要求的 0.4V 以下;阻值太大,上升沿变缓,高速通信时波形不达标。常规做法是:100kbps 时用 10kΩ 左右,400kbps 时用 4.7kΩ 或 2.2kΩ;具体还要看总线电容和挂载设备数量。
I2C 的时序包含以下几个关键要素:
- 起始条件:SCL 为高时,SDA 由高变低。
- 停止条件:SCL 为高时,SDA 由低变高。
- 数据有效性:SDA 上的数据必须在 SCL 高电平期间保持稳定,只有 SCL 为低电平时,SDA 才能切换。
- ACK/NACK:每传输 8 bit 数据后,接收方在第 9 个时钟周期拉低 SDA 表示 ACK,保持高表示 NACK。
7 位地址模式下,主机发送一个字节,高 7 位是从机地址,最低位是读写标志:0 表示写,1 表示读。从机收到地址后,地址匹配则回 ACK,不匹配则不应答,总线释放。
常见的 I2C 面试题包括:
- “I2C 可以挂多少个设备?”取决于地址位数和总线电容。7 位地址理论上最多 112 个可用地址(保留地址除外),但实际受总线电容限制,通常十几个设备就需要注意。
- “I2C 读一个寄存器的流程是什么?”第一步发送起始条件,第二步发送从机地址 + 写标志,第三步发送寄存器地址,第四步重新发送起始条件(重复起始),第五步发送从机地址 + 读标志,第六步读取数据,主机回 NACK,第七步发送停止条件。
- “设备不响应 ACK 怎么办?”先检查地址是否写错、设备供电是否正常、SDA/SCL 是否接反、上拉电阻是否存在,再检查总线上是否有设备把 SCL 拉死。
- “总线死锁怎么解决?”I2C 从机可能因为异常将 SDA 拉低。软件上可以 GPIO 模拟时序,在异常时将 SCL 连续翻转 9 次,让从机释放 SDA;硬件上可以在 SDA 串联小电阻,或在初始化前对总线做一次复位。
下面是 I2C 读寄存器数据的伪代码演示:
// I2C 读寄存器伪代码,前提是已经实现了底层发送和接收字节函数 uint8_t i2c_read_reg(uint8_t dev_addr, uint8_t reg_addr) { uint8_t data = 0; i2c_start(); // 1. 起始条件 i2c_send_byte((dev_addr << 1) | 0); // 2. 发送从机地址 + 写位 i2c_send_byte(reg_addr); // 3. 发送寄存器地址 i2c_start(); // 4. 重复起始条件 i2c_send_byte((dev_addr << 1) | 1); // 5. 发送从机地址 + 读位 data = i2c_recv_byte(); // 6. 读取数据 i2c_send_nack(); // 7. 主机回 NACK,表示不需要下一字节 i2c_stop(); // 8. 停止条件 return data; }如果面试官问“为什么读最后一个字节要回 NACK”,原因是一次读操作主机不知道从机还有多少数据要发。主机在收到自己需要的数据后,用 NACK 告诉从机“不要再发了”,然后发送停止条件。如果主机回 ACK,从机会继续发送下一字节。
5. SPI 详解:高速全双工同步通讯协议
SPI 是 Serial Peripheral Interface 的缩写,由摩托罗拉提出。它使用 4 根线:
- SCK:串行时钟,由主机产生。
- MOSI:主机输出、从机输入。
- MISO:主机输入、从机输出。
- CS/SS:片选信号,低电平有效。一个主机可以接多个从机,每个从机独立一根片选线。
SPI 是全双工通信,主机发送一个字节的同时会收到一个字节。这一点和 UART 不同:UART 的发送和接收是独立的两个流程,而 SPI 的收发在同一个时钟周期内完成。
SPI 有 4 种工作模式,由 CPOL(时钟极性)和 CPHA(时钟相位)决定。CPOL 决定空闲时 SCK 的电平,CPOL=0 表示空闲低电平,CPOL=1 表示空闲高电平;CPHA 决定数据采样沿,CPHA=0 表示在第一个边沿采样,CPHA=1 表示在第二个边沿采样。四种组合正好对应模式 0、模式 1、模式 2、模式 3。
调试 SPI 最常见的错误就是主从模式不匹配。主机配置成模式 0,从机如果只支持模式 3,读到的数据就会全部错位。排查方法是用逻辑分析仪抓 SCK 和 MOSI,对比数据采样点是否落在数据位的稳定区域。
SPI 的速率上限取决于从机芯片的数据手册。常见的 W25Q64 Flash 支持最高 104MHz 时钟,SD 卡在 SPI 模式下通常最高 25MHz,OLED 显示屏驱动芯片常见的是 10MHz 以内。如果通信不稳定,优先降低 SCK 频率,而不是去改代码逻辑。
SPI 面试题排名靠前的有:
- “SPI 和 I2C 怎么选?”SPI 速率高、全双工、协议简单,但引脚多、不支持多主机仲裁;I2C 引脚少、支持多设备挂载和多主机,但速率低、协议复杂。选型时看速率要求和从机数量。
- “一个 SPI 主机接多个从机,片选怎么处理?”每个从机一根 CS 线,主机在通信前拉低目标从机的 CS,通信完成后拉高。如果多个从机共用一个片选,那么从机必须支持菊花链拓扑,否则不能用。
- “为什么 SPI 没有应答机制?”SPI 协议本身没有 ACK,数据是否正确由应用层通过校验、读回寄存器等方式确认。这也是 SPI 不太适合远距离通信的原因之一。
下面是一段 SPI 收发数据的伪代码:
// SPI 收发一个字节,寄存器名依据具体 MCU 数据手册调整 uint8_t spi_transfer_byte(uint8_t tx_data) { // 等待发送缓冲区为空 while (!(spi_status & SPI_STATUS_TXE)); // 写入发送数据寄存器 spi_data_register = tx_data; // 等待接收缓冲区非空 while (!(spi_status & SPI_STATUS_RXNE)); // 读取接收数据寄存器 return spi_data_register; }使用 SPI 还要注意 MISO 和 MOSI 是否接反。很多硬件调试问题不是代码问题,而是排线插反了。诊断方法是:主机给从机发一个固定字节,比如 0xA5,然后读从机回读的数据;如果回读是 0x5A 或全 0xFF,大概率是接线问题或从机没有拉低 CS。
6. CAN 总线详解:汽车和工业领域的核心协议
CAN 是 Controller Area Network 的缩写,由博世提出,最早用于汽车内部 ECU 之间的通信。CAN 协议与前面几个协议差异很大:它是多主机总线,任何一个节点都可以主动发消息,不需要主机仲裁。
CAN 的物理层使用差分信号,两条线叫 CAN_H 和 CAN_L。显性电平对应逻辑 0,隐性电平对应逻辑 1。当总线空闲时,所有节点都输出隐性电平,CAN_H 和 CAN_L 电压接近;当某个节点要发送显性位时,它把 CAN_H 拉高、CAN_L 拉低,形成差分电压。总线两端各需要接一个 120Ω 终端电阻,用于匹配阻抗、消除反射。
CAN 总线的最重要的机制是“载波监听多路访问 + 非破坏性逐位仲裁”。多个节点同时发送数据时,每个节点在发送每一位的同时监听总线电平。如果发送的是隐性位但总线上读到显性位,说明有更高优先级的节点在发送,这个节点立即停止发送,转为接收状态。因为仲裁是按帧 ID 从高位到低位逐位比较的,所以帧 ID 越小,优先级越高。
CAN 的数据帧结构需要掌握。一帧数据由以下字段组成:
- 帧起始:1 bit 显性位。
- 仲裁场:标准帧 11 bit ID + RTR 位;扩展帧 29 bit ID + SRR + IDE + RTR。
- 控制场:IDE、保留位、DLC 数据长度代码,4 bit,表示数据场有几个字节。
- 数据场:0 到 8 字节。
- CRC 场:15 bit CRC + 1 bit 定界符。
- ACK 场:发送方发出隐性位,接收方在 ACK 槽拉低总线表示确认。
- 帧结束:7 bit 隐性位。
CAN 还有一个重要机制是位填充。在发送端,如果连续发送 5 个相同电平的位,自动插入一个反相位,接收端在检测到 5 个相同位后,自动删除下一个位。这样做的目的是保证总线有足够的电平跳变,便于各节点同步时钟。
面试问“CAN 为什么需要 120Ω 终端电阻”,可以从信号完整性角度回答:CAN 总线是差分传输,终端电阻用于匹配总线阻抗,防止高速信号在总线末端反射。反射会导致波形过冲或振铃,严重时位电平判断出错。如果网络拓扑是直线型,只需在物理最远的两个端点各接一个 120Ω 电阻。
CAN 波特率由位时间决定。一个位时间由同步段、传播时间段、相位缓冲段 1、相位缓冲段 2 组成。采样点在相位缓冲段 1 和相位缓冲段 2 之间,推荐设置在 75% 到 85% 左右。
CAN 面试还有一个高频问题:“CAN 总线的错误处理机制”。每个节点维护发送错误计数器和接收错误计数器。错误计数大于 127 进入错误被动状态,大于 255 进入总线关闭状态。错误被动节点发送时会影响总线,但不会主动破坏总线;总线关闭节点完全脱离总线。设计上,应用层要能读取节点状态,及时上报总线异常,并做恢复策略。
CAN 编程时,不用像 UART 那样手动拼帧。常见 MCU 内置 CAN 控制器,只需要配置波特率、过滤器、收发邮箱。开发流程通常是:
// CAN 发送报文伪代码 can_tx_message msg; msg.id = 0x123; // 11 位标准帧 ID msg.dlc = 8; // 数据长度 msg.data[0] = 0x01; msg.data[1] = 0x02; // 填充 data[2] ~ data[7] can_send_message(&msg);实际项目里,CAN 过滤器配置非常关键。如果没有过滤,MCU 会收到总线上所有帧,CPU 负担大;正确做法是只接收本节点关心的 ID,过滤掉不相关报文。
7. RS485 与 Modbus:工业现场最常见的主从通讯组合
RS485 是电气层标准,Modbus 是应用层协议。两者经常组合使用:RS485 负责物理传输,Modbus 负责数据组织和命令定义。
RS485 使用差分信号,A/B 两根线。逻辑 1 用两线之间的正电压差表示,逻辑 0 用负电压差表示。这种差分传输方式让 RS485 具备很强的共模干扰抑制能力,传输距离最远可达 1200 米,特别适合工业现场。RS485 是半双工通信,同一时刻只能有一个节点发送,所以必须做收发方向控制。MCU 通常通过一个 DE/RE 引脚控制收发芯片的方向,发送前拉高 DE,发送完成后拉低 DE 回到接收状态。
RS485 总线也要接终端电阻。和 CAN 一样,在总线两端各接一个 120Ω 电阻。如果总线上节点数量很多、通信距离短,可以在主站端加一个偏置电阻网络,保证总线空闲时电平稳定在确定状态,避免出现“接收到的全是乱码”的问题。
Modbus 是一套应用层协议,最常用的是 Modbus RTU 和 Modbus ASCII。Modbus RTU 每个字节以二进制方式传输,数据帧紧凑,效率高,是工业现场的主流。Modbus RTU 帧格式如下:
- 从机地址:1 字节,范围 1 到 247,0 为广播地址。
- 功能码:1 字节,常见的有 0x01 读线圈、0x03 读保持寄存器、0x04 读输入寄存器、0x05 写单个线圈、0x06 写单个寄存器、0x0F 写多个线圈、0x10 写多个寄存器。
- 数据区:长度可变,包含寄存器地址、寄存器数量、数据值等。
- CRC 校验:2 字节,低字节在前、高字节在后。
Modbus 主从机制是一问一答。主机发送请求帧,从机收到后执行操作并返回响应帧;如果从机收到非法功能码、非法寄存器地址或非法数据,则返回异常响应帧,功能码最高位置 1,并附带异常码。
CRC16 的计算是嵌入式面试手写代码的常考题。Modbus 的 CRC16 多项式是 0xA001(即 0x8005 的反转),初始值是 0xFFFF。计算时,每个字节先和 CRC 低字节异或,再右移 8 次,每次检测最低位,如果为 1 就和 0xA001 异或。
// Modbus RTU CRC16 计算函数 uint16_t modbus_crc16(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; uint8_t j; for (i = 0; i < len; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; }面试官经常把这道题延伸成两个问题:
第一个,“为什么 Modbus 要用 CRC 而不是求和校验?”因为 CRC 校验能检测出多位错误、突发错误,在噪声严重的工业现场更可靠;普通累加和只能检测有限错误,误判率较高。
第二个,“主机轮询 10 个从机,每个从机响应时间 20ms,一轮需要多长时间?”轮询周期等于从机数量乘以单次通信时间和从机响应时间。10 个从机,每个从机 20ms 响应,加上主机发送请求帧的时间,一轮约 250ms 左右。如果超过这个量级,就要考虑通信超时时间设置是否合理。
Modbus 调试时的常见坑是地址不对、功能码不对、字节序不对。尤其是字节序,Modbus 寄存器数据默认高位在前(大端),但有些设备手册把低字节在前,解析时要对照协议文档确认。还有一个坑是从机地址为 0 的广播帧,从机不应答;如果调试时设备完全没响应,检查是不是把地址配置成了 0。
8. USB 与以太网:嵌入式面试补充考点
UART、I2C、SPI、CAN 属于嵌入式面试的第一梯队,USB 和以太网属于第二梯队,通常只在特定岗位出现,但基础概念要掌握。
USB 的核心考点是枚举过程。设备接入主机后,主机会经历以下过程:设备连接检测、端口供电、设备复位、设备描述符请求、设置地址、配置描述符请求、选择配置。USB 设备描述符里包含 VID、PID、设备类别等信息。嵌入式开发里常见的 USB 设备类有:
- HID 类:键鼠、自定义 HID 设备,免驱动。
- CDC 类:虚拟串口,常用于调试和维护。
- MSC 类:U 盘、读卡器。
- Audio 类:声卡、麦克风。
如果面试题是“USB 和 UART 的区别”,回答思路是:USB 是主从结构,有枚举、端点和传输类型的概念;UART 是简单的点对点异步串行。USB 支持热插拔、供电、高带宽,但协议栈复杂;UART 简单直接,但速率低、无标准流控。
以太网在嵌入式里的考察重点是协议栈分层。嵌入式常用的 LwIP 是轻量级 TCP/IP 协议栈,驱动层通过 MAC 控制器和 PHY 芯片连接物理网络。MCU 内置 MAC,外接 PHY 芯片,比如 LAN8720、DP83848,走 RMII 接口和 MDIO 管理接口。面试常问“MAC 和 PHY 的区别”,MAC 负责数据链路层的帧封装、寻址、CRC 校验,PHY 负责物理层的编码、解码、信号传输。
嵌入式 Linux 开发中还会接触 Socket 编程。面试一般会考 TCP 和 UDP 的区别、TCP 三次握手和四次挥手、listen/accept 和 connect/bind 的概念。如果是驱动岗位,还会深入 Linux 网络驱动模型、NAPI、DMA 环形缓冲区等。但这个方向的深度太广,放到单独的嵌入式八股文章里再展开。
9. 通讯协议面试高频问题与答题思路
下面把通讯协议面试中最常见的十几个问题整理成一张答题速查表。这张表可以作为面试前的最后突击清单。
| 高频问题 | 核心答题要点 | 加分深挖方向 |
|---|---|---|
| UART 为什么会出现乱码 | 波特率不一致、TX/RX 接反、共地不可靠、电平不匹配 | 用示波器或逻辑分析仪抓波形,检查起始位和数据位电平 |
| I2C 为什么要上拉电阻 | 开漏输出、多设备并联、电平匹配 | 上拉电阻阻值选取与总线电容的关系 |
| I2C 数据有效性怎么保证 | 数据在 SCL 高电平期间必须稳定 | 结合时序图说明建立时间和保持时间 |
| SPI 有哪几种模式 | CPOL/CPHA 组合决定 0~3 模式 | 模式不匹配时如何通过逻辑分析仪判断 |
| SPI 和 I2C 怎么选型 | 速率、引脚数量、从机数量、多主机需求 | 结合项目场景举例,比如读 Flash 用 SPI、读温湿度传感器用 I2C |
| CAN 如何避免多个节点冲突 | 非破坏性逐位仲裁,ID 越小优先级越高 | 手动画出两个节点位仲裁的过程 |
| CAN 为什么要终端电阻 | 阻抗匹配、防止反射 | 120Ω 电阻如何分布 |
| RS485 为什么能传 1200m | 差分信号、抗共模干扰 | 和 TTL、RS232 的电压标准对比 |
| Modbus RTU 一帧包含哪些内容 | 地址、功能码、数据、CRC | 手写 CRC16 计算函数 |
| Modbus 主机轮询周期怎么算 | 从机数量 ×(请求时间 + 响应时间 + 超时时间) | 根据实际项目估算常见周期 |
| USB 枚举过程是怎样的 | 复位、设备描述符、设置地址、配置描述符 | 用抓包工具看枚举过程 |
答题时要尽量结合项目。面试官问“SPI 和 I2C 怎么选”,如果只背概念,得分一般;如果能说“我之前做 OLED 显示,用的是 SPI,因为显示刷新需要高速率;做环境监测传感器,用的是 I2C,因为传感器数据量小、引脚少”,立刻会让回答更有说服力。
10. 通讯协议项目选型与调试方法
实际项目中,协议选型需要考虑五个维度:速率、距离、节点数量、实时性、开发成本。
如果数据量小、设备数量多,优先选 I2C,比如主板上的传感器、EEPROM、RTC。如果数据量大、需要高速传输,选 SPI,比如 Flash、SD 卡、LCD。如果是设备间短距离通信、点数少,选 UART + TTL 或 UART + RS232。如果是工业现场长距离多节点,选 RS485 + Modbus。如果是车载设备或高实时性控制总线,选 CAN。如果是传输文件或大数据包,方向就换成 USB 或以太网。
选型没有绝对的对错,关键是给出理由。面试官更看重的是你能不能针对项目需求解释选择依据。
调试通讯协议时,必备三个工具:
- 逻辑分析仪:抓 UART、I2C、SPI 的时序,看起始位、ACK、片选、数据比特是否正确。逻辑分析仪是嵌入式调试中最容易上手的工具,推荐 8 通道以上。
- 示波器:测波特率误差、上升沿、下降沿、CAN/RS485 差分信号质量。
- 串口调试助手 / 总线分析工具:直接验证协议收发内容。
一组通用的排查步骤:
第一步,先做回环测试。UART 把 TX 和 RX 短接,自发自收;SPI 把 MOSI 和 MISO 短接;RS485 把 A 和 B 短接后看收发是否正常。回环测试能排除代码逻辑问题。
第二步,用逻辑分析仪或示波器检查波形。UART 看起始位下降沿和数据位电平;I2C 看起始/停止条件和 ACK;SPI 看片选拉低时序和数据采样点;CAN/RS485 用示波器看差分电压是否在正常范围。
第三步,检查物理连接。TX 和 RX 是否接反、MOSI 和 MISO 是否接反、RS485 的 A/B 是否接反、CAN 的 CAN_H 和 CAN_L 是否接反、共地是否可靠。
第四步,检查配置参数。波特率、数据位、校验位、停止位是否一致;I2C 地址是否真的对应目标设备;SPI 的 CPOL/CPHA 是否匹配;CAN 波特率和采样点配置是否符合网络要求;Modbus 从机地址和寄存器地址是否和手册一致。
第五步,看错误计数。CAN 的错误计数器是否在增长;UART 的溢出错误标志位是否置位;I2C 的 BUSY 标志是否异常;DMA 传输是否真的完成。错误标志能直接指向问题方向。
11. 通讯协议编码最佳实践与代码规范
通讯协议代码写得好不好,笔试和项目中都能体现出来。这里给出几项可以提高代码质量的实践建议。
第一,接收数据必须用环形队列。轮询接收会卡住主循环,中断接收直接处理会浪费时间,正确做法是把收到的字节放入环形队列,解析逻辑放在主循环或任务中。环形队列的主流实现使用头尾指针,当队满时可以选择丢弃或覆盖,推荐丢弃并记录溢出计数。
第二,协议解析用状态机,不要在中断里解析一整个帧。比如一串不定长 UART 数据,中断里只做“按字节入队”,主循环里用状态机识别帧头、帧尾、长度、校验,这样可以保证中断处理时间极短,不影响其他实时任务。
第三,所有通讯函数必须处理超时和错误。初始化要检查返回值;发送要检查发送缓冲是否为空;接收要检查超时;DMA 要检查传输完成标志。不要假设硬件一定正常工作。
第四,通信协议中的固定值建议用宏定义或枚举,不要到处写魔法数字。比如:
#define FRAME_HEADER 0xA5 #define FRAME_CMD_READ 0x01 #define FRAME_CMD_WRITE 0x02 #define FRAME_MAX_LEN 256 #define FRAME_CRC_LEN 2第五,模块化分层。物理层代码管寄存器操作,驱动层管协议时序,应用层管业务逻辑。比如 UART 驱动只提供uart_send_byte和uart_receive_byte,Modbus 协议层调用这两个函数,业务层再调用 Modbus 协议层。这样换 MCU 时只需要改最底层驱动。
第六,通信模块要加调试信息。用宏控制调试打印,发布版本关闭调试输出,开发阶段打开。调试输出要包含时间戳、收发数据、错误码,方便定位问题。
第七,写单元测试或自检程序。比如上电自检时,让板子进入回环测试模式,自发自收一组数据,校验通过后点亮指示灯;Modbus 从机开发时,可以用 Modbus Poll 工具模拟主机,验证从机的每一个功能码。
12. 通讯协议学习路线与后续扩展
如果你正在准备嵌入式面试,通讯协议这部分建议按下面的路线复习:
先掌握基础概念,把串行/并行、同步/异步、半双工/全双工、电平标准的区别背熟。再把 UART、I2C、SPI 的帧格式、时序、电气特性做横向对比,能默写出时序图。CAN 和 RS485/Modbus 是加分项,掌握原理后最好在开发板上实际跑一遍。最后把面试题整理成文档,结合自己做过的项目,准备两个到三个完整的通信排错案例。
如果你的目标是 Linux 驱动方向,还需要额外看 Linux 内核里的 UART 驱动框架、I2C 子系统、SPI 子系统、CAN 驱动模型。嵌入式 Linux 面试中,通讯协议经常从“裸机寄存器操作”升级到“驱动框架和设备树”。这部分内容适合在掌握了本章基础之后再深入。
通讯协议是嵌入式工程师的基本功,也是面试必考模块。把协议的物理层、数据帧、软件实现、调试方法串起来,才是一套完整的知识体系。写代码时,以状态机和环形队列为核心;排错时,优先用回环测试和逻辑分析仪定位问题。这套方法在笔试、面试和实际项目中都能直接用上。