1.什么是IIC
IIC 總線是一種串行數據總線,只有二根信號線,一根是雙向的數據線SDA,另一根是時鍾線SCL,兩條線可以掛多個設備。 IIC設備(絕大多數)里有個固化的地址,只有在兩條線上傳輸的值等於IIC設備的固化地址時,其才會作出響應。通常我們為了方便把IIC設備分為主設備和從設備,基本上誰控制時鍾線(即控制SCL的電平高低變換)誰就是主設備。
2.IIC物理層介紹
IIC通訊的主機和從機之間只用連接2根線,一個是SCL(控制時鍾),一個是SDA(傳遞數據,雙向)。SCL和SDA總線通過上拉電阻接到電源。上拉電阻4.7K-10K。
圖1 IIC協議物理層連接圖
需要注意的幾點:很重要
1.總線通過上拉電阻接到電源。當 IIC 設備空閑時,會輸出高阻態,而當所有設備都空閑,都輸出高阻態時,由上拉電阻把總線拉成高電平。高阻態的意思就是相當於斷路(IIC設備沒有和總線連接着)。可以看到SCL、SDA空閑是都是高電平。
2. 每個連接到總線的從機設備都有一個獨立的地址(有些是出廠時定死的,沒法改變),主機可以利用這個地址進行指定訪問從機。主機先廣播一個從機地址,總線上的從機都會收到這個地址,收到后和自身地址對比,若不同,忽略,若相同則給主機發送應答信號,確立連接。
3.總線上的各個設備,主機和從機。都采用開漏模式與總線相連。目的是可以輸出高阻態。總線上的設備通過把SCL、SDA兩個總線在不同時間拉低拉高,來表示數據0、1.。設備想要輸出0時,則設備接地,總線被拉低。當設備想輸出1時,不能直接輸出高電平(會導致設備之間短路),而是輸出高阻態(斷路),讓總線上的上拉電阻去把總線拉高。
4.SCL線的時鍾同步:SCL由於具有線“與”的邏輯功能,即只要有一個節點發送低電平時,總線上就表現為低電平。當所有的節點都發送高電平時,總線才能表現為高電平。正是由於線“與”邏輯功能的原理,當多個節點同時發送時鍾信號時,在總線上表現的是統一的時鍾信號。
5.SDA線的仲裁:SDA線的仲裁也是建立在總線具有線“與”邏輯功能的原理上的。節點在發送1位數據后,比較總線上所呈現的數據與自己發送的是否一致。是,繼續發送;否則,退出競爭。SDA線的仲裁可以保證I2C總線系統在多個主節點同時企圖控制總線時通信正常進行並且數據不丟失。總線系統通過仲裁只允許一個主節點可以繼續占據總線。
3.IIC協議層介紹
知道了IIC的物理層連接后,就是IIC的協議層。規定了IIC設備間通信的幾個基本信號:起始信號、停止信號、讀、寫、應答、非應答。
3.0 幾個知識點:
(1)下降沿:信號從高電平到低電平的躍變動作
(2)上升沿:信號從低電平到高電平的躍變動作
(3)釋放總線:讓總線為高電平(通過高阻態,讓上拉電阻拉高),線與特性。
SDA=0時,SDA腳在IC內部被直接接在GND上,它只能是低電平,外部器件也無法把SDA腳拉高,這叫拉低。
SDA=1時,SDA腳為高阻態,這時SDA可以被別的器件拉低或者拉高
3.1 起始信號和停止信號
圖2 IIC開始信號和停止信號的定義
(1)主機控制時鍾線。當開始發送開始信號時,SCL、SDA必須處於空閑狀態(都為高)。當SCL為高時,讓SDA產生一個下降沿跳變。即代表開始信號。
(2)主機控制時鍾線。當SCL為高時,讓SDA產生一個上升沿跳變。即代表停止信號。當產生停止信號后,SCL、SDA必須處於空閑狀態。無論讀寫方向如何。開始信號和停 止信號都是由主機控制。
3.2 寫
圖3 IIC寫
(1)寫數據寫一個字節。按位寫。主機用SDA的高低電平表示一位1或0高位先寫。
(2)在IIC主機寫數據到從機的過程中,時鍾線和數據線都由主機控制。
(3)寫數據的過程是利用SCL、SDA互相配合。當SCL為高電平期間,去判斷SDA線的電平,把SDA的數據(1或0)寫入從機。這里需要注意一點。從圖3中可以看到SDA的高電平(或低電平)時間比SCL為高電平的時間長。這就意味着,在SCL被拉高時,SDA必須提前准備好數據。並且在SCL變低之前,SDA的狀態不能變。這樣保證數據的有效性。用偽代碼表示為:
SDA=1(或0) //准備數據(主機)
SCL=1 //拉高SCL
delay //延時
SCL=0 //拉低SCL。SDA數據有效,已被寫入從機
3.3 讀
圖4 IIC寫(從機->主機)
(0)讀和寫的時序圖一樣,只是方向不同。
(1)讀數據讀一個字節。按位讀。從機用SDA的高低電平表示一位1或0,高位先讀。
(2)在IIC主機讀數據的過程中(從機->主機),時鍾線由主機控制,數據線由從機控制。
(3)讀數據的過程是利用SCL、SDA互相配合。主機控制SCL表示什么時候讀,從機控制SDA表示主機讀的時候的數據。流程:當從機接收到主機要讀數據的命令后,從機先在SDA線上准備好數據。主機拉高SCL,延時,拉低SCL,SDA數據有效。用偽代碼表示為:
SDA=1(或0) //准備數據(從機) SCL=1 //拉高SCL(主機) delay //延時 SCL=0 //拉低SCL。SDA數據有效,已被讀入主機
3.4應答、非應答
圖5 應答和非應答時序圖
(0)先來說一下應答和非應答的時序圖,再說應答和非應答信號的作用。
(1)應答和非應答其實和上面讀寫步驟一樣,都是在SCL被拉高前,在SDA線上准備好數據。其實就是和讀寫時寫一個數據0和數據1的時序一樣。那可能會有一個疑問,應答和非應答和普通讀寫一樣,那怎么區分到底是數據還是應答非應答信號呢?
每當主機向從機發送完一個字節的數據,主機總是需要等待從機給出一個應答信號,以確認從機是否成功接收到了數據。
反之亦然,主機從從機讀取數據后也會給從機發送應答、非應答信號。
從機應答主機所需要的時鍾仍是主機提供的,應答出現在每一次主機完成8個數據位傳輸(讀或寫)后緊跟着的時鍾周期,低電平0表示應答,1表示非應答。
(2)也就是說在讀寫過程中第9個時鍾周期的數據被當作了應答、非應答的信號。因為發送數據時按字節發送的(8位)。
(3)應答和非應答可以由主機發給從機,也可以由從機發送給主機。誰發信號,誰控制SDA,但SCL始終由主機控制。
之前在學習過程中,聽到別人介紹,主機發送應答信號發送非應答信號,不發送應答信號。真的暈了。現在我總結一下,說的更清楚一些。應答和非應答都是一種信號,應答表示通信還要繼續,非應答表示通信要結束了。
看到現在應該要明白的幾點:
SCL一直由主機控制,開始和結束信號都由主機控制 SDA根據讀寫確定傳輸數據方向:主機->從機、從機->主機 SDA數據按位發送(或讀取),每發送8位,第9位被判定為應答和非應答 應答和非應答可由主機發送給從機,可以由從機發送給主機。
先從主機寫數據開始理解應答和非應答:陰影部分表示主機發送,白色部分表示從機發送。
圖6 主機寫從機
(1)主機發送開始信號S,發送從機地址+0(共8位,0表示寫方向),廣播地址,由所有從機匹配地址,匹配后給主機發送應答信號A,主機接收到從機的應答信號A后,開始寫入數據(8位),每當主機發送了8位數據后,若從機還想繼續接收則發送應答信號A,如果不想在接收,或者接收出錯,則從機發送非應答信號。一旦從機發送非應答信號,此次通信接收,則必須由主機發送停止信號P。
(2)總結:從機發應答,通信繼續,主機繼續發數據。從機發非應答,通信結束,主機不再發數據,主機接着發停止信號。釋放總線。
再從主機讀數據開始理解應答和非應答:陰影部分表示主機發送,白色部分表示從機發送。
圖6 主機讀從機
(1)主機發送開始信號S,發送從機地址+1(共8位,1表示讀方向),廣播地址,由所有從機匹配地址,匹配后給主機發送應答信號A,從機緊接着發送數據(主機在接收到應答信號時就應該開始接收數據了),每當主機接收到數據后,主機發送應答信號A,則從機繼續發數據,當主機發送非應答信號,則從機不再發送數據,主機緊接着發送停止信號P。
(2)總結:主機發應答,通信繼續,從機繼續發數據。主機發非應答,通信結束,從機不再發數據,主機接着發停止信號。釋放總線。
總結:現在已經介紹完了IIC的幾個基本信號時序,和通信邏輯。再用IIC驅動其他模塊時,使用IIC基本協議去定義具體的協議。協議一層一層封裝。
IIC的EEPROM時序圖:
4.IIC軟件模擬
使用2個普通IO引腳接SCL、SDA。引腳配置成開漏模式。
4.1開始信號
1 //產生IIC起始信號 2 void IIC_Start(void) 3 { 4 SDA_OUT(); //SDA線輸出 5 IIC_SDA=1; 6 IIC_SCL=1; 7 delay_us(4); 8 IIC_SDA=0;//SCL為高,SDA下降沿 9 delay_us(4); 10 IIC_SCL=0;//鉗住I2C總線,准備發送或接收數據 11 }
4.2停止信號
1 void IIC_Stop(void) 2 { 3 SDA_OUT();//SDA線輸出 4 IIC_SCL=0; 5 IIC_SDA=0; 6 delay_us(4); 7 IIC_SCL=1; 8 IIC_SDA=1;//SCL為高,SDA上升沿。發送I2C總線結束信號 9 delay_us(4);
4.3主機應答
1 void IIC_Ack(void) 2 { 3 IIC_SCL=0; 4 SDA_OUT(); 5 IIC_SDA=0; 6 delay_us(2); 7 IIC_SCL=1; 8 delay_us(2); 9 IIC_SCL=0; 10 }
4.4主機非應答
1 void IIC_NAck(void) 2 { 3 IIC_SCL=0; 4 SDA_OUT(); 5 IIC_SDA=1; 6 delay_us(2); 7 IIC_SCL=1; 8 delay_us(2); 9 IIC_SCL=0; 10 }
4.5主機等待從機應答或非應答
1 u8 IIC_Wait_Ack(void) 2 { 3 u8 TimeOut=0; 4 SDA_IN(); //SDA設置為輸入 5 IIC_SDA=1; 6 delay_us(1); 7 IIC_SCL=1; 8 delay_us(1); 9 while(READ_SDA) 10 { 11 TimeOut++; 12 if(TimeOut>250) 13 { 14 IIC_Stop(); 15 return 1; 16 } 17 } 18 IIC_SCL=0;//時鍾輸出0 19 return 0; 20 }
主機根據從機在SDA線上的反饋,來判斷從機是發送應答還是非應答。
4.6寫
1 void IIC_Send_Byte(u8 txBuffer) 2 { 3 u8 i; 4 SDA_OUT(); 5 IIC_SCL=0;//拉低時鍾開始數據傳輸 6 for(i=0;i<8;i++) 7 { 8 if((txBuffer&0x80)>>7) 9 IIC_SDA=1; 10 else 11 IIC_SDA=0; 12 txBuffer<<=1; 13 delay_us(2); 14 IIC_SCL=1; 15 delay_us(2); 16 IIC_SCL=0; 17 delay_us(2); 18 } 19 }
寫數據,高位先寫,按位寫。
4.7讀
1 u8 IIC_Read_Byte(void) 2 { 3 unsigned char i,rxBuffer=0; 4 SDA_IN();//SDA設置為輸入 5 for(i=0;i<8;i++ ) 6 { 7 IIC_SCL=0; 8 delay_us(2); 9 IIC_SCL=1; 10 rxBuffer<<=1; 11 if(READ_SDA) 12 { 13 rxBuffer++; 14 } 15 16 delay_us(1); 17 } 18 return rxBuffer; 19 }
5.參考博客:
https://www.cnblogs.com/CodeWorkerLiMing/p/10830778.html
https://blog.csdn.net/xx_0305401/article/details/81914528
https://www.cnblogs.com/aaronLinux/p/6218660.html
https://blog.csdn.net/qq_38410730/article/details/80312357
https://blog.csdn.net/qq_38410730/article/details/80312357
https://blog.csdn.net/weixin_44586750/article/details/88599065
https://blog.csdn.net/Xiaomo_haa/article/details/87918394
https://blog.csdn.net/Do_Not_Ask_Me/article/details/97136905
https://blog.csdn.net/zhanghuaichao/article/details/48266309
https://blog.csdn.net/Firefly_cjd/article/details/51921129#t0