第一章 I2C/SPI通信协议:红外探测器常用接口

做红外探测器驱动开发,说白了就是跟芯片「对话」。你让它干活,它给你数据。这个对话靠什么?靠的就是通信协议。我这些年摸过的红外探测器,十有八九用的都是 I2C 或 SPI。今天咱们就把这两个协议掰开揉碎了讲清楚。

1.1 为什么是 I2C 和 SPI?

你可能会问:串口不行吗?当然行,但红外探测器对实时性和数据量有要求。I2C 省引脚,适合配置寄存器;SPI 速度快,适合传输图像数据。我见过不少新手一上来就纠结选哪个,其实很简单——看数据手册。探测器厂商早就帮你定好了。

核心原则:配置用 I2C,数据用 SPI。这是行业惯例,别瞎折腾。

1.2 I2C 时序与驱动实现

I2C 就两根线:SCL(时钟)和 SDA(数据)。别看它简单,坑可不少。我记得刚入行那会儿,调一个 I2C 驱动调了三天,最后发现是上拉电阻没焊。嗯,这种低级错误谁都会犯。

1.2.1 时序要点

  • 起始条件:SCL 高电平时,SDA 从高变低
  • 停止条件:SCL 高电平时,SDA 从低变高
  • 数据有效性:SCL 低电平时改变 SDA,高电平时采样
  • 应答机制:每发完一个字节,等待从机拉低 SDA

我个人习惯把起始和停止条件写成宏,这样代码看着清爽。你想想看,每次都要手动拉高拉低,多容易出错。

// I2C 起始条件
#define I2C_START()   \
  SDA_HIGH();         \
  delay_us(5);        \
  SCL_HIGH();         \
  delay_us(5);        \
  SDA_LOW();          \
  delay_us(5);        \
  SCL_LOW();

// I2C 停止条件
#define I2C_STOP()    \
  SDA_LOW();          \
  delay_us(5);        \
  SCL_HIGH();         \
  delay_us(5);        \
  SDA_HIGH();

我的经验:延时别写死。不同主频下延时不一样,最好用定时器或空循环计数。我曾经在 72MHz 的 STM32 上调好的代码,换到 48MHz 的芯片上直接废了。

1.2.2 驱动实现示例

写 I2C 驱动,核心就三个函数:读、写、写后读。红外探测器常用的就是写寄存器配置,然后读数据。

// 写一个字节到指定寄存器
uint8_t i2c_write_reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
  I2C_START();
  if (!i2c_send_byte(dev_addr << 1 | 0)) return 1;  // 写地址
  if (!i2c_send_byte(reg_addr)) return 1;             // 寄存器地址
  if (!i2c_send_byte(data)) return 1;                 // 数据
  I2C_STOP();
  return 0;
}

// 从指定寄存器读一个字节
uint8_t i2c_read_reg(uint8_t dev_addr, uint8_t reg_addr) {
  uint8_t data = 0;
  I2C_START();
  i2c_send_byte(dev_addr << 1 | 0);  // 写地址
  i2c_send_byte(reg_addr);             // 寄存器地址
  I2C_STOP();
  I2C_START();
  i2c_send_byte(dev_addr << 1 | 1);  // 读地址
  data = i2c_recv_byte(0);             // 不发送应答
  I2C_STOP();
  return data;
}

注意:读操作时,最后一个字节要发送 NACK(非应答),否则从机不会释放总线。我见过有人在这里卡了两天,逻辑分析仪一抓,发现总线一直被从机拉着。

1.3 SPI 时序与驱动实现

SPI 比 I2C 快,但线也多。四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。红外探测器用 SPI 传图像数据时,速度通常能到 10MHz 以上。

1.3.1 时序要点

  • CPOL(时钟极性):空闲时时钟是高还是低
  • CPHA(时钟相位):数据在第一个边沿还是第二个边沿采样
  • 四种模式:Mode 0~3,由 CPOL 和 CPHA 组合决定
  • 片选控制:传输前拉低 CS,传输后拉高 CS

我最烦的就是 SPI 模式搞混。有一次项目赶进度,我直接把数据手册上的时序图打印出来,拿尺子对着看。后来学乖了,写了个注释贴在代码头上。

// SPI 模式选择
// Mode 0: CPOL=0, CPHA=0 — 空闲低电平,第一个边沿采样
// Mode 1: CPOL=0, CPHA=1 — 空闲低电平,第二个边沿采样
// Mode 2: CPOL=1, CPHA=0 — 空闲高电平,第一个边沿采样
// Mode 3: CPOL=1, CPHA=1 — 空闲高电平,第二个边沿采样

// 红外探测器常用 Mode 0 或 Mode 3,具体看数据手册

1.3.2 驱动实现示例

SPI 驱动比 I2C 简单,因为它是全双工的。发一个字节的同时,也收一个字节。

// SPI 收发一个字节
uint8_t spi_transfer(uint8_t data) {
  uint8_t rx_data = 0;
  for (int i = 7; i >= 0; i--) {
    // 发送位
    if (data & (1 << i)) MOSI_HIGH();
    else MOSI_LOW();
    
    // 时钟上升沿
    SCLK_HIGH();
    delay_us(1);
    
    // 接收位
    if (MISO_READ()) rx_data |= (1 << i);
    
    // 时钟下降沿
    SCLK_LOW();
    delay_us(1);
  }
  return rx_data;
}

// 批量读取数据(用于图像传输)
void spi_read_bulk(uint8_t *buffer, uint32_t len) {
  CS_LOW();
  for (uint32_t i = 0; i < len; i++) {
    buffer[i] = spi_transfer(0x00);  // 发送空数据,读取从机数据
  }
  CS_HIGH();
}

我的习惯:批量读取时,CS 在整个传输过程中保持低电平。别每读一个字节就拉高一次,那样效率太低。我曾经优化过一个驱动,把 CS 切换次数从 1024 次降到 1 次,速度直接翻倍。

1.4 调试技巧:逻辑分析仪抓波形

写驱动最怕什么?怕代码看着没问题,但硬件就是不干活。这时候逻辑分析仪就是你的眼睛。我包里永远放着一个 8 通道的逻辑分析仪,几十块钱那种就够用。

1.4.1 抓什么?

  • I2C:看起始条件、停止条件、地址、数据、应答位
  • SPI:看时钟频率、数据对齐、片选时序
  • 常见问题:电平不对、时序错位、应答丢失

1.4.2 怎么抓?

  1. 把逻辑分析仪的通道接到 SCL/SDA(I2C)或 SCLK/MOSI/MISO/CS(SPI)
  2. 设置触发条件:I2C 触发起始条件,SPI 触发 CS 下降沿
  3. 运行代码,抓取波形
  4. 用软件解码,看数据对不对

避坑指南:我曾经遇到一个 SPI 通信问题,代码怎么看都没错,波形也正常,但数据就是不对。最后发现是 MISO 和 MOSI 接反了。所以,抓波形时一定要确认每个通道对应的是哪根线。

1.4.3 波形分析实例

假设你抓到一个 I2C 波形,发现从机没有应答。怎么办?

  • 先看地址对不对——7 位地址左移一位,加上读写位
  • 再看时序——SCL 频率是不是太快了?有些从机只能跑到 100kHz
  • 最后看电平——上拉电阻焊了吗?电压对了吗?

我一般会先查地址,因为这是最容易犯的错。数据手册上写的地址是 7 位,但代码里要左移一位。很多人忘了这茬。

1.5 本章知识体系

下面这张图是我自己画的,把 I2C 和 SPI 的核心要点串起来了。你保存下来,调试的时候对着看,能省不少时间。

红外探测器通信协议知识体系 通信协议 I2C 协议 SPI 协议 两根线:SCL + SDA 起始/停止条件 应答机制(ACK/NACK) 四根线:SCLK/MOSI/MISO/CS 四种模式(CPOL/CPHA) 全双工传输 调试技巧:逻辑分析仪抓波形 配置用 I2C,数据用 SPI — 行业惯例

好了,这一章的内容就到这儿。I2C 和 SPI 是红外探测器驱动开发的基础,你把这些吃透了,后面调驱动就会顺手很多。记住,遇到问题别硬扛,拿逻辑分析仪抓一下波形,问题往往一目了然。

专注资料整理