传感器接口协议:I2C、SPI、UART与单总线
做嵌入式这些年,打交道最多的就是传感器接口协议。说白了,传感器就是一堆模拟或数字信号,你得用对协议才能把它读出来。我见过不少新手,拿着I2C的器件当SPI去驱动,折腾一整天读回来的全是0xFF。嗯,今天咱们就把这四种最常见的协议掰开揉碎讲清楚。
2.1 I2C协议详解与调试
I2C,全称Inter-Integrated Circuit,飞利浦(现NXP)发明的。两根线:SCL(时钟)和SDA(数据)。所有设备挂在同一条总线上,靠地址区分。
2.1.1 协议基础
- 主从架构:主机发起通信,从机响应。一个主机可以带多个从机。
- 地址:7位或10位地址。7位最常见,比如0x76、0x3C。
- 速率:标准模式100kHz,快速模式400kHz,高速模式3.4MHz。
- 起始条件:SCL高电平时,SDA从高变低。
- 停止条件:SCL高电平时,SDA从低变高。
- 应答:每发送一个字节,接收方必须拉低SDA表示ACK。
2.1.2 调试技巧
调试I2C,示波器或逻辑分析仪是必备的。我个人的经验是:先看SCL有没有时钟,再看SDA有没有数据,最后看ACK。
// 典型的I2C读操作流程(伪代码)
i2c_start(); // 发送起始条件
i2c_send_byte(addr << 1); // 发送从机地址+写位
if (!i2c_wait_ack()) { // 等待ACK
// 从机无应答,报错
return ERROR;
}
i2c_send_byte(reg_addr); // 发送寄存器地址
i2c_wait_ack();
i2c_start(); // 重复起始条件
i2c_send_byte(addr << 1 | 1); // 发送从机地址+读位
i2c_wait_ack();
data = i2c_read_byte(); // 读取数据
i2c_send_nack(); // 发送NACK表示结束
i2c_stop(); // 发送停止条件
2.2 SPI协议详解与调试
SPI,Serial Peripheral Interface,摩托罗拉发明的。四根线:SCLK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)、CS(片选)。全双工,速度可以跑到几十MHz。
2.2.1 协议基础
| 参数 | 说明 |
|---|---|
| 时钟极性(CPOL) | 空闲时SCLK是高电平还是低电平 |
| 时钟相位(CPHA) | 数据在SCLK的上升沿还是下降沿采样 |
| 数据顺序 | MSB first 或 LSB first |
| 片选 | 低电平有效,选中从机 |
SPI有四种模式,由CPOL和CPHA组合决定。模式0(CPOL=0, CPHA=0)最常用。
2.2.2 调试技巧
调试SPI,我建议先看CS有没有拉低,再看时钟有没有产生,最后看数据线。很多传感器对时序要求严格,比如某些LCD驱动,时钟频率太高会花屏。
// SPI发送一个字节(模式0)
void spi_send_byte(uint8_t data) {
for (int i = 0; i < 8; i++) {
if (data & 0x80) {
MOSI_HIGH();
} else {
MOSI_LOW();
}
SCLK_HIGH();
data <<= 1;
SCLK_LOW();
}
}
2.3 UART协议详解与调试
UART,Universal Asynchronous Receiver/Transmitter。两根线:TX(发送)和RX(接收)。异步通信,没有时钟线,靠波特率同步。
2.3.1 协议基础
- 波特率:常见的有9600、115200、921600。收发双方必须一致。
- 数据格式:起始位(1位)+ 数据位(5~9位)+ 校验位(可选)+ 停止位(1~2位)。
- 电平:TTL电平(0~3.3V/5V)或RS232电平(±12V)。
2.3.2 调试技巧
UART调试,串口助手是标配。我个人的习惯是:先确认TX和RX有没有接反,再确认波特率对不对,最后看数据格式。
2.4 单总线协议
单总线,One-Wire,达拉斯半导体(现Maxim)发明的。一根线,既传数据又传电源。典型代表:DS18B20温度传感器。
2.4.1 协议基础
- 总线状态:空闲时高电平,低电平表示开始通信。
- 复位脉冲:主机拉低480μs,然后释放,从机拉低60~240μs表示存在。
- 写时序:写0拉低60μs,写1拉低1~15μs后释放。
- 读时序:主机拉低1~15μs后释放,从机在15μs内拉低表示0,保持高表示1。
2.4.2 调试技巧
单总线对时序要求极高,微秒级的误差都会导致通信失败。我建议用逻辑分析仪抓波形,看复位脉冲、存在脉冲、读写时序是否合规。
// 单总线读一位
uint8_t onewire_read_bit(void) {
uint8_t bit = 0;
ONE_WIRE_LOW();
delay_us(2); // 拉低2μs
ONE_WIRE_HIGH();
delay_us(5); // 等待5μs
if (ONE_WIRE_READ()) {
bit = 1;
}
delay_us(50); // 等待剩余时间
return bit;
}
2.5 协议对比与选型
| 协议 | 线数 | 速度 | 距离 | 多设备 | 典型应用 |
|---|---|---|---|---|---|
| I2C | 2 | ~3.4MHz | ~1m | 是(地址区分) | 温湿度、加速度计 |
| SPI | 4 | ~80MHz | ~1m | 是(片选区分) | LCD、SD卡、ADC |
| UART | 2 | ~1Mbps | ~10m | 否(点对点) | GPS、蓝牙模块 |
| 单总线 | 1 | ~16kbps | ~100m | 是(ROM地址) | DS18B20、iButton |
选型时,我一般这么考虑:如果传感器多、引脚紧张,选I2C;如果要求高速、大数据量,选SPI;如果只是简单通信、距离远,选UART;如果只有一根线可用,选单总线。你想想看,是不是这个道理?