当前位置: 首页 > news >正文

【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)是由飞利浦公司开发的一种串行通信协议,广泛应用于短距离、低速的设备间通信。它具有以下特点:

  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_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 子系统,我们实现了一个完整的驱动程序,包括设备初始化、数据读取和用户接口。

http://www.dtcms.com/a/277244.html

相关文章:

  • 深度学习16(对抗生成网络:GAN+自动编码器)
  • Vue单文件组件与脚手架工程化开发
  • 【数据结构】图 ,拓扑排序 未完
  • 弹性布局详解
  • mmap映射文件
  • 【设计模式】命令模式 (动作(Action)模式或事务(Transaction)模式)宏命令
  • 【STM32实践篇】:F407 时钟系统
  • fiddler/charles https配置完毕依然无法抓取APP https请求的解决办法
  • h() 函数
  • 【RA-Eco-RA6E2-64PIN-V1.0 开发板】ADC 电压的 LabVIEW 数据采集
  • Excel的学习
  • 如何选择合适的AI论文写作工具?七个AI英文论文写作网站
  • leetGPU解题笔记(2)
  • Agent浏览器自动化工具技术原理探析- Palywright VS OS-Atlas
  • 009_API参考与接口规范
  • Android 代码热度统计(概述)
  • Ampace厦门新能安科技Verify 测评演绎数字推理及四色测评考点分析、SHL真题题库
  • 代码随想录算法训练营第三十二天|动态规划理论基础、LeetCode 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
  • 嵌入式单片机开发 - HAL 库引入(HAL 库概述、HAL 库下载)
  • 使用macvlan实现容器的跨主机通信
  • JSON/AJAX/XHR/FetchAPI知识点学习整理
  • Feign实战
  • 六、深度学习——NLP
  • 01_类的概念和定义
  • websocket连接时发生未知错误
  • sqli-labs靶场通关笔记:第9关 时间盲注
  • 快速生成 Android 的 Splash 的 9 Patch 图片
  • 【零基础入门unity游戏开发——unity3D篇】3D光源之——unity反射和反射探针技术
  • AI进化论12:大语言模型的爆发——GPT系列“出圈”,AI飞入寻常百姓家
  • Kafka——Kafka 线上集群部署方案怎么做?