4、编写第一个I2C传感器驱动(以BMP280为例)
好,咱们今天来点真格的。
前面几章讲了PX4的驱动架构、调度器、uORB这些基础概念。说实话,光看理论容易犯困。我当年学PX4的时候,也是看了好几遍文档,感觉懂了,一上手写代码就懵了。
所以这一章,咱们直接拿一个真实的传感器——BMP280,手把手写一个I2C驱动。你跟着我走一遍,后面再写其他传感器,套路基本都一样。
4.1 硬件连接:先别急着写代码
写驱动之前,第一件事是什么?不是翻数据手册,是看硬件怎么接的。
我个人习惯,先确认三件事:
- 供电:BMP280支持1.71V~3.6V,大部分飞控板用3.3V。注意别接到5V上,会烧。
- I2C总线:SDA和SCL,确认挂在哪条总线上。比如STM32F4的I2C1还是I2C2。
- 地址:BMP280的I2C地址由SDO引脚决定。接地是0x76,接VDDIO是0x77。
硬件确认好了,咱们再看数据手册。
4.2 数据手册解读:只看关键部分
BMP280的数据手册有50多页,你不需要全看。我一般只看这几块:
| 章节 | 内容 | 重要性 |
|---|---|---|
| 1. 产品概述 | 功能框图、引脚定义 | 必看 |
| 2. 电气特性 | 供电电压、I2C时序 | 必看 |
| 3. 寄存器映射 | 所有寄存器的地址和含义 | 核心 |
| 4. 操作模式 | 正常模式、睡眠模式、强制模式 | 必看 |
| 5. 校准系数 | 用于补偿温度和压力 | 核心 |
说白了,你写驱动只需要知道三件事:
- 怎么初始化:写哪些寄存器,写什么值。
- 怎么读数据:读哪些寄存器,数据格式是什么。
- 怎么校准:用校准系数把原始数据转成物理值。
嗯,就这么多。别被数据手册吓到。
4.3 驱动框架选择:PX4里怎么写?
PX4的传感器驱动,官方推荐用I2CSPIDriver框架。这个框架封装了I2C和SPI的通用操作,你只需要实现几个关键函数:
probe():探测设备是否存在init():初始化传感器read():读取传感器数据
你想想看,这个框架把底层的I2C读写、调度、uORB发布都帮你搞定了。你只需要关注传感器本身的操作。是不是很省事?
4.4 实现probe函数:先确认设备在不在
probe()是驱动的第一步。它的任务很简单:往I2C总线上发一个读ID的命令,看看设备有没有回应。
BMP280有一个ID寄存器,地址是0xD0,读出来应该是0x58。
int BMP280::probe()
{
// 读取芯片ID
uint8_t id;
int ret = readRegister(0xD0, &id, 1);
if (ret != OK) {
PX4_ERR("BMP280 probe failed: no response");
return -EIO;
}
if (id != 0x58) {
PX4_ERR("BMP280 wrong ID: 0x%02X", id);
return -ENODEV;
}
PX4_INFO("BMP280 found, ID: 0x%02X", id);
return OK;
}
这里有个小细节:readRegister()是框架提供的封装函数,它会自动处理I2C的写地址+读数据的时序。你不需要自己拼I2C消息。
4.5 实现init函数:让传感器开始工作
probe通过后,就该初始化了。BMP280的初始化其实很简单:
- 读取校准系数(存储在OTP寄存器中)
- 设置工作模式(正常模式、采样率等)
校准系数有12个,每个都是16位有符号整数。它们存储在寄存器0x88到0xA1之间。我建议一次性读出来,存到结构体里。
int BMP280::init()
{
int ret;
// 1. 读取校准系数
struct bmp280_calib_s calib;
ret = readRegister(0x88, (uint8_t *)&calib, sizeof(calib));
if (ret != OK) {
PX4_ERR("read calibration failed");
return ret;
}
// 2. 设置配置寄存器
// 0x2C = 0b00101100
// bit7-5: t_sb=000 (0.5ms standby)
// bit4-2: filter=100 (16x filter)
// bit0: spi3w_en=0 (4-wire SPI)
ret = writeRegister(0xF5, 0x2C);
if (ret != OK) return ret;
// 3. 设置测量控制寄存器
// 0x3F = 0b00111111
// bit7-5: osrs_t=001 (1x oversampling)
// bit4-2: osrs_p=111 (16x oversampling)
// bit1-0: mode=11 (normal mode)
ret = writeRegister(0xF4, 0x3F);
if (ret != OK) return ret;
PX4_INFO("BMP280 initialized");
return OK;
}
你可能会问:为什么配置值是0x2C和0x3F?这些值是我根据数据手册的推荐配置算出来的。说白了,就是采样率、滤波、功耗之间的平衡。我一般选一个中等配置,够用就行。
4.6 实现read函数:拿到温度和压力
初始化完成后,传感器会持续测量。你只需要定时去读结果寄存器就行。
BMP280的压力和温度数据分别存储在:
- 压力:寄存器0xF7 (MSB), 0xF8 (LSB), 0xF9 (XLSB)
- 温度:寄存器0xFA (MSB), 0xFB (LSB), 0xFC (XLSB)
每个值都是20位有符号整数,左对齐。读取后需要右移4位得到真正的20位值。
int BMP280::read()
{
uint8_t data[6];
int ret = readRegister(0xF7, data, 6);
if (ret != OK) {
return ret;
}
// 解析原始数据
int32_t raw_press = ((int32_t)data[0] << 12) |
((int32_t)data[1] << 4) |
((int32_t)data[2] >> 4);
int32_t raw_temp = ((int32_t)data[3] << 12) |
((int32_t)data[4] << 4) |
((int32_t)data[5] >> 4);
// 用校准系数补偿
float temperature = compensate_temperature(raw_temp);
float pressure = compensate_pressure(raw_press);
// 发布到uORB
sensor_baro_s baro_report = {};
baro_report.timestamp = hrt_absolute_time();
baro_report.pressure = pressure;
baro_report.temperature = temperature;
_baro_pub.publish(baro_report);
return OK;
}
补偿函数的具体实现,数据手册里给了公式。说白了就是一堆乘法和加法,把原始值转成摄氏度和帕斯卡。代码我就不贴了,你照着数据手册抄就行。
4.7 完整驱动结构图
下面这张图,是我画的一个驱动整体流程。你看一眼,心里就有数了。
你看,整个流程就是一条直线。从硬件确认,到数据手册,再到三个核心函数。没有复杂的跳转,没有花哨的技巧。
4.8 总结一下
写一个I2C传感器驱动,说白了就是三步:
- probe:确认设备在不在
- init:告诉设备怎么工作
- read:定时拿数据,补偿,发出去
你把这个套路记住了,以后写任何I2C传感器,无非就是换一组寄存器地址、换一套补偿公式。框架和流程完全一样。
嗯,这一章就到这儿。代码你可以直接拿去用,但建议自己手敲一遍。只有亲手写过,才能真正理解。
公众号:蓝海资料掘金营,微信deep3321