【RK3568 平台I2C协议与AGS10驱动开发】
RK3568 平台I2C协议与AGS10驱动开发
- 一、I2C 总线协议基础
- 二、I2C 通信过程详解
- 三、AGS10 传感器概述
- 四、AGS10驱动开发
- 1. 硬件连接
- 2. 设备树(DTS)配置
- 3. 内核驱动开发
- 五、调试与验证
- 六、总结
引言
在嵌入式系统开发中,传感器数据采集是常见需求。本文将详细介绍如何在 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_master_recv(struct i2c_client *client, char *buf, int count);
功能
- 发送一个或多个 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); // 校准需要约30msreturn 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); // 校准需要约30msreturn 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); // 校准需要约30msreturn 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 子系统,我们实现了一个完整的驱动程序,包括设备初始化、数据读取和用户接口。