如何debug通訊協議 I2C 子系統

roc_guo發表於2022-07-01

I2C 常見有兩種錯誤:I2C ACK error、I2C timeout

一、I2C ACK error

在應該收到 ACK 訊號的時候沒有收到 ACK 訊號,i2c controller 就會產生一個 ACK error 的中斷,告訴 i2c driver 發生了 ACK error。通常情況是 slave 本身的問題。

如何debug通訊協議 I2C 子系統如何debug通訊協議 I2C 子系統

1、檢查 device 是否存在,i2c bus number 和 device address 是否正確。示例如下:i2c number 為 6,addr 為 0x28:

[31.092951][xxx]i2c i2c-6:addr:0x28,ACK error

2、檢查 device 是否已經上電使能,以及正確的 init。

3、檢查 i2c speed 是否適配,speed 大於 device 支援的 max speed 也會造成 ACK Error。降低速度,如果仍然可以工作的話就說明是 clk 相關問題。

4、檢查 i2c device 訊號電平是否與 AP 匹配。

5、GPIO check 以下幾個部分:

  • GPIO 電流驅動能力。
  • GPIO 工作模式是否是 I2C 模式。
  • GPIO 是否有內部上拉電阻。
  • GPIO 預設電平狀態。
  • 從 i2c spec 看,如下情況 NACK 是正常的。

    I2C 寫

    如何debug通訊協議 I2C 子系統如何debug通訊協議 I2C 子系統

    主機向從機傳送資料時,最後一個 Byte 資料時,從機可能應答也可能非應答,但不怎樣主機都可以產生停止條件,如果主機在向從機傳送資料(甚至包括從機地址在內)時檢測到從機非應答,則會及時停止傳輸。

    I2C 讀

    如何debug通訊協議 I2C 子系統如何debug通訊協議 I2C 子系統

    主機從從機接收資料時,最後一個 Byte 資料時,主機不回應該從機,即 NACK。

    二、I2C timeout

    當 I2C 傳輸發生 timeout 時,一般 kernel log 會有類似如下列印:

[48.197718][xxx]i2c i2c-1: addr:0xa,transfer timeout

如何debug通訊協議 I2C 子系統如何debug通訊協議 I2C 子系統

1、GPIO check 以下幾個部分。

  • GPIO 電流驅動能力。
  • GPIO 工作模式是否是 I2C 模式。
  • GPIO 是否有內部上拉電阻。
  • GPIO 預設電平狀態。
  • 2、排查 slave 順序。

  • log 中第一個發生 timeout 的 slave。
  • 有 power 控制和 reset 控制的 slave。
  • other slave。
  • 復現問題後,可以手動將相應外設去掉,確認是哪個外設將 i2c bus 拉住,再與供應商溝通,debug 一下該 IC 狀態,理清拉住 i2c bus 原因。

    三、i2c-tools

    i2c-tools 也很好用,這個工具工具博主上次寫過,參考以下文章:

    手把手教你使用 i2c-tools。

    四、常見問題
    1、同一條 i2c bus 上所有的外設的 i2c addr 要不同

    (1)相同 address 註冊時衝突.

    [2.059184][xxx]i2c i2c-1:Failed to register i2c client 24c02 at 0x51(-16) 
    [2.059189][xxx]i2c i2c-1:Can't create device at 0x51

    對應的錯誤碼是 -16。

    /kernel-5.10/include/uapi/asm-generic/errno-base.h
    #define EPERM   1 /* Operation not permitted */
    #define ENOENT   2 /* No such file or directory */
    #define ESRCH   3 /* No such process */
    #define EINTR   4 /* Interrupted system call */
    #define EIO   5 /* I/O error */
    #define ENXIO   6 /* No such device or address */
    #define E2BIG   7 /* Argument list too long */
    #define ENOEXEC   8 /* Exec format error */
    #define EBADF   9 /* Bad file number */
    #define ECHILD  10 /* No child processes */
    #define EAGAIN  11 /* Try again */
    #define ENOMEM  12 /* Out of memory */
    #define EACCES  13 /* Permission denied */
    #define EFAULT  14 /* Bad address */
    #define ENOTBLK  15 /* Block device required */
    #define EBUSY  16 /* Device or resource busy */
    ......

    可以執行 ls /sys/bus/i2c/devices 檢視對應的 i2c-1 下是否已經有註冊相同的 address 的外設。

    若返回 -11,-EAGAIN。代表匯流排正忙,或無法申請到匯流排鎖。如果匯流排正忙,請 retry 等待,或檢視是哪個 device 一直在傳送。如果無法申請到匯流排鎖,請檢視是否在中斷函式或原子上下文呼叫了 i2c_transfer。

    (2)隱藏的 i2c address,即外設存在多個 i2c addr 或外設 HW bug,導致 i2c 通訊異常。

    示例:eeprom 在 i2c-1 上註冊了 0x50 地址,而 type c 雖然註冊到 0x60 地址,但對 0x50 也能產生響應,type c 拉低了 SDA ,從而 timeout 。

    debug 方法:

  • 軟體上確認資料及對應的 driver 是否正確。
  • 硬體上逐一去掉外設,確認是哪個外設導致。
  • 2、示波器 ACK 的地方有毛刺

    slave 在第 9 個 clk 產生 ACK 應答後,換成 mater 端來控制時產生的毛刺。此毛刺不會影響 I2C 匯流排讀寫時序,無需處理。

    即 slave 和 master 控制匯流排切換間隔,沒有人控制匯流排,帶來的毛刺。

    3、半高電平

    在外接上拉電阻的情況下,有 enable 內部下拉電阻,導致匯流排上有半高電平。

    4、匯流排上電平拉不到地

    master 端傳送資料時電平拉不到地,可以增大驅動電流或者上拉電阻。

    slave 端拉不到地,可以諮詢供應商看是否能增大 slave 端驅動電流或者上拉電阻。

    5、RK 平臺 I2C debug

    來源於 firefly。

    
    

    I2C 通訊失敗,出現 log: “timeout, ipd: 0x00, state: 1”

    請檢查硬體上拉是否給電。

    呼叫 i2c_transfer 返回值為 -6?

    返回值為 -6 表示為 NACK 錯誤,即對方裝置無應答響應,這種情況一般為外設的問題,常見的有以下幾種情況:

  • I2C 地址錯誤,解決方法是測量 I2C 波形,確認是否 I2C 裝置地址錯誤;
  • I2C slave 裝置不處於正常工作狀態,比如未給電,錯誤的上電時序等;
  • 時序不符合 I2C slave 裝置所要求也會產生 Nack 訊號。
  • 當外設對於讀時序要求中間是 stop 訊號不是 repeat start 訊號的時候,該如何處理?

    這時需要呼叫兩次 i2c_transfer, I2C read 拆分成兩次,修改如下:

    static int i2c_read_bytes(struct i2c_client *client, u8 cmd, u8 *data, u8 data_len) {struct i2c_msg msgs[2];
         int ret;
         u8 *buffer;
         buffer = kzalloc(data_len, GFP_KERNEL);
         if (!buffer)
             return -ENOMEM;
         msgs[0].addr = client->addr;
         msgs[0].flags = client->flags;
         msgs[0].len = 1;
         msgs[0].buf = &cmd;
         ret = i2c_transfer(client->adapter, msgs, 1);
         if (ret < 0) {
          dev_err(&client->adapter->dev, "i2c read failed\n");
                kfree(buffer);
           return ret;
          }
          msgs[1].addr = client->addr;
          msgs[1].flags = client->flags | I2C_M_RD;
          msgs[1].len = data_len;
          msgs[1].buf = buffer;
          ret = i2c_transfer(client->adapter, &msgs[1], 1);
          if (ret < 0)
              dev_err(&client->adapter->dev, "i2c read failed\n");
          else
            memcpy(data, buffer, data_len);
          kfree(buffer);
          return ret;
    }

    相信以上的 I2C debug 方法已經能為大家解決大部分問題,如果還是沒解決,一般是晶片問題或者原廠底層 code bug,可以找晶片原廠支援。


    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2903764/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章