在使用者空間實現 I2C 裝置驅動程式

通常,I2C 裝置由核心驅動程式控制。但是,也可以透過 /dev 介面從使用者空間訪問介面卡上的所有裝置。為此,您需要載入模組 i2c-dev。

每個註冊的 I2C 介面卡都有一個編號,從 0 開始計數。您可以檢查 /sys/class/i2c-dev/ 以檢視哪個編號對應於哪個介面卡。或者,您可以執行 “i2cdetect -l” 來獲取在給定時間您的系統上所有 I2C 介面卡的格式化列表。 i2cdetect 是 i2c-tools 包的一部分。

I2C 裝置檔案是字元裝置檔案,主裝置號為 89,次裝置號對應於如上所述分配的編號。它們應被稱為 “i2c-%d”(i2c-0、i2c-1、...、i2c-10、...)。所有 256 個次裝置號都為 I2C 保留。

C 示例

假設您想從 C 程式訪問 I2C 介面卡。首先,您需要包含這兩個標頭檔案

#include <linux/i2c-dev.h>
#include <i2c/smbus.h>

現在,您必須決定要訪問哪個介面卡。您應該檢查 /sys/class/i2c-dev/ 或執行 “i2cdetect -l” 來決定。介面卡編號的分配有些動態,因此您不能對它們做出太多假設。它們甚至可能在兩次啟動之間發生變化。

接下來,開啟裝置檔案,如下所示

int file;
int adapter_nr = 2; /* probably dynamically determined */
char filename[20];

snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR);
if (file < 0) {
  /* ERROR HANDLING; you can check errno to see what went wrong */
  exit(1);
}

開啟裝置後,必須指定要與之通訊的裝置地址

int addr = 0x40; /* The I2C address */

if (ioctl(file, I2C_SLAVE, addr) < 0) {
  /* ERROR HANDLING; you can check errno to see what went wrong */
  exit(1);
}

好了,現在一切都設定好了。您現在可以使用 SMBus 命令或普通的 I2C 與您的裝置進行通訊。如果裝置支援,則首選 SMBus 命令。下面說明了兩者

__u8 reg = 0x10; /* Device register to access */
__s32 res;
char buf[10];

/* Using SMBus commands */
res = i2c_smbus_read_word_data(file, reg);
if (res < 0) {
  /* ERROR HANDLING: I2C transaction failed */
} else {
  /* res contains the read word */
}

/*
 * Using I2C Write, equivalent of
 * i2c_smbus_write_word_data(file, reg, 0x6543)
 */
buf[0] = reg;
buf[1] = 0x43;
buf[2] = 0x65;
if (write(file, buf, 3) != 3) {
  /* ERROR HANDLING: I2C transaction failed */
}

/* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
if (read(file, buf, 1) != 1) {
  /* ERROR HANDLING: I2C transaction failed */
} else {
  /* buf[0] contains the read byte */
}

請注意,只有 I2C 和 SMBus 協議的一個子集可以透過 read() 和 write() 呼叫來實現。特別是,所謂的組合事務(在同一事務中混合讀寫訊息)不受支援。因此,使用者空間程式幾乎從不使用此介面。

重要提示:由於使用了行內函數,您在編譯程式時必須使用“-O”或某些變體!

完整介面描述

定義了以下 IOCTL

ioctl(file, I2C_SLAVE, long addr)

更改從站地址。該地址在引數的低 7 位中傳遞(除了 10 位地址,在這種情況下在低 10 位中傳遞)。

ioctl(file, I2C_TENBIT, long select)

如果 select 不等於 0,則選擇十位地址,如果 select 等於 0,則選擇正常的 7 位地址。預設為 0。只有當介面卡具有 I2C_FUNC_10BIT_ADDR 時,此請求才有效。

ioctl(file, I2C_PEC, long select)

如果 select 不等於 0,則選擇 SMBus PEC(資料包錯誤檢查)生成和驗證,如果 select 等於 0,則停用。預設為 0。僅用於 SMBus 事務。僅當介面卡具有 I2C_FUNC_SMBUS_PEC 時,此請求才有效;即使沒有,也是安全的,只是沒有任何效果。

ioctl(file, I2C_FUNCS, unsigned long *funcs)

獲取介面卡功能並將其放入 *funcs

ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)

執行組合的讀/寫事務,中間沒有停止。僅當介面卡具有 I2C_FUNC_I2C 時才有效。該引數是指向

struct i2c_rdwr_ioctl_data {
  struct i2c_msg *msgs;  /* ptr to array of simple messages */
  int nmsgs;             /* number of messages to exchange */
}

msgs[] 本身包含指向資料緩衝區的進一步指標。該函式將根據是否在特定訊息中設定了 I2C_M_RD 標誌,將資料寫入或讀取到這些緩衝區。從站地址以及是否使用十位地址模式必須在每條訊息中設定,覆蓋透過上述 ioctl 設定的值。

ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args)

如果可能,請使用下面提供的 i2c_smbus_* 方法,而不是發出直接 ioctl。

您可以透過使用 read(2) 和 write(2) 呼叫來執行普通的 I2C 事務。您不需要傳遞地址位元組;而是,在嘗試訪問裝置之前,透過 ioctl I2C_SLAVE 設定它。

您可以透過以下函式執行 SMBus 級別事務(有關詳細資訊,請參閱文件檔案 SMBus 協議

__s32 i2c_smbus_write_quick(int file, __u8 value);
__s32 i2c_smbus_read_byte(int file);
__s32 i2c_smbus_write_byte(int file, __u8 value);
__s32 i2c_smbus_read_byte_data(int file, __u8 command);
__s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
__s32 i2c_smbus_read_word_data(int file, __u8 command);
__s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
__s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
__s32 i2c_smbus_block_process_call(int file, __u8 command, __u8 length,
                                   __u8 *values);
__s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values);
__s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length,
                                 __u8 *values);

所有這些事務在失敗時返回 -1;您可以讀取 errno 以檢視發生了什麼。 “write”事務在成功時返回 0;“read”事務返回讀取的值,除了 read_block,它返回讀取的值的數量。 塊緩衝區不需要長於 32 個位元組。

透過連結 libi2c 庫,可以使用上述函式,該庫由 i2c-tools 專案提供。 請參閱:https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/

實施細節

對於感興趣的人,這裡是當您使用 /dev 介面到 I2C 時,核心內部發生的程式碼流程

  1. 您的程式開啟 /dev/i2c-N 並在其上呼叫 ioctl(),如上面的“C 示例”部分所述。

  2. 這些 open() 和 ioctl() 呼叫由 i2c-dev 核心驅動程式處理:請參閱 i2c-dev.c:i2cdev_open() 和 i2c-dev.c:i2cdev_ioctl()。 您可以將 i2c-dev 視為可以從使用者空間程式設計的通用 I2C 晶片驅動程式。

  3. 一些 ioctl() 呼叫用於管理任務,並由 i2c-dev 直接處理。 示例包括 I2C_SLAVE(設定要訪問的裝置的地址)和 I2C_PEC(啟用或停用未來事務的 SMBus 錯誤檢查)。

  4. 其他 ioctl() 呼叫由 i2c-dev 轉換為核心函式呼叫。 示例包括 I2C_FUNCS,它使用 i2c.h:i2c_get_functionality() 查詢 I2C 介面卡功能,以及 I2C_SMBUS,它使用 i2c-core-smbus.c:i2c_smbus_xfer() 執行 SMBus 事務。

    i2c-dev 驅動程式負責檢查來自使用者空間的所有引數的有效性。 在此之後,從使用者空間透過 i2c-dev 發出的這些呼叫與核心 I2C 晶片驅動程式直接執行的呼叫之間沒有區別。 這意味著 I2C 匯流排驅動程式無需實現任何特殊功能即可支援從使用者空間進行訪問。

  5. 這些 i2c.h 函式是 I2C 匯流排驅動程式的實際實現的包裝器。 每個介面卡都必須宣告實現這些標準呼叫的回撥函式。 i2c.h:i2c_get_functionality() 呼叫 i2c_adapter.algo->functionality(),而 i2c-core-smbus.c:i2c_smbus_xfer() 呼叫 adapter.algo->smbus_xfer()(如果已實現),否則呼叫 i2c-core-smbus.c:i2c_smbus_xfer_emulated(),後者又呼叫 i2c_adapter.algo->master_xfer()。

在您的 I2C 匯流排驅動程式處理完這些請求後,執行沿著呼叫鏈向上執行,幾乎沒有進行任何處理,除了 i2c-dev 將返回的資料(如果有)以適合 ioctl 的格式打包。