試驗目的:認識計算機并口和I2C總線,用計算機并口模擬I2C總線,最后,以24CL02為例,完成對I2C EEPROM的讀寫操作。
試驗器材:一臺裝有 Tubor C 2.0 的計算機、一條25針并口電纜(看圖1插頭可要選對了)、自制的用于插入EEPROM芯片的適配器(圖2)、一片 EEPROM 如HT24LC02或AT24C02等。
試驗前的準備知識:
一、I2C總線:i2c總線是 Philips 公司首先推出的一種兩線制串行傳輸總線。它由一根數據線(SDA)和一根時鐘線(SDL)組成。i2c總線的數據傳輸過程如圖3所示,基本過程為:
1、主機發出開始信號。
2、主機接著送出1字節的從機地址信息,其中最低位為讀寫控制碼(1為讀、0為寫),高7位為從機器件地址代碼。
3、從機發出認可信號。
4、主機開始發送信息,每發完一字節后,從機發出認可信號給主機。
5、主機發出停止信號。
I2C總線上各信號的具體說明:
開始信號:在時鐘線(SCL)為高電平其間,數據線(SDA)由高變低,將產生一個開始信號。
停止信號:在時鐘線(SCL)為高電平其間,數據線(SDA)由低變高,將產生一個停止信號。
應答信號:既認可信號,主機寫從機時每寫完一字節,如果正確從機將在下一個時鐘周期將數據線(SDA)拉低,以告訴主機操作有效。在主機讀從機時正確讀完一字節后,主機在下一個時鐘周期同樣也要將數據線(SDA)拉低,發出認可信號,告訴從機所發數據已經收妥。(注:讀從機時主機在最后1字節數據接收完以后不發應答,直接發停止信號)。
注意:在I2C通信過程中,所有的數據改變都必須在時鐘線SCL為低電平時改變,在時鐘線SCL為高電平時必須保
持數據SDA信號的穩定,任何在時鐘線為高電平時數據線上的電平改變都被認為是起始或停止信號。
下面以24LC02為例,對幾個主要工作時序做詳細說明。
24LC02的控制字(節)格式(圖4):發送時緊跟開始信號后的4位是器件選擇位,通常為‘1010’,它和后面的3位器件地址碼(由24LC02的A0、A1、A2上的電平決定)共同構成了7位的從機地址。從機地址后緊跟1位讀/寫控制位,該位為1表示讀,為0表示寫。圖中最后1位是應答位,這里它由從機給出。
24LC02寫時序(圖⑤):主機發送開始信號,接著發出從機地址和寫控制碼,主機接收從機發出的應答,主機發送1字節的地址信息,主機接收應答,主機寫1字節數據到從機,主機接收應答,主機發出停止信號。寫操作完成,1字節數據被寫入24LC02內指定地址。24LC02提供一種頁寫的方式,每次最多可連續寫入8字節數據再發送停止信號,當寫入數據多時可采用這種方式以加快速度。
24LCO2隨機讀時序(圖⑥):主機發送開始信號,接著發送從機地址和寫控制碼,主機接收應答,主機發送1字節的的地址信息,主機接收應答(注意:前面的時序為寫操作,目的把起始地址寫入24CL02緩沖中,以告知隨后的讀操作從哪個地址開始,這個步驟在讀時序中有時被稱為“偽寫”),主機發送開始信號,主機發送從機地址和讀控制碼,主機接收應答,主機讀取1字節數據,主機不發應答,主機發送停止信號。完成上面步驟,主機已從24LCO2中讀出指定地址內1字節數據。
24LC02讀時序(圖⑦):如圖⑦所示,與隨機讀時序相比,主機沒有給從機寫入起始地址,所以這種方式用于讀取當前地址內的數據。另,24LC02也可以采用連續讀的方式(見圖⑧),這樣每次最多可以讀取8字節。注意:連續讀時每讀完1字節后主機要發應答給主機,但在最后1字節后(即停止信號前)主機不發應答。
數據線(SDA)上的信號:讀時,從機在SCL的上升沿將數據放到SDA上,寫時,遇到SCL的上升沿,從機將接收SDA上的數據。
二、并行口:它包含了一批輸入/輸出端口,在PC機上它是一個25針的 D 型插口,一般用于連接打印機,因此有時也稱為打印口。
并行口信號:以打印機為例,并口I/0信號中有些是專門用來把數據傳送給打印機的,有些則是用來對傳送過程給以控制的,還有將打印機的各種工作狀態信息發送給CPU的。詳細如表1所示。表中所有信號用低電平(0V)表示邏輯0,用高電平(5V)表示邏輯1(電壓都是相對于18-25腳上的接地電勢而言),凡是前綴用符號‘-’表示的信號均指低電平為現役信號。
可以看出,編號為2-9針腳上的信號是傳遞實際數的信號,而其它線上的信號則是用在對打印機進行初始化處理和對打印機動作進行同步上。下面簡單介紹一下打印過程以加深對并口的理解,CPU通過并口中16和17腳上的信號來選擇打印機,并給以初始化處理。且用13腳上的信號給以響應。在打印機已準備好接收數據時,就將11腳置為低電平(表示可以接收),CPU把數據放到并口的數據線(2-9)上,并通過1腳上的選通信號對打印機的數據進行選通。打印機在收到選通信號時將忙信號(11)置為高電平,表示正在接收數據。數據接收完畢后,打印機在短時間內把現役的確認信號(10腳低電平)發送出去,然后再把忙信號(11)置成低電平(既非現役)并準備好接收更多數據。
并行口硬件:并口行現在通常被集成在系統板上,25針插口上的信號可通過數據鎖存器、打印狀態和打印機控制三個寄存器(也就是三個輸入/輸出端口)進行程序設計和控制。計算機系統中通常有多個并行端口,表2列出了它們在輸入/輸出系統中的地址。需要注意的是這些地址是由系統 BIOS 給出的,并不是硬件的物理地址,所以可以通過設置 BIOS 來改變當前端口的配置。
端口寄存器:表3列出了并行端口寄存器各位的意義。這些信號也是在外部插頭上出現的主要信號。不過寄存器中有些信號的極性與插頭上相應信號的極性正好相反。比如,選通信號在插頭上為低電平時,信號是現役的,而在打印機控制寄存器中則為高電平是現役的。
通過上面的準備知識,應有以下理解:1、可以把并口的25個針腳理解為三個寄存器對外的映射,除了傳送8位實際數據的引腳外,還有用于控制打印機和取得打印機當前狀態的引腳,這些引腳有的為輸入,有的為輸出,因此可以像用單片機I/O一樣靈活的運用它們。2、I2C總線在通訊過程中,數據線(SDA)上的信號流動方向是不斷變化的,如:主機正在寫24LC02時,SDA的方向為主機到從機,SDA為輸出,寫完一字節后,要接收應答時,SDA的方向變為從機到主機,SDA為輸入(對于主機)。3、并口模擬I2C總線,其實是用軟件控制并口的 I/O 來輸入輸出 I2C 總線需要的高、低電平信號,從而產生I2C總線的各種時序。
制作試驗電路:
試驗用的電路如圖⑨,分析如后:P1的4-7腳并聯(為了加大輸出電流),接IC1的VCC端,為IC1供電。P1的2腳接IC1的SCL端,用做I2C總線的串行時鐘輸出。因I2C總線中數據線(SDA)在不同的時間可能是輸入也可能是輸出,所以接在IC1 SDA端上的信號也有兩路,輸出時,P1 3腳輸出低電平T1導通,SDA被置為低電平,P1 3 腳輸出高電平T1截止,因 R1的作用SDA被置為高電平。輸入時,P1 通過判斷 13 腳上的電平高低,來讀取SDA上的數據。要注意的是用于輸入時T1必須是截止的,以免SDA被箝位。
這個電路具有通用性,24C01、24CO2、24LC64等24系列的I2C EEPROM 均可按這個電路與并口連接,所以不妨把它當作實用工具來認真制作。先找一條并口電纜,看電纜插頭的形式,找一個與之配套的25針插座,購買一個撥動式的IC插座,將IC插座按圖中IC1的連接方法與找來的并口插座相連,然后按圖將T1、R1、C1、直接焊在IC插座或并口插座上,要盡量作的緊湊些。最后將電路固定在一個合適的小塑料盒內,好了,現在它是我們的試驗器材,等看過后面的內容,你會發現只要為其配上軟件,它就是一個用于讀寫I2C EEPROM 的好工具。
試驗程序編寫:
和其它高級語言相比,C 更適合于對硬件編程。本試驗所用的程序就是在 Tubor C 2.0 環境下編譯通過的。
一、C 語言相關:對本試驗較關鍵的幾個函數和運算。
讀端口函數 inprotb(); 可從指定的輸入端口讀入一個字節,并返回這個字節,用法為:inprotb(端口號或端口地址);例如:b=inprotb(379H);由于379H為‘打印機狀態’寄存器的地址,因此執行后變量 b中將存放由函數讀取的 379H 的值。
寫端口函數 outprotb(); 可寫一字節數據到指定的輸出端口,用法為:outprotb(端口地址,整型數);例如:outprotb(378H,1);由于端口地址378H為并口的‘數據鎖存器’地址,因此執行后將在并口的 2 腳輸出高電平,3-9腳輸出低電平。
位運算:位運算的對象只能是整型或字符型數據,本試驗程序中用到了兩種位運算。左移運算(<<):運算符左邊是位移對象,右邊是整形表達式,代表左移的位數,左移時,右端補 0;左端移出的部分舍棄。右移運算(>>):運算符的使用方法與左移運算符一樣,所不同的是移位方向相反。右移時,右端(低位)移出的二進制數舍棄,左端(高位)移入的二進制數分兩種情況:對于無符號整數和正整數,高位補 0,對于負整數,高位補 1。舉例:假設b和c為字符型變量,并且 b 已賦初值,用二進制表示時 b 的值為 01110110 ;現在若要求的 b 的第 3 位的二進制數是 1 ,還是 0 ,可暫將 b 的值賦給變量 c (c=b;),再對 c 進行位移運算,先將 c 右移 2 位(c=c>>2;),再左移 7 位(c=c<<7;),然后用程序判斷 c 的值是否為 0,為0則所求位的二進制數為 0,否則為所求位的二進制數為 1。經過位移 c 的值變為‘10000000’,而不是0,因此可以判斷 b 的第 3 位中的二進制數是 1。后面的試驗程序就是用這種方法來接收應答和讀取SDA上的數據的。
二、編程前的分析:現在從編程的角度對圖⑨ 的電路再次分析。參見表1、表2、表3。現在計算機上的并口通常被默認的設置成端口2,既數據鎖存器地址為378H的端口。
并行口(P1)13腳:它是一個輸入端,是‘打印機狀態’寄存器(見表2、表3)中的位 4。‘打印機狀態’寄存器地址為379H,可以用 C 語言中的 inprotb() 函數來讀取379H的值,然后通過位運算即可獲得當前P1 13腳(IC1的SDA端)的電平狀態。注意:在讀端口時,要確認T1是截止的。
并行口(P1)2 腳:它是‘數據鎖存’寄存器中的位 0,在這里作為一個輸出端。‘數據鎖存’寄存器的地址為378H,可以用 C 語言中的 outprotb() 函數給378H的位 0 寫入1或0,,從而模擬出 I2C 總線中SCL上的高、低電平。這里需要注意的是,從2腳輸出時,用函數寫數據鎖存器每次只能改變位 0 的狀態,而不能影響到其它位的狀態。
并行口(P1)3 腳:它是‘數據鎖存’寄存器中的位 1,在這里作為輸出端與T1基極相連,可以用 C 語言中的 outprotb() 函數給‘數據鎖存’378H的位 1 寫入1或0,從而控制 T1 的導通和截止,配合 R1 的作用,模擬出I2C時鐘線SDA上的高、低電平信號。 3 腳輸出低電平將使 T1 導通,SDA既被置為低電平,3 腳輸出高電平 T1 截止,由R1將SDA上拉為高電平。要注意操作這一位時不能影響到其它位。
并行口的 4-7 腳:它們分別是‘數據鎖存’寄存器中的位2、位3、位4和位5,這4 位全部作為輸出端接在IC1的VCC上,通過寫端口函數將它們全部寫入1(既都輸出高電平),用于給IC1提供電源。注意,因這4位是作為電源使用的,必須保證這4位的值始終為1 ,所以每次寫378H時要特別注意。這4個引腳是并在一起的,其中若有1位被寫成0,就會因高低電平抵消而中斷IC1的電源使操作失敗,甚至可能會損壞并口。
三、編程:通過上面分析,要用并口來模擬I2C總線來讀寫 24LC02 ,程序需有以下幾部分。
發送I2C開始信號:用 outprotb() 函數向378H寫入16進制數“0XFF”(即2-9腳全部輸出高電平),SCL和SDA都為高電平,延時一段時間后,向378H寫入“0XFD”(其它腳狀態不變,只是將位 1 置為低電平),使SDA由高電平變為低電平,即產生了I2C的開始信號。最后將在378H中寫入“0XFC”(即其它腳不變,將位0和位1置為低電平)使SCL為低電平,以完成一個時鐘,也為后面的讀寫作準備。
發送I2C停止信號:I2C的停止信號是在SCL為高時,SDA由低變高。程序可按下面步驟來寫,用寫端口函數向378H寫入“0XFC”,使SCL和SDA為低電平,延時一段時間,向378H寫入“0XFD”,使SCL變為高電平,SDA為低電平,延時,向378H寫入“0XFF”SCL保持不變,使SDA由原來的低電平變為高電平,即產生了一個停止信號。延時一段時間,最后向378H寫入“0XFE”,使SCL為低電平,以完成一個時鐘。
發送數據:先把要發送的數據放在一個變量里,然后按位發送。方法為,通過位運算求得欲發送位的值(1或0),然后用寫端口函數模擬出SCL和SDA,并按I2C的寫時序將一位數據發送出去,程序中可用while循環語句來控制發送的位數和字節數。
主機(并口)發送應答:I2C總線,主機發送應答用在連續讀時序中,每讀取一字節(8位)后,主機使SDA保持一個時鐘周期的低電平。可以用寫端口函數先將SDA、SCL置為 0,然后將SCL變高,SDA保持低電平,一個應答信號既被發送,最后將SCL置低,完成一個時鐘。
接收數據:并口讀取I2C總線的數據時,必須讓 T1截止,使用并口的13腳來接收SDA上的數據。可按下面步驟操作,先用寫端口函數使SCL為低電平,同時在并口3腳輸出高電平使 T1 截止。然后用寫端口函數單獨將SCL置1,其它位保持不變,模擬出時鐘上升沿,IC1 將把一位數據放到數據線SDA上,用讀端口函數 inprotb() 讀取‘打印機狀態’寄存器379H當前的值,將結果賦值給一個變量,然后對這個變量進行先右移4位,再左移7位的運算(用以獲得13 腳電平狀態,即打印機狀態寄存器的位 4 的值),判斷該變量是否為0,最后將判斷結果移入另外的一個用于存放‘已讀取數據’的變量中,完成讀取一位數據的操作,用寫端口函數使SCL為低電平,在下一個SCL的上升沿,同樣用上面的方法將一位數據加入‘已讀取數據’變量中。可用while循環控制要讀的位數和字節數。注意:以上過程都是在 T1 為截止態時進行的。
主機(并口)接收應答:接收應答用于寫 I2C 時,每寫一字節數據到從機后,如果操作成功,從機在下一個時鐘內使 SDA 為低。主機查詢應答可以加強操作的可靠性。接收應答和上面說的接收數據大致相同,只是僅接收一位數據并且不存儲,直接判斷其值是否為 0,不為 0 時(即沒有收到應答)轉錯誤處理程序,為 0則繼續后面的操作。在實際編程時將這個步驟合并到寫I2C的操作中。
有關延時:I2C器件對SDA和SCL上的高、低電平信號需保持的時間是有規定的。如:開始信號的高、低電平要保持多長時間,數據信號的高、低電平最低要保持多長時間等。不同的器件對這個時間有不同的規定。查找24LO02的數據手冊,可以知道,它在不同的電壓下對各信號要保持的時間分別在幾百納秒到幾微秒之間。這個時間也體現了I2C器件的讀寫速度。因為計算機的速度不同,要用計算機并口來模擬I2C很難將這個時間精確到微秒。為了能夠在不同的計算機上可靠的操作I2C總線,試驗程序用了C語言的延時函數delay();這個函數能產生的最小延時為1毫秒。雖然這樣做降低了I2C的讀寫速度,但可以保證操作的可靠性。
四、用并口讀寫I2C總線的源程序:程序中把I2C的一些操作時序定義成了獨立的函數供主函數調用,這樣增加了程序的靈活性,也方便對程序的修改和擴充。
源程序如下:
#include "stdio.h"
#include "dos.h"
#include "conio.h"
/***********void i2cstart()***********/
void i2cstart(){
outportb(0x378,0xff);/*scl 1, sda 1*/
delay(1);/**/
outportb(0x378,0xfd);/*scl 1, sda 0*/
delay(1);/**/
outportb(0x378,0xfc);/*scl 0, sda 0*/
delay(1);
}
/***********void i2cstop()***********/
void i2cstop(){
outportb(0x378,0xfc);/*scl 0, sda 0*/
delay(1);/**/
outportb(0x378,0xfd);/*scl 1, sda 0*/
delay(1);/***/
outportb(0x378,0xff);/*scl 1, sda 1*/
delay(1);/**/
outportb(0x378,0xfe);/*scl 0, sad 1*/
}
/***********writebyte()***********/
writebyte(char s){
short int a=7;
char d,e;
outportb(0x378,0xfc);/*scl 0, sda 0*/
delay(1);/***/
while(a>=0){
d=s>>a; d=d<<7;
if (d=='\x80')/*****"1"***/
{
outportb(0x378,0xfe);/*scl 0, sda 1*/
delay(1);/***/
outportb(0x378,0xff);/*scl 1, sda 1*/
}
else
{
outportb(0x378,0xfc);/*scl 0, sda 0*/
delay(1);/***/
outportb(0x378,0xfd);/*scl 1, sda 0*/
}
a=(a-1);
}
/**ask**/
delay(1);/***/
outportb(0x378,0xfe);/*scl 0, sda 1*/
delay(1);/***/
outportb(0x378,0xff);/*scl 1, sda 1*/
delay(1);/***/
outportb(0x378,0xfc);/*scl 0, sda 1*/
delay(1);/***/
e=inportb(0x379); d=e>>4; d=d<<7;
if (d=='\x0') return 0;
else
printf("not acknowledge!\n");
return 1;
}
/***********readbyte()***************/
char readbyte(){
unsigned short a=8;
char d,e,f='\x0';
while(a>0){
delay(1);/***/
outportb(0x378,0xfe);/*scl 0, sda 1*/
delay(1);/***/
outportb(0x378,0xff);/*scl 1, sda 1*/
delay(1);/***/
e=inportb(0x379); d=e>>4; d=d<<7;
if(d=='\x80') d='\x1';
f=f<<1; f=(f+d); a=(a-1);
outportb(0x378,0xfe);/*scl 0, sda 1*/
delay(1);/***/
}
return f;
}
/************mainask()*****************/
mainask(){
delay(1);/**/
outportb(0x378,0xfc);/*scl 0, sda 0*/
delay(1);/**/
outportb(0x378,0xfd);/*scl 1, sda 0*/
delay(1);
outportb(0x378,0xfc);/*scl 0, sda 0*/
}
/*************************************/
main(){
unsigned short a,b,c,g;
char d,e,f;
textcolor(2);
clrscr();
printf("press 'r' or 'w' :");
scanf("%c",&f);
if(f=='w')
{
/************ W 256 BYTES ****/
e='\x0'; c=32; /* 24lc02: 32=256/8 */
while(c>0){
i2cstart();/*****start****/
writebyte('\xa0');/***send contbyte***/
writebyte(e);/***send start address***/
/************W 8 bytes****/
b=8; d='\x0'; /* num */
while(b>0){
if ((writebyte(d))==1) exit(0);/***send a byte***/
b=(b-1);d=(d+1);
}
i2cstop();
delay(40); /****writer delay****/
c=(c-1);e=(e+8);
}
printf("write ok!!\n"); exit(0);
}
if(f=='r')
{
/****** read ***********************************/
printf("please import start address:");
scanf("%x",&b);
a=(256-b); c=(a%8); a=(a/8);
while(a>0){
g=8;
i2cstart();/*****start****/
writebyte('\xa0');/***send contbyte***/
d=(char)b;/****/
writebyte(d);/***send start address***/
i2cstart();/*****start****/
writebyte('\xa1');/***send contbyte***/
while(g>0){
d=readbyte();
if(d=='\xff')
printf(" FF");
else
printf(" %.2X",d);
g=(g-1); if(g>0) mainask();
b=(b+1);
}
i2cstop();
a=(a-1);
}
while(c>0){
i2cstart();/*****start****/
writebyte('\xa0');/***send contbyte***/
d=(char)b;/****/
writebyte(d);/***send start address***/
i2cstart();/*****start****/
writebyte('\xa1');/***send contbyte***/
d=readbyte();
if(d=='\xff')
printf(" FF");
else
printf(" %.2X",d);
c=(c-1); if(c>0) mainask();
}
printf("\nREAD OK!\n");
exit(0);
}
else {printf("\nCommand Error!!!"); exit(0);}
}
以上程序是在 Tubor C 2.0 環境下編譯通過的,運行結果如后:程序先在屏幕上提示“press 'r' or 'w' :”'r'為讀24LC02,'w'為寫。如果輸入'r'并按回車,程序將會提示:“please import start address:”這時請按16進制的格式輸入要讀的起始地址,然后回車,程序將會從該地址開始把后面的所有數據讀出并按大寫 16進制的格式在屏幕上打印出來。完成后提示“READ OK!”并結束程序。如果在程序開始時輸入的是'w',程序將從24LC02的00H地址開始,按“00 01 02 03 04 05 06 07”的格式,每8字節循環一次,直到寫滿24LC02所有的存儲空間,即256個字節。寫的過程中如果出現錯誤將提示“not acknowledge!”,如果操作順利完成,程序將提示“write ok!!”并結束運行。在程序開始時如果輸入的不是'r'也不是'w',程序將提示“Command Error!!!”并退出運行。
這個程序雖然只是個簡單的演示,但卻是并口模擬I2C的最關鍵的核心部分,只要給它加些改動并配上簡單的界面,即可以成為一個很實用的并口 I2C 總線讀寫程序。
評論
查看更多