第一章 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 怎么抓?
- 把逻辑分析仪的通道接到 SCL/SDA(I2C)或 SCLK/MOSI/MISO/CS(SPI)
- 设置触发条件:I2C 触发起始条件,SPI 触发 CS 下降沿
- 运行代码,抓取波形
- 用软件解码,看数据对不对
避坑指南:我曾经遇到一个 SPI 通信问题,代码怎么看都没错,波形也正常,但数据就是不对。最后发现是 MISO 和 MOSI 接反了。所以,抓波形时一定要确认每个通道对应的是哪根线。
1.4.3 波形分析实例
假设你抓到一个 I2C 波形,发现从机没有应答。怎么办?
- 先看地址对不对——7 位地址左移一位,加上读写位
- 再看时序——SCL 频率是不是太快了?有些从机只能跑到 100kHz
- 最后看电平——上拉电阻焊了吗?电压对了吗?
我一般会先查地址,因为这是最容易犯的错。数据手册上写的地址是 7 位,但代码里要左移一位。很多人忘了这茬。
1.5 本章知识体系
下面这张图是我自己画的,把 I2C 和 SPI 的核心要点串起来了。你保存下来,调试的时候对着看,能省不少时间。
好了,这一章的内容就到这儿。I2C 和 SPI 是红外探测器驱动开发的基础,你把这些吃透了,后面调驱动就会顺手很多。记住,遇到问题别硬扛,拿逻辑分析仪抓一下波形,问题往往一目了然。