通信協(xié)議 I2C 子系統(tǒng)之 Debug
I2C 常見有兩種錯誤:I2C ACK error、I2C timeout
一、I2C ACK error
在應該收到 ACK 信號的時候沒有收到 ACK 信號,i2c controller 就會產(chǎn)生一個 ACK error 的中斷,告訴 i2c driver 發(fā)生了 ACK error。通常情況是 slave 本身的問題。
1、檢查 device 是否存在,i2c bus number 和 device address 是否正確。示例如下:i2c number 為 6,addr 為 0x28:
[31.092951][xxx]i2c i2c-6:addr:0x28,ACK error
2、檢查 device 是否已經(jīng)上電使能,以及正確的 init。
3、檢查 i2c speed 是否適配,speed 大于 device 支持的 max speed 也會造成 ACK Error。降低速度,如果仍然可以工作的話就說明是 clk 相關問題。
4、檢查 i2c device 信號電平是否與 AP 匹配。
5、GPIO check 以下幾個部分:
- GPIO 電流驅(qū)動能力。
- GPIO 工作模式是否是 I2C 模式。
- GPIO 是否有內(nèi)部上拉電阻。
- GPIO 默認電平狀態(tài)。
從 i2c spec 看,如下情況 NACK 是正常的。
I2C 寫
主機向從機發(fā)送數(shù)據(jù)時,最后一個 Byte 數(shù)據(jù)時,從機可能應答也可能非應答,但不怎樣主機都可以產(chǎn)生停止條件,如果主機在向從機發(fā)送數(shù)據(jù)(甚至包括從機地址在內(nèi))時檢測到從機非應答,則會及時停止傳輸。
I2C 讀
主機從從機接收數(shù)據(jù)時,最后一個 Byte 數(shù)據(jù)時,主機不回應該從機,即 NACK。
二、I2C timeout
當 I2C 傳輸發(fā)生 timeout 時,一般 kernel log 會有類似如下打?。?/p>
[48.197718][xxx]i2c i2c-1: addr:0xa,transfer timeout
1、GPIO check 以下幾個部分。
- GPIO 電流驅(qū)動能力。
- GPIO 工作模式是否是 I2C 模式。
- GPIO 是否有內(nèi)部上拉電阻。
- GPIO 默認電平狀態(tài)。
2、排查 slave 順序。
- log 中第一個發(fā)生 timeout 的 slave。
- 有 power 控制和 reset 控制的 slave。
- other slave。
復現(xiàn)問題后,可以手動將相應外設去掉,確認是哪個外設將 i2c bus 拉住,再與供應商溝通,debug 一下該 IC 狀態(tài),理清拉住 i2c bus 原因。
三、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 */
......
可以執(zhí)行 ls /sys/bus/i2c/devices 查看對應的 i2c-1 下是否已經(jīng)有注冊相同的 address 的外設。
若返回 -11,-EAGAIN。代表總線正忙,或無法申請到總線鎖。如果總線正忙,請 retry 等待,或查看是哪個 device 一直在發(fā)送。如果無法申請到總線鎖,請查看是否在中斷函數(shù)或原子上下文調(diào)用了 i2c_transfer。
(2)隱藏的 i2c address,即外設存在多個 i2c addr 或外設 HW bug,導致 i2c 通訊異常。
示例:eeprom 在 i2c-1 上注冊了 0x50 地址,而 type c 雖然注冊到 0x60 地址,但對 0x50 也能產(chǎn)生響應,type c 拉低了 SDA ,從而 timeout 。
debug 方法:
- 軟件上確認數(shù)據(jù)及對應的 driver 是否正確。
- 硬件上逐一去掉外設,確認是哪個外設導致。
2、示波器 ACK 的地方有毛刺
slave 在第 9 個 clk 產(chǎn)生 ACK 應答后,換成 mater 端來控制時產(chǎn)生的毛刺。此毛刺不會影響 I2C 總線讀寫時序,無需處理。
即 slave 和 master 控制總線切換間隔,沒有人控制總線,帶來的毛刺。
3、半高電平
在外接上拉電阻的情況下,有 enable 內(nèi)部下拉電阻,導致總線上有半高電平。
4、總線上電平拉不到地
- master 端發(fā)送數(shù)據(jù)時電平拉不到地,可以增大驅(qū)動電流或者上拉電阻。
- slave 端拉不到地,可以咨詢供應商看是否能增大 slave 端驅(qū)動電流或者上拉電阻。
5、RK 平臺 I2C debug
來源于 firefly。
https://wiki.t-firefly.com/zh_CN/Firefly-RK3399/driver_i2c.html
I2C 通信失敗,出現(xiàn) log: “timeout, ipd: 0x00, state: 1”
請檢查硬件上拉是否給電。
調(diào)用 i2c_transfer 返回值為 -6?
返回值為 -6 表示為 NACK 錯誤,即對方設備無應答響應,這種情況一般為外設的問題,常見的有以下幾種情況:
- I2C 地址錯誤,解決方法是測量 I2C 波形,確認是否 I2C 設備地址錯誤;
- I2C slave 設備不處于正常工作狀態(tài),比如未給電,錯誤的上電時序等;
- 時序不符合 I2C slave 設備所要求也會產(chǎn)生 Nack 信號。
當外設對于讀時序要求中間是 stop 信號不是 repeat start 信號的時候,該如何處理?
這時需要調(diào)用兩次 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 方法已經(jīng)能為大家解決大部分問題,如果還是沒解決,一般是芯片問題或者原廠底層 code bug,可以找芯片原廠支持。