1.前奏
很久沒有更新部落格了,hiahia,之前忙著考研,現在卻忙著找工作,哎咦呦~,但也沒事,研沒考上也可以好好學學我感興趣的東西,用這些來找工作也挺好,做自己喜歡的事情,研究生還是想考,等快到暑假再看吧。這段時間重新把以前學的東西又鞏固了一遍,和我想的一樣,以前的基礎果然不牢。這次感覺花的時間挺久的就是IIC協議了,經過這次自己敲程式碼寫,對協議的理解更加深了。所以想分享給大家,主要是程式碼,雖然網上有很多,但是就是想把自己的程式碼分享出來,當然,其中也有一些走過的小誤區,可以給大家做一個參考,也可以讓自己以後回寫協議的時候有一個回憶參考。
2.IIC協議簡單說明開始訊號:SCL為高電平時,SDA為下降沿;停止訊號:SCL為高電平時,SDA為上升沿;應答訊號:傳送完一個位元組後,當SCL為高電平時,讀出的SDA為低電平;傳送資料:把SDA資料一位一位準備好,SCL為上升沿時一位一位地寫入資料,方向為高位到低位;接收資料:SCL為下降沿時一位一位地讀出資料,讀取資料的方向為高位到低位;
3.程式碼分享與說明及小誤區分享我這裡的程式碼因為是我自己寫的,有的地方想盡量寫簡單,所以才會犯了我除錯過程中的一些錯誤,這裡面大家當做參考就好,如有和大家一樣的錯誤,那就真是英雄所見略同啦,這裡用的主控晶片是STM32F103ZET6。開始訊號:
//下降沿為開始訊號void IIC_Start(void){ SDA_OUT(); SDA=1; SCL=1; delay_us(3); SDA=0; delay_us(3); SCL=0;}
小說明:這裡的第二第三句SDA=1;SCL=1;最好不要變。誤區解讀:若是SCL=1;SDA=1;我們不能確定在SCL=1之前SDA是高電平還是低電平,假如是低電平,那麼這兩條語句之後,SDA就會產生一個上升沿。
停止訊號:
//上升沿為停止訊號void IIC_End(void){ SDA_OUT(); SDA=0; SCL=1; delay_us(3); SDA=1; delay_us(3); SCL=0; delay_us(1);}
小說明:同理,這裡的第二第三句SDA=0;SCL=1;不能變。誤區解讀:若是SCL=1;SDA=0;我們不能確定在SCL=1之前SDA是高電平還是低電平,假如是高電平,那麼這兩條語句之後,SDA就會產生一個下降沿。這裡我就是因為把這兩句調換了,所以才一直接受不到資料,除錯了很久,不知道是哪的問題,後來還是走在路上買東西一直想才終於搞明白了,然後才有了想法寫一篇部落格,hiahia~,開心開心。
應答訊號:
//返回值為0表示應答成功,1表示非應答uint8_t IIC_Ack(void){ uint8_t count; SDA_IN(); SCL=0; delay_us(3); SCL=1; delay_us(2); while(Read_SDA==1) { count++; if(count>250) { IIC_End(); return 1; } } SCL=0; //釋放時鐘線 return 0;}
小說明:這裡定義了一個count,為的就是防止SDA接收不到低電平程式卡炸在這裡。第三句SCL=0; delay_us(3);是先釋放匯流排,等待SDA的低電平訊號,但是親測過,不加這兩句也可以。
傳送資料:
//上升沿寫入資料,從高位到低位寫資料void IIC_Send_One_Byte(uint8_t data){ uint8_t i; SCL=0; SDA_OUT(); for(i=0;i<8;i++) { if((data&0x80)>>7) SDA=1; else SDA=0; data<<=1; delay_us(3); SCL=1; delay_us(3); SCL=0; delay_us(2); } }
小說明:先把SDA上的資料準備好,然後利用SCL上升沿傳送,注意是先高位再低位傳送就可以,逐位資料程式碼傳送的演算法也可以自己再想想。
接收資料:
//下降沿讀出資料,從高位到低位讀資料uint8_t IIC_Read_One_Byte(void){ uint8_t data=0; uint8_t i; SDA_IN(); for(i=0;i<7;i++) { SCL=1; delay_us(3); data|=Read_SDA; data<<=1; SCL=0; delay_us(3); } return data;}
誤區解讀:這裡注意i<7,之前想當然地以為接收8位資料就是i<8了,後來在自己的腦海中模擬了一下這段程式才想明白。
向AT24C02指定地址寫一個位元組:
extern uint8_t num;void AT24C02_Write_One_Byte(uint8_t addr,uint8_t data){ IIC_Start(); IIC_Send_One_Byte(0xA0); if(IIC_Ack()==0) { IIC_Send_One_Byte(addr); if(IIC_Ack()==0) { IIC_Send_One_Byte(data); if(IIC_Ack()==0) { IIC_End(); USART1_Send(num); } } } delay_ms(10); //可以註釋掉}
小說明:這裡按照AT24C02的寫位元組操作來就好了
向AT24C02指定地址讀一個位元組:
uint8_t AT24C02_Read_One_Byte(uint8_t addr){ uint8_t data; IIC_Start(); IIC_Send_One_Byte(0xA0); if(IIC_Ack()==0) { IIC_Send_One_Byte(addr); if(IIC_Ack()==0) { IIC_Start(); IIC_Send_One_Byte(0xA1); if(IIC_Ack()==0) { data=IIC_Read_One_Byte(); IIC_End(); } } } delay_ms(10); //可以註釋掉 return data;}
小說明:這裡按照AT24C02的讀指定地址操作來就好了
其他函式:
#define SCL PBout(6)#define SDA PBout(7)#define Read_SDA PBin(7)//轉變SDA的輸入輸出模式#define SDA_IN() {GPIOB->CRL&=0x0fffffff;GPIOB->CRL|=0x80000000;}#define SDA_OUT() {GPIOB->CRL&=0x0fffffff;GPIOB->CRL|=0x30000000;}void AT24C02_Init(void){ GPIO_InitTypeDef GPIO_Struct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_Struct.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_Struct.GPIO_Pin=GPIO_Pin_6; //IIC SCL GPIO_Struct.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_Struct); GPIO_Struct.GPIO_Pin=GPIO_Pin_7; //IIC SDA GPIO_Init(GPIOB,&GPIO_Struct); GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); }int main(void){ AT24C02_Init(); delay_init(); key_init(); uart_init(115200); num=AT24C02_Read_One_Byte(0); USART1_Send(num); while(1) { if(key0==0) //°´¼ü°´Ï { delay_ms(10); if(key0==0) { num+=2; AT24C02_Write_One_Byte(0,num); }while(!key0); } }}
4.實驗現象:小說明:按一次key0,num的值加2,然後顯示在串列埠上位機上,一直連續重複出現的AC、AE、B2、B6我按過復位鍵之後顯示的,由此可以檢驗AT24C02可以斷電之後儲存原來的資料,程式碼磨問題啦~