大家好,通過前一期的學習,我們已經對ICD2 仿真燒寫器和增強型PIC 實驗板的使用方法及學習方式有所了解與熟悉,學會了如何用單片機來控制發光管、繼電器、蜂鳴器、按鍵、數碼管、RS232 串口、步進電機、溫度傳感器等資源,體會到了學習板的易用性與易學性,看了前幾期實例,當你實驗成功后一定很興奮,很有成就感吧!現在我們就趁熱打鐵,再向上跨一步,一起來學習一下I2C 總線的工作原理及使用方法,這樣我們可以將一些我們要保存的數據存儲到I2C總線的非易失存儲器中,實現斷電保持的功能,比如:你設置了一個密碼,但不至于這個設備斷過電以后就要重新設置過,我們可以將密碼數據寫在非易失存儲器里面,還有如汽車的量程表的讀數是不斷累計的,可以通過不斷訪問I2C 存儲器實現。
一、I2C總線特點
I2C 總線是主從結構,單片機是主器件,存儲器是從器件。一條總線可以帶多個從器件( 也可以有多主結構),I2C 總線的SDA 和SCL 是雙向的,開路門結構,通過上拉電阻接正電源。進行數據傳輸時,SDA 線上的數據必須在時鐘的高電平周期保持穩定。數據線的高或低電平狀態只有在SCL 線的時鐘信號是低電平時才能改變,如圖1 所示。
圖1 數據位的有效性規定
在SCL 線是高電平時,SDA 線從高電平向低電平切換表示起始條件;當SCL 是高電平時SDA 線由低電平向高電平切換表示停止條件如圖2 所示。
圖2 起始和停止信號
發送到SDA 線上的每個字節必須為8 位。
可以由高位到低位傳輸多個字節。每個字節后必須跟一個響應位(ACK)。響應時鐘脈沖由主機產生。主機釋放SDA 線從機將SDA 線拉低,并在時鐘脈沖的高電平期間保持穩定。如圖3 示。當主機接受數據時,它收到最后一個數據字節后,必須向從機發出一個結束傳送的信號。這個信號是由主機對從機的“非應答”來實現的。然后,從機釋放SDA 線,以允許主機產生終止或重復起始信號。
圖3 字節格式與應答
二、數據幀格式
(1)主機向從機發送數據,數據的傳送方向在傳輸過程中不改變,如圖4 所示。
圖4 主機向從機發送數據
注:陰影部分:表示主機向從機發送數據;無陰影部分:表示主機向從機讀取數據。
A:表示應答;:表示非應答。S:起始信號;P :終止信號。
(2)主機在第一個字節后,立即向從機讀取數據,如圖5 所示。
圖5 主機在第一個字節后立即讀從機
(3)復合格式,如圖6 所示。傳輸改變方向的時候,起始條件和從機地址都會被重復,但R/ W-位取反。如果主機接收器發送一個停止或重復起始信號,它之前應該發送了一個不響應信號()。
圖6 復合格式
由以上格式可見,無論哪種傳輸方式,起始信號、終止信號和地址均由主機發出(圖中陰影部分),數據字節的傳送方向則由尋址字節中的方向位規定,每個字節的傳送都必須有應答位(A 或)。
下面通過24C02 實例在增強型PIC 實驗板上編程,其硬件原理圖如圖7 所示,U7 為實驗板上24C02 芯片,SDA 與單片機的RB5 口相連,SCL 與單片機RB4 相連,七段數碼管D5、D7、D8 組成了顯示單元,字形碼的數據通過RC 口送入,各數碼管的顯示片選信號分別不同的RA 口進行控制。
圖7 讀/ 寫AT24C 系列存儲器原理圖
在MPLab IDE 軟件中新建工程,加入源程序代碼,同時進行芯片型號的選擇和配置位的設置,我們實驗所用的芯片型號為PIC16F877A。
編寫的程序代碼如下,其中程序流程圖如圖8 所示。
三、軟件流程圖
圖8 I2C 總線讀/ 寫數據流程圖
四、軟件代碼
/**********/
/* 目標器件:PIC16F877A */
/* 晶振:4.0MHZ */
/* 編譯環境:MPLAB V7.51 */
/**********/
/**********
包含頭文件
**********/
#include /********** 數據定義 **********/ #define address 0xa #define nop() asm("nop") #define OP_READ 0xa1 // 器件地址以及讀取操作 #define OP_WRITE 0xa0 // 器件地址以及寫入操作 /********** 端口定義 **********/ #define SCL RB4 #define SDA RB5 #define SCLIO TRISB4 #define SDAIO TRISB5 /********** 共陰LED 段碼表 **********/ const char table[]={0xC0,0xF9,0xA4,0x B0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x 83,0xC6,0xA1,0x86,0x8E}; /********** 函數功能: 延時子程序 **********/ void delay() { int i; for(i=0;i<100;i++) {;} } /********** 函數功能: 開始信號 **********/ void start() { SDA=1; nop(); SCL=1; nop();nop();nop();nop();nop(); SDA=0; nop();nop();nop();nop();nop(); SCL=0; nop();nop(); } /********** 函數功能: 停止信號 **********/ void stop() { SDA=0; nop(); SCL=1; nop();nop();nop();nop();nop(); SDA=1; nop();nop();nop();nop(); } /********** 函數功能: 讀取數據 出口參數:read_data **********/ unsigned char shin() { unsigned char i,read_data; for(i=0;i<8;i++) { nop();nop();nop(); SCL=1; nop();nop(); read_data《=1; if(SDA == 1) read_data=read_data+1; nop(); SCL=0; } return(read_data); } /********** 函數功能: 向EEPROM 寫數據 入口參數:write_data 出口參數:ack_bit **********/ bit shout(unsigned char write_data) { unsigned char i; unsigned char ack_bit; for(i = 0; i < 8; i++) { if(write_data&0x80) SDA=1; else SDA=0; nop(); SCL = 1; nop();nop();nop();nop();nop(); SCL = 0; nop(); write_data 《= 1; } nop();nop(); SDA = 1; nop();nop(); SCL = 1; nop();nop();nop(); ack_bit = SDA; // 讀取應答 SCL = 0; nop();nop(); return ack_bit; // 返回AT24Cxx 應答位 } /********** 函數功能: 向指定地址寫數據 入口參數:addr,write_data **********/ void write_byte(unsigned char addr, unsigned char write_data) { start(); shout(OP_WRITE); shout(addr); SDAIO = 0; // 在寫入數據前SDA 應設置為輸出 shout(write_data); stop(); delay(); } /********** 函數功能: 向指定地址讀數據 入口參數:random_addr 出口參數:read_data **********/ unsigned char read_random(unsigned char random_addr) { unsigned char read_data; start(); shout(OP_WRITE); shout(random_addr); start(); shout(OP_READ); SDAIO = 1; // 讀取數據前SDA 應設置為輸入 read_data = shin(); stop(); return(read_data); } /********** 函數功能: 顯示子程序 入口參數:k **********/ void display(unsigned char k) { TRISA=0X00; // 設置A 口全為輸出 PORTC=table[k/1000]; // 顯示千位 PORTA=0xEF; delay(); PORTC=table[k/100%10]; // 顯示百位 PORTA=0xDF; delay(); PORTC = table [k/ 10%10] ; // 顯示十位 PORTA=0xFB; delay(); PORTC=table[k%10]; // 顯示個位 PORTA=0xF7; delay(); } /********** 函數功能: 主程序 **********/ void main() { unsigned char eepromdata; TRISB=0X00; OPTION&=~(1《7); // 設置RB 口內部上拉電阻有效 TRISC=0X00; PORTB=0X00; PORTC=0xff; TRISA=0X00; eepromdata=0; write_byte(0x01,0x55); // 向0x01 地址寫入0x55(85) 的數據 delay(); write_byte(0x02,0xaa); // 向0x02 地址寫入0xAA(170) 的數據 delay(); eepromdata=read_random(0x02); // 讀取其中一個地址內的數據來驗證 while(1) { display(eepromdata); } } 編好程序后將編譯好的HEX 碼通過ICD2仿真燒寫器燒入單片機芯片,上電運行,主程序中在0x01 地址寫入了“0x55”, 在0x02 地址寫入了“0xaa”,然后在while 循環中讀出0x02地址的值,也就是我們之前寫入的“0x55”,讀出后顯示在數碼管上,我們可以看到數碼管顯示“170”,即“0xaa”相應的十進制數。 作為初學者的讀者一定對有些語句會有點疑問,可以看程序中的注釋部份,24c 系列IC 數據手冊和源程序相結合來進行分析。
上一篇:基于PIC單片機產生SPWM信號控制逆變橋的方法設計
下一篇:PIC單片機之步進電機
推薦閱讀
史海拾趣