在嵌入式開(kāi)發(fā)中,I2C總線是連接外設(shè)的“橋梁”——小到傳感器、EEPROM,大到LCD驅(qū)動(dòng)器、音頻芯片,都離不開(kāi)它的控制。而瑞芯微(Rockchip)系列芯片作為主流嵌入式方案,其I2C控制器的開(kāi)發(fā)是很多工程師的必備技能。
今天這篇文章,我們將從I2C硬件原理和數(shù)據(jù)幀講起,再結(jié)合官方《ROCKCHIP I2C開(kāi)發(fā)指南》,梳理RK平臺(tái)I2C開(kāi)發(fā)的全流程、關(guān)鍵配置和常見(jiàn)問(wèn)題,幫你快速上手!
一、先搞懂基礎(chǔ):I2C硬件原理與數(shù)據(jù)幀
在動(dòng)手開(kāi)發(fā)前,必須先掌握I2C的“底層邏輯”——硬件結(jié)構(gòu)和數(shù)據(jù)傳輸規(guī)則,否則后續(xù)排查問(wèn)題會(huì)寸步難行。
1. I2C硬件原理:兩條線搞定通信
I2C總線最核心的特點(diǎn)是“簡(jiǎn)單”:僅需SDA(串行數(shù)據(jù)線)和SCL(串行時(shí)鐘線)兩條線,就能實(shí)現(xiàn)多主多從的通信(RK平臺(tái)僅支持主模式)。
關(guān)鍵硬件細(xì)節(jié):
?總線拓?fù)?/span>:所有設(shè)備的SDA連在一起,SCL連在一起;每個(gè)設(shè)備有唯一地址,主設(shè)備(如RK芯片)通過(guò)地址識(shí)別從設(shè)備(如傳感器)。
?上拉電阻:SDA和SCL必須外接上拉電阻(通常4.7kΩ~10kΩ),因?yàn)?/span>I2C設(shè)備的引腳是開(kāi)漏輸出——只能拉低電平,無(wú)法主動(dòng)輸出高電平,需通過(guò)上拉電阻將總線拉到高電平(Vcc通常為3.3V)。
??RK文檔中提到:“改變上拉電阻大小可調(diào)節(jié)I2C總線的上拉強(qiáng)度”,本質(zhì)是通過(guò)電阻值影響總線的上升沿時(shí)間(后面會(huì)講上升沿的重要性)。
?主從關(guān)系:主設(shè)備負(fù)責(zé)生成SCL時(shí)鐘、發(fā)起通信(發(fā)送起始/停止信號(hào));從設(shè)備被動(dòng)響應(yīng),根據(jù)主設(shè)備發(fā)送的地址匹配自身。
2. I2C數(shù)據(jù)幀:通信的“語(yǔ)言規(guī)則”
I2C數(shù)據(jù)以“幀”為單位傳輸,每幀包含固定的結(jié)構(gòu),主從設(shè)備必須遵循這套規(guī)則才能正常通信。完整幀結(jié)構(gòu)如下(以常用的7位尋址為例):
[起始信號(hào)]→[地址字節(jié)]→[ACK/NACK]→[數(shù)據(jù)字節(jié)1]→[ACK/NACK]→ ... →[數(shù)據(jù)字節(jié)n]→[ACK/NACK]→[停止信號(hào)]
各部分詳解:
?起始信號(hào)(S):主設(shè)備拉低SDA(此時(shí)SCL為高電平),表示通信開(kāi)始。
?地址字節(jié):8位,前7位是從設(shè)備地址,第8位是“讀寫(xiě)位”——0表示“主→從寫(xiě)數(shù)據(jù)”,1表示“主←從讀數(shù)據(jù)”。
若用10位尋址,地址字節(jié)分兩部分:第一字節(jié)是固定前綴11110+ 10位地址的高2位,第二字節(jié)是10位地址的低8位。
?ACK/NACK:每傳輸1字節(jié)后,接收方需反饋1位:
?ACK(應(yīng)答):接收方拉低SDA(表示已接收);
?NACK(非應(yīng)答):接收方不拉低SDA(表示未接收或結(jié)束傳輸)。
?數(shù)據(jù)字節(jié):8位,可連續(xù)傳輸(RK控制器一次最多傳32字節(jié))。
?停止信號(hào)(P):主設(shè)備拉高SDA(此時(shí)SCL為高電平),表示通信結(jié)束。

二、RK平臺(tái)I2C開(kāi)發(fā)核心:控制器、驅(qū)動(dòng)與流程
掌握基礎(chǔ)后,我們聚焦RK平臺(tái)的具體開(kāi)發(fā)——先了解控制器功能,再區(qū)分驅(qū)動(dòng)差異,最后梳理開(kāi)發(fā)流程。
1. RK I2C控制器:支持哪些能力?
RK系列芯片的I2C控制器兼容性強(qiáng)、配置靈活,核心功能如下(文檔V2.2.0版本):
?兼容I2C協(xié)議和SMBus協(xié)議(常見(jiàn)外設(shè)均支持);
?僅支持主模式(RK芯片作為主設(shè)備,控制外部從設(shè)備);
?時(shí)鐘頻率:軟件可編程,最高支持1000kbps(Fast-mode Plus),部分芯片默認(rèn)支持400kbps(Fast-mode);
?尋址模式:支持7位地址和10位地址(覆蓋絕大多數(shù)外設(shè));
?數(shù)據(jù)傳輸:一次中斷/輪詢最多傳輸32字節(jié),效率較高。
2.關(guān)鍵注意:RK平臺(tái)的兩種I2C驅(qū)動(dòng)
RK平臺(tái)的I2C驅(qū)動(dòng)因內(nèi)核版本不同分為兩類,配置方式差異較大,必須區(qū)分清楚:
|
驅(qū)動(dòng)文件
|
適用內(nèi)核版本
|
配置方式
|
最高頻率
|
|
i2c-rk3x.c
|
Linux 4.19+(如RK356X、RV1126)
|
設(shè)備樹(shù)(DTS)配置
|
1000kbps
|
|
i2c-rockchip.c
|
Linux 3.10內(nèi)核
|
代碼中配置i2c_msg結(jié)構(gòu)體
|
1000kbps
|
已支持“所有RK芯片+所有內(nèi)核版本”,實(shí)際開(kāi)發(fā)時(shí)需先確認(rèn)所用內(nèi)核版本,再選擇對(duì)應(yīng)驅(qū)動(dòng)。
3. RK I2C開(kāi)發(fā)流程:三種傳輸模式
RK I2C控制器的核心是“傳輸模式”,不同場(chǎng)景對(duì)應(yīng)不同模式,本質(zhì)是通過(guò)配置寄存器(如I2C_CON、I2C_CLKDIV)實(shí)現(xiàn)。
模式1:只發(fā)送模式(Transmit only,I2C_CON[1:0] = 2'b00)
適用于“主設(shè)備向從設(shè)備寫(xiě)數(shù)據(jù)”(如配置傳感器參數(shù)),步驟如下:
1.配置I2C_CLKDIV:設(shè)置SCL時(shí)鐘頻率(如400kbps);
2.配置I2C_CON:選擇“只發(fā)送模式”,并發(fā)送起始信號(hào)(S);
3.向I2C_TXDATA0~TXDATA7寫(xiě)入要發(fā)送的數(shù)據(jù);
4.配置I2C_MTXCNT:設(shè)置發(fā)送數(shù)據(jù)的字節(jié)數(shù);
5.等待“發(fā)送完成中斷”(I2C_IPD[2]);
6.若有更多數(shù)據(jù),重復(fù)步驟3~5;若無(wú),配置I2C_CON發(fā)送停止信號(hào)(P),結(jié)束傳輸。
模式2:混合模式(Mix mode,I2C_CON[1:0] = 2'b01/11)
適用于“先寫(xiě)后讀”(如先發(fā)送寄存器地址,再讀取該寄存器的值),步驟如下:
1.配置I2C_CLKDIV:設(shè)置SCL頻率;
2.配置I2C_CON:選擇“混合模式”,發(fā)送起始信號(hào);
3.配置I2C_MRXADDR和I2C_MRXRADDR:設(shè)置從設(shè)備地址和讀取地址;
4.配置I2C_MRXCNT:設(shè)置要讀取的數(shù)據(jù)字節(jié)數(shù);
5.等待“接收完成中斷”(I2C_IPD[3]);
6.若有更多數(shù)據(jù),重復(fù)步驟3~5;若無(wú),發(fā)送停止信號(hào),結(jié)束傳輸。
模式3:只接收模式(Receive only,I2C_CON[1:0] = 2'b10)
適用于“主設(shè)備從從設(shè)備讀數(shù)據(jù)”(如讀取傳感器采集的數(shù)值),步驟如下:
1.配置I2C_CLKDIV:設(shè)置SCL頻率;
2.配置I2C_CON:選擇“只接收模式”,發(fā)送起始信號(hào);
3.配置I2C_MRXCNT:設(shè)置接收數(shù)據(jù)的字節(jié)數(shù);
4.等待“接收完成中斷”(I2C_IPD[3]);
5.若有更多數(shù)據(jù),重復(fù)步驟3~4;若無(wú),發(fā)送停止信號(hào),結(jié)束傳輸。
4.驅(qū)動(dòng)參數(shù)配置:關(guān)鍵是“時(shí)鐘頻率”
I2C通信能否穩(wěn)定,核心是“時(shí)鐘頻率配置”——需符合I2C協(xié)議對(duì)“上升沿時(shí)間(Tr)”和“下降沿時(shí)間(Tf)”的要求,否則會(huì)出現(xiàn)通信失敗。
第一步:明確協(xié)議時(shí)序要求
I2C協(xié)議對(duì)不同模式的時(shí)序有嚴(yán)格規(guī)定(文檔中表格整理):
|
參數(shù)
|
標(biāo)準(zhǔn)模式(100kbps)
|
快速模式(400kbps)
|
高速模式(1000kbps)
|
單位
|
|
SCL頻率
|
≤100
|
≤400
|
≤1000
|
kHz
|
|
上升沿Tr
|
≤1000
|
≤300
|
≤120
|
ns
|
|
下降沿Tf
|
≤300
|
≤300
|
≤300
|
ns
|
注:Tr和Tf需用示波器測(cè)量,若超過(guò)最大值,需調(diào)整上拉電阻(如減小電阻值縮短上升沿)。
第二步:兩種驅(qū)動(dòng)的配置示例
?i2c-rk3x.c(DTS配置):
配置在設(shè)備樹(shù)中,關(guān)鍵參數(shù)是clock-frequency(時(shí)鐘頻率)、i2c-scl-rising-time-ns(SCL上升沿時(shí)間)。
示例(配置I2C1為400kbps):
&i2c1 {status ="okay";i2c-scl-rising-time-ns = <265>; //示波器實(shí)測(cè)上升沿265nsi2c-scl-falling-time-ns = <11>; //下降沿通常不變,可默認(rèn)clock-frequency = <400000>; //400kbps(Fast-mode)// 掛載從設(shè)備(如 ES8316 音頻芯片)es8316: es8316@10 {compatible ="everest,es8316";reg = <0x10>; //從設(shè)備地址0x10// 其他外設(shè)參數(shù)...};};
?i2c-rockchip.c(代碼配置):
在代碼中配置i2c_msg結(jié)構(gòu)體的scl_rate成員,示例(配置200kbps):
structi2c_msg xfer_msg;xfer_msg[0].addr = client->addr; // 從設(shè)備地址xfer_msg[0].len = num; // 數(shù)據(jù)長(zhǎng)度xfer_msg[0].flags = client->flags;// 讀寫(xiě)標(biāo)志(0=寫(xiě),1=讀)xfer_msg[0].buf = buf; // 數(shù)據(jù)緩存xfer_msg[0].scl_rate =200*1000; //200kbps 時(shí)鐘頻率
5. RK I2C如何使用??jī)?nèi)核態(tài)/用戶態(tài)/工具
(1)內(nèi)核態(tài)使用(推薦,適合產(chǎn)品化)
RK I2C內(nèi)核態(tài)開(kāi)發(fā)遵循Linux標(biāo)準(zhǔn)I2C接口,參考內(nèi)核文檔Documentation/i2c/writing-clients,核心是:
?注冊(cè)I2C客戶端驅(qū)動(dòng)(i2c_driver);
?使用i2c_master_send()(寫(xiě)數(shù)據(jù))和i2c_master_recv()(讀數(shù)據(jù))接口。
(2)用戶態(tài)使用(適合調(diào)試)
通過(guò)/dev/i2c-%d設(shè)備節(jié)點(diǎn)直接訪問(wèn),步驟:
1.打開(kāi)設(shè)備節(jié)點(diǎn):int fd = open("/dev/i2c-1", O_RDWR);;
2.設(shè)置從設(shè)備地址:ioctl(fd, I2C_SLAVE, 0x10);(0x10為從設(shè)備地址);
3.讀寫(xiě)數(shù)據(jù):用read()/write()函數(shù)直接讀寫(xiě)。
??參考內(nèi)核文檔Documentation/i2c/dev-interface。
(3)I2C工具(快速調(diào)試必備)
I2C-tools是開(kāi)源工具集,需交叉編譯后使用,支持命令行調(diào)試:
?下載地址:
https://www.kernel.org/pub/software/utils/i2c-tools/
或git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git
?核心工具:
?i2cdetect:掃描I2C總線及掛載的設(shè)備(如i2cdetect -y 1掃描總線1);
?i2cdump:讀取從設(shè)備所有寄存器的值(如i2cdump -f -y 1 0x10);
?i2cget:讀取單個(gè)寄存器(如i2cget -y 1 0x10 0x01讀地址0x10的0x01寄存器);
?i2cset:寫(xiě)入單個(gè)寄存器(如i2cset -y 1 0x10 0x01 0x55寫(xiě)0x55到0x01寄存器)。
(4)GPIO模擬I2C(不推薦)
RK內(nèi)核支持用GPIO模擬I2C,但效率低,僅適合臨時(shí)調(diào)試。配置示例(DTS):
i2c@4{compatible ="i2c-gpio";gpios = <&gpio5?9GPIO_ACTIVE_HIGH>,// SDA 引腳<&gpio5?8GPIO_ACTIVE_HIGH>;// SCL 引腳i2c-gpio,delay-us = <2>;// 約 100kbps 頻率status ="okay";// 掛載從設(shè)備(如 GT9xx 觸摸屏)gt9xx: gt9xx@14{compatible ="goodix,gt9xx";reg = <0x14>;// 其他外設(shè)參數(shù)...};};
三、RK I2C常見(jiàn)問(wèn)題:從錯(cuò)誤碼到波形Debug
開(kāi)發(fā)中難免遇到問(wèn)題,文檔中整理了兩類驅(qū)動(dòng)的常見(jiàn)錯(cuò)誤,我們按“錯(cuò)誤類型”分類梳理,方便排查。
1. NACK錯(cuò)誤:從設(shè)備無(wú)應(yīng)答
現(xiàn)象:
?i2c-rk3x.c:調(diào)用傳輸接口返回-6(-ENXIO);
?i2c-rockchip.c:調(diào)用傳輸接口返回-11(-EAGAIN)。
原因與解決:
1.I2C地址錯(cuò)誤:確認(rèn)從設(shè)備地址(如文檔中ES8316是0x10,GT9xx是0x14),注意地址是否需要左移(7位地址通常需左移1位,加讀寫(xiě)位);
2.從設(shè)備未正常工作:檢查從設(shè)備供電(如是否上電)、上電時(shí)序是否正確;
3.時(shí)序不匹配:從設(shè)備需要停止信號(hào)(P),但主設(shè)備發(fā)送了重復(fù)起始信號(hào)(Sr),需修改傳輸流程;
4.總線干擾:用示波器測(cè)量,若實(shí)際是ACK波形卻報(bào)NACK,需排查外部干擾(如布線是否靠近強(qiáng)電)。
2.超時(shí)錯(cuò)誤:日志提示“timeout”
根據(jù)日志中ipd的值,對(duì)應(yīng)不同問(wèn)題:
(1)日志:timeout, ipd: 0x00, state: 1
?問(wèn)題:I2C控制器異常,無(wú)法發(fā)送起始信號(hào);
?原因:
a.SCL/SDA引腳復(fù)用錯(cuò)誤(IOMUX配置錯(cuò));
b.上拉電壓不對(duì)(如3.3V上拉變成1.8V);
c.引腳被外設(shè)拉低(電壓異常);
d.I2C時(shí)鐘未開(kāi)啟或時(shí)鐘源過(guò)小;
e.同時(shí)配置了CON_START和CON_STOP位(寄存器配置沖突)。
(2)日志:timeout, ipd: 0x10, state: 1
?問(wèn)題:控制器正常,但CPU無(wú)法響應(yīng)I2C中斷;
?原因:
a.CPU0被阻塞(RK I2C中斷默認(rèn)在CPU0,用cat /proc/interrupts查看);
b.I2C中斷位被關(guān)閉(檢查中斷使能寄存器)。
(3)日志:timeout, ipd: 0x80, state: 1或scl was hold by slave
?問(wèn)題:SCL被從設(shè)備拉低(總線卡死);
?排查方法:
a.排除法:若外設(shè)少,逐個(gè)斷開(kāi)外設(shè),復(fù)現(xiàn)問(wèn)題定位“元兇”;
b.硬件檢測(cè):在SCL總線串入電阻(如220Ω,約上拉電阻的1/20),測(cè)量電阻兩端壓差——電壓更低的一端對(duì)應(yīng)拉低SDA/SCL的設(shè)備;
c.波形驗(yàn)證:用示波器抓取波形,對(duì)比不同從設(shè)備的低電平,與故障時(shí)的低電平匹配的即為問(wèn)題設(shè)備。
3.終極Debug:抓取I2C波形
若以上方法無(wú)法解決,最有效的方式是抓取故障時(shí)的I2C波形:
1.在代碼中“卡住CPU”:在出錯(cuò)位置加while(1),避免發(fā)起新的I2C任務(wù);
2.用示波器測(cè)量SDA和SCL引腳:觀察是否有起始信號(hào)、地址字節(jié)、ACK信號(hào);
3.對(duì)比協(xié)議要求:若波形缺失(如無(wú)起始信號(hào)),檢查控制器配置;若有NACK,檢查從設(shè)備地址或狀態(tài)。
四、RK I2C開(kāi)發(fā)知識(shí)腦圖
最后,用一張腦圖總結(jié)全文核心,方便大家收藏回顧:

寫(xiě)在最后
RK平臺(tái)的I2C開(kāi)發(fā),核心是“理解協(xié)議+區(qū)分驅(qū)動(dòng)+重視時(shí)序”——只要掌握了硬件原理和數(shù)據(jù)幀規(guī)則,再結(jié)合官方文檔配置驅(qū)動(dòng)、排查問(wèn)題,就能快速搞定絕大多數(shù)場(chǎng)景。
如果大家在開(kāi)發(fā)中遇到具體問(wèn)題,歡迎在評(píng)論區(qū)交流;也可以收藏本文,遇到問(wèn)題時(shí)對(duì)照腦圖和排查步驟,效率會(huì)更高!
-
I2C總線
+關(guān)注
關(guān)注
8文章
422瀏覽量
63437 -
瑞芯微
+關(guān)注
關(guān)注
27文章
805瀏覽量
54524 -
音頻芯片
+關(guān)注
關(guān)注
3文章
165瀏覽量
18885
發(fā)布評(píng)論請(qǐng)先 登錄
ElfBoard技術(shù)貼|如何在【RK3588】ELF 2開(kāi)發(fā)板實(shí)現(xiàn)I2C功能復(fù)用
Firefly-RK3288--I2C主板的使用介紹
Firefly-RK3128主板I2C控制器
STM32的硬件I2C有BUG嗎?
嵌入式內(nèi)核及驅(qū)動(dòng)開(kāi)發(fā)-09IIC子系統(tǒng)框架使用(I2C協(xié)議和時(shí)序,I2C驅(qū)動(dòng)框架,I2C從設(shè)備驅(qū)動(dòng)開(kāi)發(fā),MPU6050硬件連接
STM32F103硬件I2C Slave
硬件I2C與模擬I2C
再談I2C,硬件問(wèn)題匯總及死鎖解決辦法
I2C debug出現(xiàn)問(wèn)題怎么解決
OpenHarmony:如何使用HDF平臺(tái)驅(qū)動(dòng)控制I2C
GD32 MCU硬件I2C不可靠不如軟件I2C?來(lái)看看紅楓派開(kāi)發(fā)版的硬件I2C驅(qū)動(dòng)如何做到穩(wěn)得一批
RK平臺(tái)Linux IOMMU開(kāi)發(fā):從原理到實(shí)戰(zhàn)
RK3576平臺(tái)PCA9548 I2C開(kāi)關(guān)設(shè)備樹(shù)配置與生效全解析
RK平臺(tái)I2C開(kāi)發(fā):從硬件原理到實(shí)戰(zhàn)排查
評(píng)論