關於IIC我覺這個博客里面說的已經夠清楚了
如下圖所示的寫操作的時序圖:
其實像這種通信協議的要求是很精確的,一點點不對都可能導致在實際工程中無法讀取數據。我就是被一個應答位耽誤了好久,還好最后被我發現了。雖然程序不長,但是每一句話都是值得我們認真學習的,下面是我自己結合網上還有書中的程序綜合的,親測可用。最后用keil的邏輯分析儀測試了iic端口輸出的波形。
iic.h
#ifndef __IIC_H #define __IIC_H #include <reg52.h> #include <intrins.h> sbit SCL = P2^1; sbit SDA = P2^0; void iic_init(void); void iic_start(void); void iic_stop(void); void iic_dalay_us(void);//iic延時專用 void iic_send_ack(bit _ack);//讀取數據之后發送應答位 void iic_wait_ack(void);//寫入數據之后,等待從機應答信號 void iic_write_byte(unsigned char _byte); //寫一個字節 unsigned char iic_read_byte(void);//讀取一個字節 void iic_write_addr_byte(unsigned char _addr,unsigned char _byte);//任意地址寫一個字節 unsigned char iic_read_addr_byte(unsigned char _addr); //任意地址讀一個字節 #endif
iic.c
//可以用keil自帶的邏輯分析儀查看iic端口的輸出波形,通過對波形的分析,判斷iic協議有沒有工作。 #include "iic.h" #include "mpu6050.h" //---------5us,實際測試是8us-------- void iic_dalay_us(void) { unsigned char i; _nop_();//nop是1個指令周期 = 1機器周期 = 12時鍾周期 = 12*1/f,對於12M晶振,1us i = 1; while(--i); //執行一次while循環2us } //-------起始信號,時鍾高,數據高變低--------------- void iic_start(void) { SDA = 1; iic_dalay_us(); SCL = 1; iic_dalay_us(); SDA = 0; iic_dalay_us(); SCL = 0; //鉗住I2C總線,准備發送或接收數據 } //--------停止信號------- void iic_stop(void) { SDA = 0; iic_dalay_us(); SCL = 0; iic_dalay_us(); SCL = 1; iic_dalay_us(); SDA = 1; iic_dalay_us(); } //------------- void iic_init(void) { SCL = 1; iic_dalay_us(); SDA = 1; iic_dalay_us(); } //----讀取數據完成后,發送應答位, //----0->ack,應答,告訴從機我要繼續讀取下一個字節,從機收到這個信號后繼續發送數據 //----1->not ack,不應答,告訴從機我不在繼續接受數據,從機停止發送數據 void iic_send_ack(bit _ack) { SDA = _ack;//接收完成后,拉高SDA,發送非應答信號 SCL = 1; iic_dalay_us(); SCL = 0; //拉低,完成應答位 iic_dalay_us(); } //-------------主機發送完成,等待應答---------------- //主機發送完一個字節的數據之后會把SDA拉高,等待從機的信號,如果從機不應答,SDA將一直拉高,此時while函數等待250us, //如果在SCL高電平期間,SDA被從設備拉低表示從設備應答,此時while跳過,應答位結束, void iic_wait_ack(void) { unsigned char i = 0; SCL = 1; iic_dalay_us(); while((SDA==1)&&(i<250))i++;//應答時,SDA為0表示從機成功接受到數據, SCL = 0; //拉低,完成應答位 } //------------------------ //寫一個字節,從高位往低位寫 //主機給從機發送數據,從機是在時鍾的下降沿采集SDA的數據 void iic_write_byte(unsigned char _byte) { unsigned char i; for(i=0;i<8;i++) { SDA = _byte&0X80; //先寫高位 iic_dalay_us(); SCL = 1; iic_dalay_us(); SCL = 0; //下降沿采集數據 iic_dalay_us(); _byte = _byte<<1; } SDA = 1;//發送完畢后,釋放數據線,檢測從機應答 iic_wait_ack();//等待應答 } //---------------------------------- //任意地址寫一個字節,因為我使用的時候從設備只有一個,所以不需要寫入從設備地址, //如果有多個從設備的話,可以增加一個參數,寫入從設備地址 void iic_write_addr_byte(unsigned char _addr,unsigned char _byte) { iic_start(); iic_write_byte(SlaveAddress); //此處寫入從機地址,我這里只有一個,沒有在函數里面體現 iic_write_byte(_addr); iic_write_byte(_byte); iic_stop(); } //------------------------ //讀字節,也是先讀取的是高位,需要左移 //主機從從機讀取數據,本質上是從機檢測主機發出的時鍾信號有8個上升沿 unsigned char iic_read_byte(void) { unsigned char i = 0; unsigned char read_byte = 0; SDA = 1; //先確保主機釋放SDA iic_dalay_us(); for(i = 0;i<8;i++) { SCL = 1; read_byte = (read_byte<<1)|SDA ; iic_dalay_us(); SCL = 0; //拉低,采集數據 iic_dalay_us(); } return read_byte; } //------------------ //在某一地址內讀取數據 unsigned char iic_read_addr_byte(unsigned char _addr) { unsigned char read_data; iic_start(); iic_write_byte(SlaveAddress);//發送設備地址+寫信號 iic_write_byte(_addr); //發送存儲器單元 iic_start(); //必須從新啟動IIC iic_write_byte(SlaveAddress + 1);//發送設備地址+讀信號 read_data = iic_read_byte();//讀出數據 iic_send_ack(1);//發送非應答信號 /* 原來問題出在這里!!,如果后面跟的是iic_stop的話,就發送非應答,告訴從機不要在發數據了,注意這里沒有連續讀取 不然的話,從機收到0的應答位之后會一直發送數據,但是這時主機又沒有接受,導致數據只能第一次發送成功,后面都收不到數據。 */ iic_stop(); return read_data; }
keil仿真波形: