4. 传感器驱动开发:I2C读写函数封装、传感器初始化代码、像素数据读取与校验、帧同步机制
好,咱们进入第四章。这一章,说白了就是让传感器「开口说话」。
你想想看,热成像传感器再厉害,它也就是个黑盒子。你得通过 I2C 总线跟它握手,告诉它「兄弟,开始工作」,然后它把一帧一帧的温度数据吐给你。这个过程,就是驱动开发的核心。
我个人习惯把驱动拆成四块:I2C 读写封装、传感器初始化、像素数据读取与校验、帧同步机制。咱们一块一块啃。
4.1 I2C 读写函数封装
热成像传感器,比如 MLX90640、AMG8833,基本都是 I2C 接口。I2C 协议本身不复杂,但坑不少。
我建议你不要直接调用 HAL 库的 I2C 函数。为什么?因为项目后期要移植到不同 MCU,或者要加调试信息,你一个个改会疯掉。
正确的做法是:封装一层。
/* i2c_drv.h */
typedef struct {
int (*read)(uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint16_t len);
int (*write)(uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint16_t len);
} i2c_bus_t;
/* 具体实现(以 STM32 HAL 为例) */
static int i2c_read_hal(uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint16_t len) {
HAL_I2C_Mem_Read(&hi2c1, dev_addr << 1, reg_addr, I2C_MEMADD_SIZE_16BIT, data, len, 100);
return 0;
}
static int i2c_write_hal(uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint16_t len) {
HAL_I2C_Mem_Write(&hi2c1, dev_addr << 1, reg_addr, I2C_MEMADD_SIZE_16BIT, data, len, 100);
return 0;
}
i2c_bus_t i2c_bus = {
.read = i2c_read_hal,
.write = i2c_write_hal
};
4.2 传感器初始化代码
初始化,说白了就是往寄存器里写一堆配置值。但这里有个关键点:不同传感器的初始化流程完全不同。
以 MLX90640 为例,它的初始化步骤大致是:
- 读取芯片 ID,确认通信正常
- 设置刷新率(0.5Hz ~ 64Hz)
- 配置 ADC 分辨率
- 加载出厂校准参数(EEPROM)
- 启动连续采集模式
int mlx90640_init(i2c_bus_t *bus, uint8_t addr) {
uint16_t id;
int ret;
/* 1. 读取芯片 ID,验证通信 */
ret = bus->read(addr, 0x2400, (uint8_t *)&id, 2);
if (ret != 0 || id != 0x2400) {
return -1; /* 通信失败 */
}
/* 2. 设置刷新率为 8Hz */
uint16_t ctrl_reg = 0x0000;
ctrl_reg |= (0x03 << 4); /* 8Hz */
bus->write(addr, 0x800D, (uint8_t *)&ctrl_reg, 2);
/* 3. 配置 ADC 为 18 位模式 */
uint16_t adc_cfg = 0x0400;
bus->write(addr, 0x800E, (uint8_t *)&adc_cfg, 2);
/* 4. 加载 EEPROM 校准参数(略,后续章节详述) */
/* 5. 启动连续采集 */
ctrl_reg |= 0x0001; /* 使能采集 */
bus->write(addr, 0x800D, (uint8_t *)&ctrl_reg, 2);
return 0;
}
4.3 像素数据读取与校验
热成像传感器的像素数据,通常以 16 位有符号整数存储。比如 MLX90640 有 32x24 = 768 个像素,每个像素 2 字节,一帧就是 1536 字节。
读取方式有两种:
- 逐行读取: 每次读一行(32 像素),共读 24 次。适合内存小的 MCU。
- 整帧读取: 一次性读 1536 字节。速度快,但需要大缓冲区。
我个人推荐整帧读取,因为 I2C 每次启动都有开销,整帧读效率更高。
int mlx90640_read_frame(i2c_bus_t *bus, uint8_t addr, int16_t *frame_data) {
uint8_t raw[1664]; /* 1536 + 128 额外校验数据 */
int ret;
ret = bus->read(addr, 0x0400, raw, 1664);
if (ret != 0) {
return -1;
}
/* 数据校验:检查帧尾的 CRC */
uint16_t crc_calc = calc_crc16(raw, 1662);
uint16_t crc_recv = (raw[1662] << 8) | raw[1663];
if (crc_calc != crc_recv) {
return -2; /* CRC 校验失败 */
}
/* 提取像素数据(小端格式) */
for (int i = 0; i < 768; i++) {
frame_data[i] = (raw[i*2 + 1] << 8) | raw[i*2];
}
return 0;
}
4.4 帧同步机制
帧同步,说白了就是「什么时候读数据是完整的」。
热成像传感器有两种帧同步方式:
| 方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 查询状态寄存器 | 轮询传感器状态位,检测新帧就绪 | 实现简单 | 占用 CPU,可能丢帧 |
| 硬件中断(INT 引脚) | 传感器输出脉冲,触发 MCU 中断 | 实时性好,不占 CPU | 需要额外引脚 |
我建议量产项目用硬件中断。为什么?因为轮询方式在帧率较高时(比如 32Hz),CPU 大部分时间都在查状态,干不了别的。
/* 中断服务函数示例 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == THERMAL_INT_PIN) {
/* 设置标志位,主循环中处理 */
frame_ready = 1;
}
}
/* 主循环 */
while (1) {
if (frame_ready) {
frame_ready = 0;
mlx90640_read_frame(&i2c_bus, 0x33, frame_buffer);
process_frame(frame_buffer); /* 温度计算、显示等 */
}
}
4.5 本章知识体系
下面这张图,是我梳理的本章核心逻辑。你一看就明白:
这张图把整个流程串起来了。从 I2C 封装开始,到初始化、数据读取,最后用帧同步收尾。每一步都有坑,但只要你按这个框架走,基本不会出大问题。
好了,这一章就到这里。记住:驱动开发是硬件和软件的桥梁,写得好,后面所有上层应用都稳;写得烂,后面全是坑。
公众号:蓝海资料掘金营,微信deep3321