I2C幾乎是嵌入系統(tǒng)中最為通用串行總線,MCU周邊的各種器件只要對速度要求不高都可以使用。優(yōu)點(diǎn)是兼容性好(幾乎所有MCU都有I2C主機(jī)控制器驳遵,沒有也可以用IO模擬)怨喘,管腳占用少津畸,芯片實(shí)現(xiàn)簡單。I2C協(xié)議雖然簡單必怜,實(shí)際使用過程中小毛病還不少肉拓。今天先來看一個(gè)平日最為常見的問題:I2C從機(jī)掛死。
很多事情不難而且經(jīng)常碰到棚赔,每次自認(rèn)為懂了但最終讓你站出來說清楚的時(shí)候卻總是不能自圓其說帝簇,很難受。所以我決定寫博客的時(shí)候就想盡量把內(nèi)容寫清楚詳細(xì)甚至是透徹靠益,希望讓每一個(gè)閱讀博文的同學(xué)都能看得明明白白丧肴,學(xué)會(huì)一點(diǎn)小知識(shí)。如果還有不清楚的可以留言交流.
I2C規(guī)范與特性
I2C是什么胧后,我相信99%的同學(xué)能點(diǎn)到這篇博文對I2C也有了一定的了解芋浮,這里附上一份I2C鼻祖NXP (前Philips半導(dǎo)體)的一份權(quán)威手冊:I2C-bus specification and user manual v.6
描述一下I2C最重要的幾個(gè)特性,為了后面描述問題和解決方案作一些鋪墊壳快。
- I2C是由兩根線(時(shí)鐘SCL + 數(shù)據(jù)SDA)組成的多主多從串行同步通信總線纸巷。
- 規(guī)范要求接入I2C的器件,SCL時(shí)鐘和SDA數(shù)據(jù)線都必須是雙向開漏結(jié)構(gòu)的眶痰,通過總線上的上拉電阻拉到邏輯高電平瘤旨。這樣的結(jié)構(gòu)可以實(shí)現(xiàn)線與(&)功能。
-
一般情況下I2C的SDA只有在SCL為低電平的時(shí)候才能改變竖伯,為高電平的時(shí)候需要保持存哲。對應(yīng)到芯片設(shè)計(jì)上則是上升沿采樣,下降沿變化七婴。
-
兩個(gè)例外情況由主機(jī)發(fā)出的總線起始條件START(SCL為高時(shí)SDA由高變低)和停止條件STOP(SCL為高時(shí)SDA由低變高)
掛死 = 掛了 + 死機(jī)
掛死這個(gè)詞應(yīng)該來源于英文hangs : To cause (a computer system) to halt so that input devices, such as the keyboard or the mouse, do not function.
前面提到因?yàn)?strong>線與&結(jié)構(gòu)祟偷,是I2C總線設(shè)計(jì)上最關(guān)鍵的特征,用了這種結(jié)構(gòu)才能實(shí)現(xiàn)
- 多主機(jī)仲裁同步
- 慢從機(jī)同步快主機(jī)
因?yàn)檫@個(gè)特性打厘,只要總線上任何一個(gè)器件拉低了SDA或者SCL修肠,其他器件都無法拉高它們,看到的都是低電平户盯。如果有器件不釋放總線嵌施,則整個(gè)總線上的通訊都會(huì)被暫停饲化,我們成為I2C bus hangs:I2C總線掛死
因?yàn)镮2C主機(jī)一般是可編程的器件,受我們控制艰管,如果主機(jī)主動(dòng)拉低了總線滓侍,我們可以通過調(diào)試代碼了解原因,也可以很方便的通過復(fù)位I2C外設(shè)或者復(fù)位芯片來退出這種狀態(tài)牲芋。而I2C從機(jī)往往不帶RESET引腳撩笆,如果掛死了總線即使整個(gè)系統(tǒng)復(fù)位都無法解除,僅重新上下電才可以恢復(fù)缸浦。很多系統(tǒng)上是不可接受的夕冲,因此我們需要更加小心的處理I2C從機(jī)掛死的情況,下面分析也是針對I2C從機(jī)掛死來寫的裂逐。
SDA掛死
先來看下哪些情況下I2C從機(jī)會(huì)需要拉低SDA線歹鱼。
- 主機(jī)向從機(jī)寫數(shù)據(jù)或地址時(shí),從機(jī)如果發(fā)出ACK應(yīng)答卜高,則會(huì)第9個(gè)CLK的期間拉低SDA
- 主機(jī)讀數(shù)據(jù)的時(shí)候弥姻,從機(jī)會(huì)在bit為0時(shí)對應(yīng)的CLK期間拉低SDA
那什么情況I2C從機(jī)又可能鉗住SDA線呢?我們先來看一個(gè)典型的I2C主機(jī)發(fā)起對某一器件地址讀操作掺涛,讀到的數(shù)據(jù)為10011000b庭敦,MSB在先也就是0x98。在圖中地址字節(jié)第9個(gè)CLK期間從機(jī)拉低SDA表示對地址進(jìn)行應(yīng)答薪缆,在返回的數(shù)據(jù)字節(jié)的第2秧廉,3,6拣帽,7疼电,8幾個(gè)CLK器件從機(jī)拉低SDA輸出邏輯0電平。
根據(jù)上面講的I2C協(xié)議SCL為高的時(shí)候减拭,SDA電平應(yīng)保持蔽豺,而等到SCL為低后(也就是下降沿后)才能發(fā)生改變。如果在上面幾個(gè)CLK的前半個(gè)周期SCL拉高后主機(jī)不再拉低呢拧粪?從機(jī)會(huì)有什么動(dòng)作修陡?YES,從機(jī)會(huì)持續(xù)拉低著SDA既们,直到見到下一個(gè)他應(yīng)該輸出高電平的下降沿濒析。
最常見的情況就是主機(jī)在通訊的過程中產(chǎn)生了復(fù)位正什。由于復(fù)位動(dòng)作通常會(huì)立刻執(zhí)行啥纸,外設(shè)狀態(tài)機(jī)都恢復(fù)到默認(rèn)狀態(tài),也就發(fā)不出完整的CLK了婴氮。那么等到主機(jī)復(fù)位完成回來后斯棒,SCL為高盾致,SDA被從機(jī)拉低。主機(jī)無法發(fā)起START起始條件荣暮,不能開始下一次與從機(jī)的通訊庭惜,這稱為SDA掛死。
要想辦法恢復(fù)穗酥,我們先得知道從機(jī)什么時(shí)候會(huì)釋放SDA护赊。由于剛剛的SCL下降沿沒有給出來,恢復(fù)總線要做的第一件事情就是在想辦法用GPIO在SCL線上模擬一個(gè)下降沿砾跃,讓從機(jī)狀態(tài)機(jī)繼續(xù)走下去骏啰。只發(fā)一個(gè)下降沿并不一定能將SDA釋放,因?yàn)槲覀儾⒉磺宄?dāng)主機(jī)復(fù)位異常發(fā)生時(shí)刻從機(jī)到底處于圖中哪一個(gè)狀態(tài)抽高,所以需要逐個(gè)CLK去探測判耕,直到見到SDA被釋放了,我們才終止并且發(fā)送STOP條件告訴從機(jī)這次坑爹的通訊結(jié)束了翘骂。
網(wǎng)上通常的傳授的方法是模擬9個(gè)連續(xù)的CLK壁熄,但是我更喜歡上面的方法,一是速度快碳竟,二是具備可確定性草丧。發(fā)送9個(gè)CLK我主要擔(dān)心從機(jī)在最后一個(gè)CLK時(shí)又拉低了SDA,還是需要用到上面的方法來釋放瞭亮。
通過模擬幾種情形來實(shí)際體會(huì)一下(從機(jī)對SDA的操作紅色表示):
如果在地址字節(jié)第9個(gè)CLK拉高后主機(jī)復(fù)位方仿。在模擬的第一個(gè)時(shí)鐘低電平期間就可以看到SDA的釋放,隨后主機(jī)先拉低SDA统翩,再模擬一個(gè)STOP結(jié)束條件仙蚜。
在數(shù)據(jù)字節(jié)第2個(gè)CLK拉高后主機(jī)復(fù)位,在第二個(gè)模擬的時(shí)鐘低電平期間才看到SDA釋放
在數(shù)據(jù)字節(jié)第6個(gè)CLK拉高后主機(jī)復(fù)位厂汗,在第三個(gè)模擬的時(shí)鐘低電平期間才看到SDA釋放
通過以上三種情況的分析委粉,想必你已經(jīng)非常清楚改如何處理了,最后附上一個(gè)程序處理流程圖:
SCL掛死
I2C從機(jī)主動(dòng)拉低SCL線在規(guī)范中是一個(gè)合法的行為娶桦,稱之為Clock Stretching(時(shí)鐘擴(kuò)展贾节,我一般叫他時(shí)鐘同步)。通常是主機(jī)請求數(shù)據(jù)( 收或者發(fā))后從機(jī)需要一些時(shí)間處理衷畦,且沒有多余Buffer可以接收接或者提供接下來的數(shù)據(jù)的時(shí)候從機(jī)則會(huì)拉低SCL一段時(shí)間直到有新的數(shù)據(jù)準(zhǔn)備好栗涂。
SCL掛死(也就是前面所說一直拉低SCL)這種情況在標(biāo)準(zhǔn)I2C從器件上基本不會(huì)出現(xiàn),因?yàn)橹灰酒€在正常工作buffer總算有準(zhǔn)備好的時(shí)候祈争,自然就就釋放SCL了斤程。往往是使用用戶使用MCU作為I2C從機(jī)時(shí),程序設(shè)計(jì)上的問題導(dǎo)致MCU無法讀取&填充buffer而導(dǎo)致菩混,重點(diǎn)分析MCU I2C中斷服務(wù)程序忿墅。
- I2C中斷服務(wù)程序被意外屏蔽
- 中斷服務(wù)程序中陷入了一些標(biāo)志位查詢的
while(flag != xxx)
死循環(huán) - I2C功能系統(tǒng)被意外禁止