【RK3568 平台I2C协议与AGS10驱动开发】

97 阅读11分钟

引言

在嵌入式系统开发中,传感器数据采集是常见需求。本文将详细介绍如何在 RK3568 平台上开发 AGS10 空气质量传感器的 Linux 驱动,同时深入解析 I2C 总线协议的工作原理。通过本文,你将掌握 I2C 通信的核心概念,并学会如何为特定传感器开发 Linux 内核驱动。

一、I2C 总线协议基础

I2C(Inter-Integrated Circuit)是由飞利浦公司开发的一种串行通信协议,广泛应用于短距离、低速的设备间通信。它具有以下特点:

  1. 双线制:仅需两根信号线 SDA(Serial Data Line):数据传输线 SCL(Serial Clock Line):时钟线
  2. 主从架构: 主设备(Master):控制总线,发起通信 从设备(Slave):被动响应主设备请求
  3. 寻址机制: 每个从设备有唯一的 7 位或 10 位地址 地址在通信开始时由主设备发送
  4. 传输速率: 标准模式:100kHz 快速模式:400kHz 高速模式:3.4MHz
  5. 信号特征: 开漏输出,需外接上拉电阻 逻辑 0:低电平;逻辑 1:高阻态(由上拉电阻拉至高电平)

二、I2C 通信过程详解

I2C 通信的基本流程如下:

  1. 起始条件(Start): 主设备在 SCL 为高电平时,将 SDA 从高电平拉至低电平 标志一次通信的开始
  2. 地址帧: 主设备发送从设备地址(7 位或 10 位) 第 8 位为 R/W 位(0 表示写,1 表示读)
  3. 应答位(ACK/NACK): 每传输 8 位数据后,接收方需发送一个 ACK(低电平)或 NACK(高电平) 表示是否成功接收数据
  4. 数据传输: 根据 R/W 位决定数据方向 写操作:主设备→从设备 读操作:从设备→主设备
  5. 停止条件(Stop): 主设备在 SCL 为高电平时,将 SDA 从低电平拉至高电平 标志一次通信的结束
  6. 重复起始条件(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 子系统,我们实现了一个完整的驱动程序,包括设备初始化、数据读取和用户接口。