4、I2C/SPI传感器驱动开发:I2C协议与Linux I2C子系统、温湿度传感器(SHT30/DHT22)驱动实现、气压传感器(BMP280)驱动实现、用户空间sysfs接口导出
好,咱们进入第四章。这一章我打算跟你聊聊传感器驱动开发。说实话,这是嵌入式Linux项目里最常碰到的活儿。你想想看,气象数据采集系统,核心就是靠传感器嘛。温湿度、气压,这些数据怎么从硬件读到应用层?嗯,这就是本章要解决的问题。
4.1 I2C协议基础与Linux I2C子系统
先说说I2C协议。I2C是个串行通信协议,两根线:SCL(时钟)和SDA(数据)。它属于主从架构,一个总线上可以挂多个设备。每个设备有唯一地址,7位或10位。我个人习惯用7位地址,简单够用。
I2C的通信流程其实不复杂:
- 主机发送起始条件
- 然后发送从机地址+读写位
- 从机应答后,开始传输数据
- 最后主机发送停止条件
我在项目中遇到过一个问题:总线上两个设备地址冲突了。当时排查了半天,最后发现是芯片手册上写的地址是7位,但我代码里用了8位。嗯,这里要注意,很多数据手册给的是7位地址,左移一位才是你在代码里要用的。
Linux下怎么操作I2C?Linux I2C子系统分三层:
- I2C核心层:提供通用接口和数据结构
- I2C总线驱动:处理硬件相关的适配器操作
- I2C设备驱动:针对具体传感器芯片的驱动
说白了,我们写传感器驱动,主要就是写设备驱动这一层。Linux内核里有个好东西叫i2c-dev,它允许你在用户空间直接操作I2C设备。调试阶段特别方便。
i2cdetect -y 总线号扫描一下,看看设备地址对不对。我曾经因为没做这步,浪费了半天时间。
4.2 温湿度传感器驱动实现:SHT30与DHT22
温湿度传感器,我选了两个典型代表:SHT30和DHT22。SHT30是数字I2C接口,精度高。DHT22是单总线协议,便宜但时序要求严格。
4.2.1 SHT30驱动实现
SHT30的I2C地址是0x44(7位)。它有个命令集,比如测量命令0x2C06。发送这个命令后,等待一段时间,读取6个字节数据:温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验。
代码实现大概这样:
/* SHT30驱动核心代码片段 */
#define SHT30_I2C_ADDR 0x44
#define SHT30_CMD_MEAS 0x2C06
static int sht30_read_data(struct i2c_client *client,
int *temp, int *humi)
{
u8 buf[6];
int ret;
/* 发送测量命令 */
u8 cmd[2] = {0x2C, 0x06};
ret = i2c_master_send(client, cmd, 2);
if (ret < 0) return ret;
msleep(20); /* 等待测量完成 */
/* 读取数据 */
ret = i2c_master_recv(client, buf, 6);
if (ret < 0) return ret;
/* 计算温度和湿度 */
*temp = ((buf[0] << 8) | buf[1]) * 175 / 65535 - 45;
*humi = ((buf[3] << 8) | buf[4]) * 100 / 65535;
return 0;
}
这里有个坑:CRC校验。SHT30每个数据后面跟一个CRC字节。我刚开始没做校验,数据偶尔跳变。后来加上CRC校验,问题就解决了。你想想看,工业环境里干扰多,不做校验风险很大。
4.2.2 DHT22驱动实现
DHT22用的是单总线协议,不是标准I2C。但很多开发板会把DHT22接在GPIO上模拟时序。它的数据格式是40位:16位湿度、16位温度、8位校验和。
时序要求很严格:主机拉低至少18ms,然后拉高20-40us。从机响应后,数据位用高低电平宽度表示0或1。
/* DHT22读取时序示例 */
static int dht22_read_data(int gpio, int *temp, int *humi)
{
/* 主机发起信号 */
gpio_direction_output(gpio, 0);
msleep(20); /* 拉低至少18ms */
gpio_direction_output(gpio, 1);
udelay(30); /* 拉高20-40us */
/* 等待从机响应 */
gpio_direction_input(gpio);
while(gpio_get_value(gpio) == 1); /* 等待从机拉低 */
while(gpio_get_value(gpio) == 0); /* 等待从机拉高 */
/* 读取40位数据 */
for(i = 0; i < 40; i++) {
while(gpio_get_value(gpio) == 0); /* 等待数据位开始 */
udelay(40);
if(gpio_get_value(gpio) == 1)
data |= (1 << (39 - i));
while(gpio_get_value(gpio) == 1); /* 等待数据位结束 */
}
/* 解析数据 */
*humi = (data >> 24) & 0xFFFF;
*temp = (data >> 8) & 0xFFFF;
/* 校验和验证 */
...
}
4.3 气压传感器BMP280驱动实现
BMP280是博世的气压传感器,I2C接口。它内部有校准参数,存储在寄存器0x88到0xA1。每次读取原始数据后,要用这些校准参数做补偿计算。
BMP280的I2C地址是0x76(SDO接地)或0x77(SDO接VDD)。
驱动实现要点:
- 初始化时读取校准参数
- 配置测量模式(比如正常模式、强制模式)
- 读取原始温度和压力数据
- 用校准参数计算实际值
/* BMP280校准参数结构体 */
struct bmp280_calib {
u16 dig_T1;
s16 dig_T2;
s16 dig_T3;
u16 dig_P1;
s16 dig_P2;
s16 dig_P3;
s16 dig_P4;
s16 dig_P5;
s16 dig_P6;
s16 dig_P7;
s16 dig_P8;
s16 dig_P9;
};
/* 温度补偿计算 */
static s32 bmp280_compensate_temp(s32 adc_temp,
struct bmp280_calib *calib)
{
s32 var1, var2, T;
var1 = ((((adc_temp >> 3) - ((s32)calib->dig_T1 << 1))) *
((s32)calib->dig_T2)) >> 11;
var2 = (((((adc_temp >> 4) - ((s32)calib->dig_T1)) *
((adc_temp >> 4) - ((s32)calib->dig_T1))) >> 12) *
((s32)calib->dig_T3)) >> 14;
T = (var1 + var2) >> 5;
return T; /* 单位:0.01摄氏度 */
}
说实话,BMP280的补偿计算有点绕。我第一次看数据手册时也懵了。后来我写了个测试程序,把原始数据和校准参数打印出来,对照手册一步步算,才搞明白。
4.4 用户空间sysfs接口导出
驱动写好了,怎么让应用层读到数据?Linux下最常用的方法就是sysfs。sysfs是个虚拟文件系统,挂载在/sys目录下。我们可以把传感器数据导出成文件,应用层直接cat就能读到。
实现方法:在驱动里创建attribute,然后注册到sysfs。
/* sysfs接口导出示例 */
static ssize_t temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
int temp, humi;
int ret;
ret = sht30_read_data(client, &temp, &humi);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", temp);
}
static DEVICE_ATTR(temp, S_IRUGO, temp_show, NULL);
/* 在probe函数中创建sysfs文件 */
static int sht30_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* ... 其他初始化 ... */
device_create_file(&client->dev, &dev_attr_temp);
device_create_file(&client->dev, &dev_attr_humi);
return 0;
}
这样,应用层就能这样读数据:
# cat /sys/bus/i2c/devices/0-0044/temp
2567 # 表示25.67摄氏度
还有个细节:权限设置。S_IRUGO表示所有用户可读。如果数据需要保密,可以设成S_IRUSR(仅所有者可读)。
嗯,这一章的内容就这些。从I2C协议到具体传感器驱动,再到sysfs接口导出,一条线串下来了。下一章我们会聊聊数据采集的调度策略和缓存管理。到时候见。