传感器接口协议:I2C、SPI、UART与单总线

做嵌入式这些年,打交道最多的就是传感器接口协议。说白了,传感器就是一堆模拟或数字信号,你得用对协议才能把它读出来。我见过不少新手,拿着I2C的器件当SPI去驱动,折腾一整天读回来的全是0xFF。嗯,今天咱们就把这四种最常见的协议掰开揉碎讲清楚。

核心观点:选对协议,调试就成功了一半。I2C适合多设备、低速;SPI适合高速、大数据量;UART简单但点对点;单总线省引脚但时序苛刻。

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。
我的习惯:写I2C驱动时,先确认从机地址。很多传感器有多个地址引脚,焊错了地址,怎么调都调不通。我踩过这个坑,后来每次画板子都再三核对原理图。

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();                 // 发送停止条件
避坑指南:我曾经调试一个气压传感器,读回来的数据总是跳变。查了半天,发现是上拉电阻没焊。I2C的SDA和SCL必须外接上拉电阻(通常4.7kΩ),否则信号不稳定。切记!

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();
    }
}
个人经验:SPI调试时,如果MISO一直没数据,先检查CS有没有正确拉低。我遇到过CS引脚被复用成GPIO输出高电平,从机根本没被选中,折腾了两小时才发现。

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有没有接反,再确认波特率对不对,最后看数据格式。

避坑指南:我曾经调试一个GPS模块,串口助手显示乱码。查了半天,发现是波特率设置成了9600,但模块默认是115200。还有一次,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;
}
我的建议:单总线调试,别用软件延时,用硬件定时器或DMA。软件延时受中断影响,时序不准。我吃过这个亏,后来改用定时器捕获,问题就解决了。

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;如果只有一根线可用,选单总线。你想想看,是不是这个道理?

传感器接口协议知识体系 传感器接口协议 I2C协议 SPI协议 UART协议 单总线协议 调试技巧 选型建议 多设备、低速 → I2C | 高速、大数据 → SPI | 简单通信 → UART | 省引脚 → 单总线 调试核心:示波器/逻辑分析仪 + 确认时序 + 检查电平
总结:四种协议各有优劣,没有万能的方案。我的经验是:先看传感器手册,确定它支持什么协议,再根据项目需求选。调试时,逻辑分析仪是你的好朋友,波形不会骗人。