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_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); // 校準需要約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 子系統,我們實現了一個完整的驅動程序,包括設備初始化、數據讀取和用戶接口。