跳轉到內容

Khepera III 工具箱/工具箱/模組/i2cal

來自華夏公益教科書

i2cal 模組提供了一個非常易於使用的 I2C 介面。I2C 匯流排是 Khepera III 機器人的“主幹”,用於與三個微控制器通訊

  • 主微控制器(紅外感測器、超聲波感測器、電池)
  • 左側電機控制器
  • 右側電機控制器

要使用機器人的感測器和執行器,您可能需要使用khepera3 模組,該模組包含高階函式,並使用此模組本身來與微控制器通訊。但是,如果您開發了自己的擴充套件板,並將它們連線到機器人的頂部,此模組提供了您透過 I2C 匯流排訪問這些板所需的一切。

// Initialize the module
i2cal_init();

// Let's assume that the I2C address of your device is 0x45
i2c_address = 0x45;

// Write the 16-bit value 10000 to register 0x82
i2cal_start();
i2cal_writedata_uint8(0x82);
i2cal_writedata_uint16(10000);
i2cal_write(i2c_address);
i2cal_commit();

// Read a 16-bit followed by two 8-bit values (4 bytes total) from register 0x83
i2cal_start();
i2cal_writedata_uint8(0x83);
i2cal_write(i2c_address);
msg_read = i2cal_read(i2c_address, 4);
i2cal_commit();

first_value = i2cal_readdata_uint16(msg_read, 0);  // read two bytes starting at byte 0
second_value = i2cal_readdata_uint8(msg_read, 2);  // read one byte starting at byte 2
third_value = i2cal_readdata_uint8(msg_read, 3);   // read one byte starting at byte 3

在初始化 (i2cal_init) 之後,i2cal 模組允許在 I2C 總線上傳送事務。事務由一系列訊息交換組成,要麼從處理器到您的板 (寫入),要麼從您的板到處理器 (讀取)。

事務以i2cal_start開始,並以i2cal_commit執行。在這兩個呼叫之間,可以新增一系列讀寫訊息。寫訊息透過首先寫入資料 (i2cal_writedata_*) 新增,然後呼叫i2cal_write,並將裝置地址作為引數。讀訊息透過i2cal_read新增,該訊息將裝置地址和要讀取的位元組數作為引數,並返回指向i2c_msg 結構的指標。

i2cal_commit 呼叫傳輸訊息,並在所有資料成功傳送和接收後返回。接收到的位元組在i2c_msg 結構中可用,可以使用i2cal_readdata_* 函式讀取。請注意,在開始新的事務之前,必須從這些結構中讀取所有資料。

錯誤處理

[編輯 | 編輯原始碼]

i2cal_commit 函式返回 -1 表示成功,或 0 表示失敗。失敗僅僅意味著資料無法傳輸,通常是由以下原因之一引起

  • 微控制器韌體中的錯誤,即微控制器沒有(或錯誤地)響應 I2C 請求。
  • I2C 總線上另一個微控制器的韌體中的錯誤,導致其行為異常(例如,透過將 SDA 或 SCL 拉低)。
  • 錯誤的電氣連線,例如 SDA/SCL 翻轉,其中一個訊號接地,...

有效地除錯此類錯誤需要示波器(或類似的測試基礎設施),最好是支援 I2C 解碼。

如果電氣設計和微控制器的韌體實現正確,錯誤極不可能發生。因此,通常不值得實現複雜的錯誤處理程式。

事務大小限制

[編輯 | 編輯原始碼]

i2cal 模組將事務限制為 16 條訊息(讀寫訊息組合)。此外,傳送的總位元組數不能超過 256,讀取的總位元組數也不能超過 256。鑑於下一節討論的計時問題,這對於所有實際應用來說應該足夠了。如果需要(例如,用於測試),可以在i2cal.c 中更改這些數字。

計時問題

[編輯 | 編輯原始碼]

Khepera III 機器人上的 I2C 匯流排以 100 KHz 的速度執行。因此,透過該匯流排傳輸一個位元組(8 位 + 一些開銷)大約需要 0.1 毫秒。

一條訊息由一個地址位元組加上要讀取或寫入的資料組成。根據經驗,事務的最小時間可以按如下方式計算

實際上,還有另外兩個延遲會新增到此時間

  • 排隊延遲:由於匯流排一次只能傳送一條訊息,核心會保留訊息和事務的佇列。您的事務將不得不與使用 I2C 匯流排的其他程序競爭。為了對所有程序實現低延遲,最好以小事務(一次< 100 位元組)進行通訊。
  • 微控制器上的處理延遲:每個傳輸的位元組都必須由微控制器進行確認,並且每個接收到的位元組都必須由微控制器準備。對於後者,微控制器允許購買大約 10 毫秒的時間(I2C 時鐘拉伸),在此期間匯流排被佔用,您的程序正在等待。為了實現良好的匯流排利用率,必須避免這種情況。

同步問題

[編輯 | 編輯原始碼]

這裡的事務之所以被稱為事務,是因為它是作為一個原子塊執行的。這一點非常重要,因為以下程式碼片段說明了這一點

// Read a 16-bit value from register 0x83 in one transaction -> correct
i2cal_start();
i2cal_writedata_uint8(0x83);
i2cal_write(i2c_address);
msg_read = i2cal_read(i2c_address, 2);
i2cal_commit();

// Read a 16-bit value from register 0x83 in two transactions -> wrong
i2cal_start();
i2cal_writedata_uint8(0x83);
i2cal_write(i2c_address);
i2cal_commit();
    // <-- Another process may communicate with the same device here, overwriting your 0x83 request
i2cal_start();
msg_read = i2cal_read(i2c_address, 2);
i2cal_commit();

如果只有一個程序在任何給定時間與該裝置通訊,則這兩個程式碼片段是等效的。但是,如果多個程序正在訪問該裝置(並且您應該始終以這種方式思考),將任務拆分為兩個事務是錯誤的。

要了解為什麼,請將自己置於微控制器(或其他 I2C 從裝置)的位置。您收到暫存器 0x83,併為接下來的讀取操作準備您的輸出緩衝區。由於事務在那裡結束,因此另一個程序可能在第一個程序讀取其兩個位元組之前與您通訊。此程序可能傳送暫存器 0x92,並且您正在準備(並且可能正在提供)您的輸出緩衝區以供此新請求使用。當第一個程序最終嘗試讀取其兩個位元組時,微控制器要麼提供 0x92 請求的前兩個位元組,要麼提供一些錯誤程式碼來指示 0x92 的結果已被讀取。

根據經驗,每個獨立的操作(例如暫存器讀取或暫存器寫入)必須執行為恰好一個事務。

多執行緒程式

[編輯 | 編輯原始碼]

由於此模組的資料結構是靜態分配的,因此從兩個不同執行緒呼叫更新相同欄位的函式是不安全的。為了避免執行緒之間的干擾,必須同步從**i2cal_start**到**i2cal_commit**(包含)之間的整個塊。

與 Linux I2C 介面的比較

[編輯 | 編輯原始碼]

Linux(以及大多數其他 Unix 系統)透過對 I2C 裝置檔案(/dev/i2c/0)的檔案操作(特別是 ioctl 呼叫)提供了一個相當簡單的 I2C 匯流排介面。如果您檢視i2cal模組的原始碼,您會注意到i2cal_commit只是對/dev/i2c/0的一個這樣的ioctl呼叫,並且所有其他函式主要準備資料結構以傳遞給該呼叫。然而,i2cal模組透過處理所有必要的緩衝區,以及使用函式來正確地從這些緩衝區讀取和寫入 2 位元組和 4 位元組值,提供了更簡單的介面。

以下列表總結了i2cal模組的功能。

// Module initialization
int i2cal_init();

// Start a new transaction
void i2cal_start();

// Add general read/write messages
struct i2c_msg *i2cal_read_buffer(int dev, unsigned char *buffer, int len);
struct i2c_msg *i2cal_write_buffer(int dev, unsigned char *buffer, int len);

// Simple read/write, using built-in read/write buffers (use i2cal_writedata_* functions)
struct i2c_msg *i2cal_read(int dev, int len);
struct i2c_msg *i2cal_write(int dev);

// Write data to built-in buffer
unsigned char *i2cal_writedata_uint8(unsigned char value);
unsigned char *i2cal_writedata_int16(int value);
unsigned char *i2cal_writedata_uint16(unsigned int value);
unsigned char *i2cal_writedata_int32(int value);
unsigned char *i2cal_writedata_uint32(unsigned int value);
unsigned char *i2cal_writedata_buffer(int len);

// Commit transaction by sending/receiving data. Returns the ioctl return value, which is negative on error.
int i2cal_commit();

// Read data from result buffer
unsigned char i2cal_readdata_uint8(struct i2c_msg *message, int offset);
int i2cal_readdata_int16(struct i2c_msg *message, int offset);
unsigned int i2cal_readdata_uint16(struct i2c_msg *message, int offset);
int i2cal_readdata_int32(struct i2c_msg *message, int offset);
unsigned int i2cal_readdata_uint32(struct i2c_msg *message, int offset);
華夏公益教科書