3、数字接口基础:I2C、SPI、UART 协议详解
各位同学,大家好。今天我们来聊聊压力传感器里最常用的三种数字接口:I2C、SPI 和 UART。说实话,我当年刚入行时,总觉得这些协议就是些时序图,背下来就行。后来踩了不少坑才明白——理解协议背后的设计思想,比死记硬背重要得多。
这三种接口各有各的脾气。I2C 省引脚,适合板内短距离通信;SPI 速度快,适合大数据量传输;UART 最简单,但需要双方约定好参数。咱们一个一个来拆解。
3.1 I2C 接口协议详解
I2C 总线,说白了就是两根线干大事。一根时钟线 SCL,一根数据线 SDA。我最早接触 I2C 是在一个温湿度传感器项目上,当时觉得两根线就能挂一堆设备,真神奇。
3.1.1 地址机制
每个 I2C 设备都有一个唯一地址。标准模式是 7 位地址,所以理论上一条总线上最多挂 127 个设备。不过实际中,我建议你别超过 10 个——总线电容会变大,信号质量下降。
7位地址:0x00 ~ 0x7F(常用)
10位地址:0x000 ~ 0x3FF(扩展模式,需特殊时序)
我记得有一次调试一个压力传感器,怎么都读不到数据。折腾了半天,发现是地址搞错了——传感器默认地址是 0x76,我写成了 0x77。嗯,这种低级错误,谁还没犯过呢?
3.1.2 读写时序
I2C 的读写时序其实很优雅。主机发起 START 条件(SCL 高时 SDA 下降沿),然后发送 7 位地址 + 1 位读写位。从机收到后拉低 SDA 表示应答(ACK)。
// 伪代码:I2C 写一个字节到从机地址 0x76
START();
send_byte(0x76 << 1 | 0); // 地址 + 写位
wait_ACK();
send_byte(0x01); // 寄存器地址
wait_ACK();
send_byte(0xAB); // 要写的数据
wait_ACK();
STOP();
读操作稍微复杂一点。通常需要先写寄存器地址,然后重新发起 START 条件(这叫重复起始条件),再发地址+读位。为什么要这样?因为从机需要知道你要读哪个寄存器。
3.1.3 时钟拉伸
时钟拉伸是 I2C 里一个很有意思的特性。从机可以拉低 SCL 线,告诉主机「我还没准备好,你等等」。主机检测到 SCL 被拉低后,就会等待。
我个人习惯是:如果传感器支持时钟拉伸,尽量用软件 I2C 实现。虽然慢一点,但兼容性好。硬件 I2C 虽然快,遇到拉伸就容易出幺蛾子。
3.2 SPI 接口协议详解
SPI 和 I2C 完全是两种性格。I2C 像公交车,大家共用线路;SPI 像专车,主从一对一通信。SPI 需要 4 根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。
3.2.1 模式选择
SPI 有 4 种模式,由时钟极性 CPOL 和时钟相位 CPHA 决定。说白了就是:时钟空闲时是高还是低?数据在时钟上升沿还是下降沿采样?
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| 模式 0 | 0 | 0 | 空闲低,上升沿采样 |
| 模式 1 | 0 | 1 | 空闲低,下降沿采样 |
| 模式 2 | 1 | 0 | 空闲高,下降沿采样 |
| 模式 3 | 1 | 1 | 空闲高,上升沿采样 |
我最常用的是模式 0 和模式 3。为什么?因为大部分传感器默认就是这两种。你想想看,如果传感器手册没写清楚模式,你就得用逻辑分析仪去抓波形,自己判断。
3.2.2 片选管理
片选信号 CS 是 SPI 的灵魂。它低电平有效,意味着你要把 CS 拉低,然后开始通信,结束后再拉高。
这里有个坑:片选信号必须在整个数据帧传输过程中保持低电平。我见过有人每发一个字节就拉高一次 CS,结果传感器根本不认。为什么?因为很多传感器把 CS 的上升沿当作数据帧结束的标志。
3.2.3 数据帧格式
SPI 的数据帧格式很灵活。常见的是 8 位一帧,但也支持 16 位甚至 32 位。我个人习惯用 8 位,因为大多数 MCU 的 SPI 外设默认就是 8 位模式。
// SPI 读取压力传感器数据(模式0,8位)
CS_LOW();
spi_transfer(0xAA); // 发送读命令
uint8_t high = spi_transfer(0x00); // 读高字节
uint8_t low = spi_transfer(0x00); // 读低字节
CS_HIGH();
uint16_t value = (high << 8) | low;
注意看,SPI 是全双工的——你发数据的同时也在收数据。所以读操作时,主机需要发送 dummy 字节(通常是 0x00 或 0xFF)来产生时钟。
3.3 UART 接口协议详解
UART 是最古老的数字接口之一,但至今仍在广泛使用。它只需要两根线:TX(发送)和 RX(接收)。没有时钟线,所以通信双方必须事先约定好参数。
3.3.1 波特率
波特率就是每秒传输的符号数。常见的有 9600、19200、38400、115200 等。波特率越高,传输越快,但误码率也会上升。
我曾经在一个项目中,把波特率设成了 115200,结果在长距离传输时数据全是乱码。后来降到 9600,问题就解决了。所以我的建议是:能低就别高,除非你确认线路质量很好。
3.3.2 数据位
数据位可以是 5、6、7 或 8 位。现在基本都用 8 位,因为一个字节正好 8 位。7 位数据位主要用于传输 ASCII 字符,现在已经很少见了。
3.3.3 校验位
校验位用于简单的错误检测。有三种选择:无校验、奇校验、偶校验。
- 无校验: 最简单,没有校验位。适合短距离、低噪声环境。
- 奇校验: 数据位 + 校验位中 1 的个数为奇数。
- 偶校验: 数据位 + 校验位中 1 的个数为偶数。
起始位(1) + 数据位(8) + 校验位(0) + 停止位(1) = 10 位
所以波特率 9600 时,实际传输速率是 960 字节/秒
你可能会问:校验位到底有没有用?说实话,在短距离通信中,校验位的作用有限。但如果你要通过长线传输,或者环境干扰大,校验位还是能帮你发现一些错误的。
3.4 三种接口的对比与选择
好了,三种接口都讲完了。最后做个简单对比,方便你选型时参考。
| 特性 | I2C | SPI | UART |
|---|---|---|---|
| 引脚数 | 2(SCL+SDA) | 4(最少3) | 2(TX+RX) |
| 速度 | 100k~1M | 可达数十MHz | 通常 9600~115200 |
| 通信方式 | 半双工 | 全双工 | 全双工 |
| 多设备 | 支持(地址寻址) | 支持(片选) | 不支持(点对点) |
| 距离 | 短(<1m) | 短(<1m) | 中(可达数米) |
| 复杂度 | 中等 | 低 | 最低 |
我个人选型的习惯是:
- 如果传感器数量多、引脚紧张 → 选 I2C
- 如果要求高速、大数据量 → 选 SPI
- 如果只是简单传几个字节、距离稍远 → 选 UART
当然,实际项目中还要考虑 MCU 的外设资源、传感器的接口支持等因素。没有绝对的好坏,只有合不合适的方案。
公众号:蓝海资料掘金营,微信deep3321