引言
在嵌入式系统开发中,传感器数据采集是常见需求。本文将详细介绍如何在 RK3568 平台上开发 AGS10 空气质量传感器的 Linux 驱动,同时深入解析 I2C 总线协议的工作原理。通过本文,你将掌握 I2C 通信的核心概念,并学会如何为特定传感器开发 Linux 内核驱动。
一、I2C 总线协议基础
I2C(Inter-Integrated Circuit)是由飞利浦公司开发的一种串行通信协议,广泛应用于短距离、低速的设备间通信。它具有以下特点:
- 双线制:仅需两根信号线 SDA(Serial Data Line):数据传输线 SCL(Serial Clock Line):时钟线
- 主从架构: 主设备(Master):控制总线,发起通信 从设备(Slave):被动响应主设备请求
- 寻址机制: 每个从设备有唯一的 7 位或 10 位地址 地址在通信开始时由主设备发送
- 传输速率: 标准模式:100kHz 快速模式:400kHz 高速模式:3.4MHz
- 信号特征: 开漏输出,需外接上拉电阻 逻辑 0:低电平;逻辑 1:高阻态(由上拉电阻拉至高电平)
二、I2C 通信过程详解
I2C 通信的基本流程如下:
- 起始条件(Start): 主设备在 SCL 为高电平时,将 SDA 从高电平拉至低电平 标志一次通信的开始
- 地址帧: 主设备发送从设备地址(7 位或 10 位) 第 8 位为 R/W 位(0 表示写,1 表示读)
- 应答位(ACK/NACK): 每传输 8 位数据后,接收方需发送一个 ACK(低电平)或 NACK(高电平) 表示是否成功接收数据
- 数据传输: 根据 R/W 位决定数据方向 写操作:主设备→从设备 读操作:从设备→主设备
- 停止条件(Stop): 主设备在 SCL 为高电平时,将 SDA 从低电平拉至高电平 标志一次通信的结束
- 重复起始条件(Repeated Start): 在不发送 Stop 条件的情况下,再次发送 Start 条件 用于连续传输不同地址的数据
三、AGS10 传感器概述
AGS10 是奥松电子推出的一款高精度空气质量传感器,用于检测空气中的挥发性有机化合物(VOCs)。其主要特性包括:
传感器采用标准I2C通信协议,适应多种设备。I2C的物理接口包含串行数据信号(SDA)与串行
时钟信号(SCL)两个接口。两个接口需通过1kΩ~10kΩ电阻上拉至VDD。SDA用于读、写传感器数
据。SCL上电必须保持高电平直到进行I2C通信开始,否则会引起I2C通讯不良。当I2C通信时SCL用于主机与传感器之间的通讯同步。多个I2C设备可以共享总线,但是只能允许一个主机设备出现在总线上。传感器I2C器件地址为0x1A(7-bit),写指令为0x34,读指令为0x35。通讯速率不高于15kHz。
命令集合:
四、AGS10驱动开发
1. 硬件连接
AGS10 SCL ---> RK3568 I2C3_SCL_MO
AGS10 SDA ---> RK3568 I2C3_SDA_MO
AGS10 GND ---> RK3568 GND
AGS10 VCC ---> RK3568 VCC3V3_SYS
2. 设备树(DTS)配置
在 RK3568 的设备树文件kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi中添加 ags10节点:
&i2c3 {
clock-frequency = <15000>;
status = "okay";
ags10: ags10@1a {
compatible = "aosong,ags10";
reg = <0x1A>;
status = "okay";
};
};
i2c3定义如下:
kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi
i2c3引脚复用如下:
kernel/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi
3. 内核驱动开发
i2c函数介绍:
函数原型
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
功能
- 向指定 I2C 从设备发送数据,适用于简单的写操作(如配置寄存器)。
参数
client:指向目标 I2C 设备的客户端结构体指针buf:指向要发送的数据缓冲区count:要发送的字节数
返回值
- 成功:返回实际发送的字节数(通常等于
count) - 失败:返回负值错误码(如
-ENODEV、-EIO等)
函数原型
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
功能
- 从指定 I2C 从设备接收数据,适用于简单的读操作(如读取传感器数据)。
参数
client:指向目标 I2C 设备的客户端结构体指针buf:指向接收数据的缓冲区count:期望接收的字节数
返回值
- 成功:返回实际发送的字节数(通常等于
count) - 失败:返回负值错误码(如
-ENODEV、-EIO等)
函数原型
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
功能
- 发送一个或多个 I2C 消息(
struct i2c_msg数组),支持复杂的通信序列(如带重复 START 的复合操作)。
参数
adap:指向 I2C 适配器的指针msgs:指向struct i2c_msg数组的指针num:消息数组的长度(即消息数量)
返回值
- 成功:返回实际成功传输的消息数(等于
num) - 失败:返回负值错误码,或已成功传输的消息数(小于
num)
struct i2c_msg结构
struct i2c_msg {
__u16 addr; /* 从设备地址 */
__u16 flags; /* 标志位(如I2C_M_RD表示读操作) */
__u16 len; /* 消息长度 */
__u8 *buf; /* 数据缓冲区 */
};
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h> // 包含miscdevice相关定义
#include <linux/uaccess.h>
/* 寄存器命令 */
#define AGS10_CMD_READ_TVOC 0x00 // 读取TVOC值
#define AGS10_CMD_CALIBRATE 0x01 // 校准命令
#define AGS10_CMD_READ_VERSION 0x11 // 读取固件版本
#define AGS10_CMD_RESISTANCE 0x20 // 读取阻值
#define AGS10_IOC_MAGIC 'a'
/* 定义命令 */
#define AGS10_CMD_GET_TVOC _IOR(AGS10_IOC_MAGIC, 1, u16) // 读取TVOC值
#define AGS10_CMD_CALIBRATE _IO(AGS10_IOC_MAGIC, 2) // 触发校准
struct ags10_data {
struct i2c_client *client;
struct mutex lock;
struct miscdevice miscdev;
u8 firmware_version;
bool initialized;
};
static int ags10_i2c_write(struct i2c_client *client, u8 cmd, u8 *data, int len)
{
int ret;
u8 buf[len + 1];
memcpy(buf + 1, data, len);
/* 发送命令 */
buf[0] = cmd;
ret = i2c_master_send(client, buf, len + 1);
if (ret != 1) {
dev_err(&client->dev, "Failed to send command: %d\n", ret);
return ret;
}
return 0;
}
/* 发送命令并读取响应 */
static int ags10_send_command(struct i2c_client *client, u8 cmd, u8 *data, int len)
{
int ret;
u8 buf[16];
u8 addr = client->addr;
/* 发送命令 */
struct i2c_msg msgs[] = {
[0] = {
.addr = addr,
.flags = 0,
.len = sizeof(u8),
.buf = &addr,
},
[1] = {
.addr = addr,
.flags = 0,
.len = sizeof(u8),
.buf = &cmd,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
printk(KERN_INFO "ags10_send_command ret = %d, addr = %x, cmd = %x", ret, addr, cmd);
/*buf[0] = cmd;
ret = i2c_master_send(client, buf, 1);
if (ret != 1) {
dev_err(&client->dev, "Failed to send command: %d\n", ret);
return ret;
}*/
/* 等待传感器响应 */
msleep(10);
/* 读取响应 */
if (data && len > 0) {
ret = i2c_master_recv(client, data, len);
if (ret != len) {
dev_err(&client->dev, "Failed to read response: %d\n", ret);
return ret;
}
}
return 0;
}
/* 计算CRC校验 */
static u8 ags10_calculate_crc(u8 *data, int len)
{
u8 crc = 0xFF;
int i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x31;
} else {
crc <<= 1;
}
}
}
return crc;
}
/* 读取TVOC值 (单位: ppb) */
static int ags10_read_tvoc(struct ags10_data *data, u16 *tvoc)
{
int ret;
u8 buf[5]; // 5字节缓冲区: [STATUS][DATA_H][DATA_M][DATA_L][CRC]
u32 raw_value;
mutex_lock(&data->lock);
/* 发送读取TVOC命令并接收4字节数据 */
ret = ags10_send_command(data->client, AGS10_CMD_READ_TVOC, buf, 5);
if (ret) {
mutex_unlock(&data->lock);
return ret;
}
int i;
for(i = 0; i < 5; i++)
{
printk(KERN_INFO "ags10_read_tvoc[%d] = 0x%x", i, buf[i]);
}
/* 验证CRC (校验前3个数据字节) */
if (buf[4] != ags10_calculate_crc(&buf[0], 4)) {
dev_err(&data->client->dev, "TVOC CRC check failed: 0x%02X vs 0x%02X\n",
buf[4], ags10_calculate_crc(&buf[0], 4));
mutex_unlock(&data->lock);
return -EIO;
}
/* 计算TVOC值 (24位原始值转换为ppb) */
raw_value = ((u32)buf[1] << 16) | ((u32)buf[2] << 8) | buf[3];
printk(KERN_INFO "ags10_read_tvoc raw_value = 0x%x", raw_value);
*tvoc = (raw_value);
mutex_unlock(&data->lock);
return 0;
}
//零点恢复校准
static int ags10_reset_calibration(struct ags10_data *data)
{
int ret;
u8 buf[5] = {0x00, 0x0C, 0xFF, 0xFF, 0x81};
mutex_lock(&data->lock);
ret = ags10_i2c_write(data->client, AGS10_CMD_CALIBRATE, buf, 5);
if (ret) {
dev_err(&data->client->dev, "ags10_reset_calibration failed\n");
return ret;
}
mutex_unlock(&data->lock);
msleep(30); // 校准需要约30ms
return 0;
}
/*以当前阻值为零点校准*/
static int ags10_current_resistance_calibration(struct ags10_data *data)
{
int ret;
u8 buf[5] = {0x00, 0x0C, 0x00, 0x00, 0xAC};
mutex_lock(&data->lock);
ret = ags10_i2c_write(data->client, AGS10_CMD_CALIBRATE, buf, 5);
if (ret) {
dev_err(&data->client->dev, "ags10_current_resistance_calibration failed\n");
return ret;
}
mutex_unlock(&data->lock);
msleep(30); // 校准需要约30ms
return 0;
}
/*以raw为零点校准*/
static int ags10_calibration(struct ags10_data *data, uint16_t raw)
{
int ret;
u8 buf[5] = {0x00, 0x0C, (raw >> 8) & 0xFF, (raw >> 0) & 0xFF, };
buf[4] = ags10_calculate_crc(buf, 4);
mutex_lock(&data->lock);
ret = ags10_i2c_write(data->client, AGS10_CMD_CALIBRATE, buf, 5);
if (ret) {
dev_err(&data->client->dev, "ags10_calibration failed\n");
return ret;
}
mutex_unlock(&data->lock);
msleep(30); // 校准需要约30ms
return 0;
}
/* 读取固件版本 */
static int ags10_read_version(struct ags10_data *data)
{
int ret;
u8 buf[5]; //0-2:[Reserved][Version][CRC]
mutex_lock(&data->lock);
/* 发送读取版本命令 */
ret = ags10_send_command(data->client, AGS10_CMD_READ_VERSION, buf, 5);
if (ret) {
mutex_unlock(&data->lock);
return ret;
}
int i;
for(i = 0; i < 5; i++)
{
printk(KERN_INFO "ags10_read_version[%d] = 0x%x", i, buf[i]);
}
/* 验证CRC */
if (buf[4] != ags10_calculate_crc(&buf[0], 4)) {
dev_err(&data->client->dev, "CRC check failed\n");
mutex_unlock(&data->lock);
return -EIO;
}
data->firmware_version = buf[3];
dev_info(&data->client->dev, "Firmware version: %d\n", data->firmware_version);
mutex_unlock(&data->lock);
return 0;
}
/* 文件操作: 读取 */
static ssize_t ags10_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct ags10_data *data = filp->private_data;
u16 tvoc;
int ret;
size_t len;
if (count < 4)
return -EINVAL;
/* 读取TVOC值 */
ret = ags10_read_tvoc(data, &tvoc);
if (ret)
return ret;
/* 复制到用户空间 */
if (copy_to_user(buf, &tvoc, sizeof(u16)))
return -EFAULT;
return len;
}
/* 文件操作: ioctl */
static long ags10_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct ags10_data *data = filp->private_data;
int ret;
u16 tvoc;
switch (cmd) {
case AGS10_CMD_READ_TVOC:
/* 读取TVOC值 */
ret = ags10_read_tvoc(data, &tvoc);
if (ret)
return ret;
/* 复制到用户空间 */
if (copy_to_user((u16*)arg, &tvoc, sizeof(u16)))
return -EFAULT;
return 0;
case AGS10_CMD_CALIBRATE:
/* 发送校准命令 */
ret = ags10_send_command(data->client, AGS10_CMD_CALIBRATE, NULL, 0);
if (ret)
return ret;
return 0;
default:
return -ENOTTY;
}
}
/* 文件操作表 */
static const struct file_operations ags10_fops = {
.read = ags10_read,
.unlocked_ioctl = ags10_ioctl,
.llseek = no_llseek,
};
/* 探测函数 */
static int ags10_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct ags10_data *data;
int ret;
/* 检查设备是否支持 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C functionality not supported\n");
return -ENODEV;
}
dev_err(&client->dev, "ags10 I2C functionality supported\n");
/* 分配并初始化驱动数据结构 */
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(&client->dev, "Failed to allocate memory\n");
return -ENOMEM;
}
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
/* 初始化misc设备 */
data->miscdev.minor = MISC_DYNAMIC_MINOR;
data->miscdev.name = "ags10";
data->miscdev.fops = &ags10_fops;
data->miscdev.parent = &client->dev;
ret = misc_register(&data->miscdev);
if (ret) {
dev_err(&client->dev, "Failed to register misc device\n");
return ret;
}
/* 读取固件版本 */
ret = ags10_read_version(data);
if (ret) {
dev_err(&client->dev, "Failed to read firmware version\n");
misc_deregister(&data->miscdev);
return ret;
}
u16 tvoc;
ags10_read_tvoc(data, &tvoc);
printk(KERN_INFO "ags10_read_tvoc tvoc = %d", tvoc);
data->initialized = true;
dev_info(&client->dev, "AGS10 TVOC sensor initialized\n");
return 0;
}
/* 移除函数 */
static int ags10_remove(struct i2c_client *client)
{
printk(KERN_INFO "AGS10 TVOC sensor remove");
struct ags10_data *data = i2c_get_clientdata(client);
if (data->initialized) {
misc_deregister(&data->miscdev);
data->initialized = false;
}
return 0;
}
/* 设备ID表 */
static const struct i2c_device_id ags10_id_table[] = {
{ "ags10", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, ags10_id_table);
/* OF匹配表 */
static const struct of_device_id ags10_of_match[] = {
{ .compatible = "aosong,ags10" },
{},
};
MODULE_DEVICE_TABLE(of, ags10_of_match);
/* I2C驱动结构体 */
static struct i2c_driver ags10_driver = {
.driver = {
.name = "ags10",
.owner = THIS_MODULE,
.of_match_table = ags10_of_match,
},
.probe = ags10_probe,
.remove = ags10_remove,
.id_table = ags10_id_table,
};
module_i2c_driver(ags10_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("AGS10 TVOC Sensor Driver");
MODULE_VERSION("1.0");
五、调试与验证
将编译好的驱动文件拷贝到开发板进行测试:
也可以通过i2cdetect 、i2cget、i2cset等命令进行调试。
六、总结
本文详细介绍了 I2C 总线协议的工作原理,并展示了如何在 RK3568 平台上开发 AGS10 空气质量传感器的 Linux 驱动。通过深入理解 I2C 协议和 Linux 内核 I2C 子系统,我们实现了一个完整的驱动程序,包括设备初始化、数据读取和用户接口。