|
12.2.I2C尋址模式 上一節介紹的是I2C每一位信號的時序流程,而I2C通信在字節級的傳輸中,也有固定的時序要求。I2C通信的起始信號(Start)后,首先要發送一個從機的地址,這個地址一共有7位,緊跟著的第8位是數據方向位(R/W),“0”表示接下來要發送數據(寫),‘“1”表示接下來是請求數據(讀)。 打電話的時候,當撥通電話,接聽方撿起電話肯定要回一個“喂”,這就是告訴撥電話的人,這邊有人了。同理,這個第九位ACK實際上起到的就是這樣一個作用。當發送完了這7位地址和1位方向后,如果發送的這個地址確實存在,那么這個地址的器件應該回應一個ACK(拉低SDA即輸出“0”),如果不存在,就沒“人”回應NACK(SDA將保持高電平即“1”)。 寫一個簡單的程序,訪問一下Kingst51開發板上的EEPROM的地址,另外再寫一個不存在的地址,看看它們是否能回一個ACK,來了解和確認一下這個問題。 Kingst51開發板上的EEPROM器件型號是24C02,在24C02的數據手冊3.6節中可查到,24C02的7位地址中,其中高4位是0b1010,低3位的地址取決于具體電路的設計,由芯片的A2、A1、A0這3個引腳的實際電平決定,來看一下電路圖,如圖12-4所示。
12-4.png (32.25 KB, 下載次數: 0)
下載附件
2026-4-28 14:41 上傳
圖12-4 24C02原理圖 從圖12-4可以看出,A2、A1、A0都是接的GND,也就是說都是0,因此24C02的7位地址實際上是二進制的0b1010000,也就是0x50。用I2C的協議來尋址0x50,另外再尋址一個不存在的地址0x62,尋址完畢后,通過邏輯分析儀觀察一下兩個地址是否回復ACK。 /*****************************main.c文件程序源代碼******************************/ #include <reg52.h> #include <intrins.h> #define I2CDelay() {_nop_();_nop_();_nop_();_nop_();} sbit I2C_SCL = P3^7; sbit I2C_SDA = P3^6; bit I2CAddressing(unsigned char addr); void main() { I2CAddressing(0x50); //查詢地址為0x50的器件 I2CAddressing(0x62); //查詢地址為0x62的器件 while (1); } /* 產生總線起始信號 */ void I2CStart() { I2C_SDA = 1; //首先確保SDA、SCL都是高電平 I2C_SCL = 1; I2CDelay(); I2C_SDA = 0; //先拉低SDA I2CDelay(); I2C_SCL = 0; //再拉低SCL } /* 產生總線停止信號 */ void I2CStop() { I2C_SCL = 0; //首先確保SDA、SCL都是低電平 I2C_SDA = 0; I2CDelay(); I2C_SCL = 1; //先拉高SCL I2CDelay(); I2C_SDA = 1; //再拉高SDA I2CDelay(); } /* I2C總線寫操作,dat-待寫入字節,返回值-從機應答位的值 */ bit I2CWrite(unsigned char dat) { bit ack; //用于暫存應答位的值 unsigned char mask; //用于探測字節內某一位值的掩碼變量 for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行 { if ((mask&dat) == 0) //該位的值輸出到SDA上 I2C_SDA = 0; else I2C_SDA = 1; I2CDelay(); I2C_SCL = 1; //拉高SCL I2CDelay(); I2C_SCL = 0; //再拉低SCL,完成一個位周期 } I2C_SDA = 1; //8位數據發送完后,主機釋放SDA,以檢測從機應答 I2CDelay(); I2C_SCL = 1; //拉高SCL ack = I2C_SDA; //讀取此時的SDA值,即為從機的應答值 I2CDelay(); I2C_SCL = 0; //再拉低SCL完成應答位,并保持住總線 return ack; //返回從機應答值 } /* I2C尋址函數,即檢查地址為addr的器件是否存在,返回值-從器件應答值 */ bit I2CAddressing(unsigned char addr) { bit ack; I2CStart(); //產生起始位,即啟動一次總線操作 ack = I2CWrite(addr<<1); //器件地址需左移一位,因尋址命令的最低位 //為讀寫位,用于表示之后的操作是讀或寫 I2CStop(); //不需進行后續讀寫,而直接停止本次總線操作 return ack; } 前面的章節中已經提到利用庫函數_nop_()可以進行精確延時,一個_nop_()的時間就是一個機器周期,這個庫函數包含在intrins.h這個文件中,如果要使用這個庫函數,只需要在程序最開始,和包含reg52.h一樣,include<intrins.h>之后,程序中就可以使用這個庫函數了。 還有一點要提一下,I2C通信分為低速模式100kbit/s、快速模式400kbit/s和高速模式3.4Mbit/s。因為所有的I2C器件都支持低速,但卻未必支持另外兩種速度,所以作為通用的I2C程序選擇100k這個速率,也就是說實際程序產生的時序必須小于等于100k的時序參數,很明顯也就是要求SCL的高低電平持續時間都不短于5us,因此在時序函數中通過插入I2CDelay()這個總線延時函數(它實際上就是4個NOP指令,用define在文件開頭做了定義),加上改變SCL值語句本身占用的至少一個周期,來達到這個速度限制。如果以后需要提高速度,那么只需要減小這里的總線延時時間即可。 此外學習一個發送數據的技巧,就是I2C通信時如何將一個字節的數據發送出去。注意函數I2CWrite中,用的for循環的技巧。for (mask=0x80; mask!=0; mask>>=1),由于I2C通信是從高位開始發送數據,所以先從最高位開始,0x80和dat進行按位與運算,從而得知dat第7位是0還是1,然后右移一位,也就是變成了用0x40和dat按位與運算,得到第6位是0還是1,一直到第0位結束,最終通過if語句,把dat的8位數據依次發送了出去。 使用Kingst LA5016邏輯分析儀將抓到的波形顯示出來,并且用過I2C的協議解碼器將協議解析出來,如圖12-5所示。從圖上可以看出,第一個字節發的是0x50,回復了一個ACK;第二個字節發了一個0x62,但是出現的是NAK,說明這個地址沒有產生應答。
12-5.png (13.44 KB, 下載次數: 0)
下載附件
2026-4-28 14:41 上傳
圖12-5 邏輯分析儀抓取I2C地址 在邏輯分析儀的I2C協議設置中,有三種地址格式顯示方式,也就是目前市面上各種資料對I2C協議地址定義的方式。如圖12-6所示。
12-6.png (21.5 KB, 下載次數: 0)
下載附件
2026-4-28 14:41 上傳
圖12-6 I2C地址顯示格式 前邊講I2C發送的第一個字節是7位地址加一位讀寫位,但是有些資料直接將讀寫位歸結到I2C的地址,也有的資料將7位地址位認為是高7位,以開發板的0x50地址和0x62地址為例,即地址二進制0b1010 000,寫的時候是0b1010 0000;讀的時候是0b1010 0001。 方式1:8-bit,包含讀/寫位,0x50地址對應這種方式寫地址為0xA0;讀地址為0xA1。 方式2:8-bit,讀/寫位顯示為0,即寫地址和讀地址都是0xA0。 方式3:7-bit,本教材采用的方式,寫地址和讀地址都是0x50。
|