版權(quán)聲明:本文為博主原創(chuàng)文章,允許轉(zhuǎn)載,但希望標(biāo)注轉(zhuǎn)載來源。 https://blog.csdn.net/qq_38410730/article/details/80312357
IIC的基本介紹
IIC的簡介
IIC(Inter-Integrated Circuit)總線是一種由PHILIPS公司在80年代開發(fā)的兩線式串行總線,用于連接微控制器及其外圍設(shè)備。它是半雙工通信方式。
- IIC總線最主要的優(yōu)點(diǎn)是其簡單性和有效性。由于接口直接在組件之上,因此IIC總線占用的空間非常小,減少了電路板的空間和芯片管腳的數(shù)量,降低了互聯(lián)成本。總線的長度可高達(dá)25英尺,并且能夠以10Kbps的最大傳輸速率支持40個(gè)組件。
- IIC總線的另一個(gè)優(yōu)點(diǎn)是,它支持多主控(multimastering), 其中任何能夠進(jìn)行發(fā)送和接收的設(shè)備都可以成為主總線。一個(gè)主控能夠控制信號(hào)的傳輸和時(shí)鐘頻率。當(dāng)然,在任何時(shí)間點(diǎn)上只能有一個(gè)主控。
IIC串行總線一般有兩根信號(hào)線,一根是雙向的數(shù)據(jù)線SDA,另一根是時(shí)鐘線SCL,其時(shí)鐘信號(hào)是由主控器件產(chǎn)生。所有接到IIC總線設(shè)備上的串行數(shù)據(jù)SDA都接到總線的SDA上,各設(shè)備的時(shí)鐘線SCL接到總線的SCL上。對于并聯(lián)在一條總線上的每個(gè)IC都有唯一的地址。
一般情況下,數(shù)據(jù)線SDA和時(shí)鐘線SCL都是處于上拉電阻狀態(tài)。因?yàn)?#xff1a;在總線空閑狀態(tài)時(shí),這兩根線一般被上面所接的上拉電阻拉高,保持著高電平。
STM32的IIC接口
目前絕大多數(shù)的MCU都附帶IIC總線接口,STM32也不例外。但是在本文中,我們不使用STM32的硬件IIC來讀取24C02,而是通過軟件的方式來模擬。
原因是因?yàn)?#xff1a;STM32的硬件IIC非常復(fù)雜,更重要的是它并不穩(wěn)定,故不推薦使用。
?
IIC協(xié)議
IIC總線在傳輸數(shù)據(jù)的過程中一共有三種類型信號(hào),分別為:開始信號(hào)、結(jié)束信號(hào)和應(yīng)答信號(hào)。這些信號(hào)中,起始信號(hào)是必需的,結(jié)束信號(hào)和應(yīng)答信號(hào),都可以不要。同時(shí)我們還要介紹其空閑狀態(tài)、數(shù)據(jù)的有效性、數(shù)據(jù)傳輸。
先來看一下IIC總線的時(shí)序圖:
這可能會(huì)比較復(fù)雜,可以先看一份簡化了的時(shí)序圖:
空閑狀態(tài)
當(dāng)IIC總線的數(shù)據(jù)線SDA和時(shí)鐘線SCL兩條信號(hào)線同時(shí)處于高電平時(shí),規(guī)定為總線的空閑狀態(tài)。此時(shí)各個(gè)器件的輸出級(jí)場效應(yīng)管均處在截止?fàn)顟B(tài),即釋放總線,由兩條信號(hào)線各自的上拉電阻把電平拉高。?
起始信號(hào)與停止信號(hào)
- 起始信號(hào):當(dāng)時(shí)鐘線SCL為高期間,數(shù)據(jù)線SDA由高到低的跳變;啟動(dòng)信號(hào)是一種電平跳變時(shí)序信號(hào),而不是一個(gè)電平信號(hào);
- 停止信號(hào):當(dāng)時(shí)鐘線SCL為高期間,數(shù)據(jù)線SDA由低到高的跳變;停止信號(hào)也是一種電平跳變時(shí)序信號(hào),而不是一個(gè)電平信號(hào)。
應(yīng)答信號(hào)
發(fā)送器每發(fā)送一個(gè)字節(jié)(8個(gè)bit),就在時(shí)鐘脈沖9期間釋放數(shù)據(jù)線,由接收器反饋一個(gè)應(yīng)答信號(hào)。?
- 應(yīng)答信號(hào)為低電平時(shí),規(guī)定為有效應(yīng)答位(ACK,簡稱應(yīng)答位),表示接收器已經(jīng)成功地接收了該字節(jié);
- 應(yīng)答信號(hào)為高電平時(shí),規(guī)定為非應(yīng)答位(NACK),一般表示接收器接收該字節(jié)沒有成功。?
對于反饋有效應(yīng)答位ACK的要求是:接收器在第9個(gè)時(shí)鐘脈沖之前的低電平期間將數(shù)據(jù)線SDA拉低,并且確保在該時(shí)鐘的高電平期間為穩(wěn)定的低電平。?如果接收器是主控器,則在它收到最后一個(gè)字節(jié)后,發(fā)送一個(gè)NACK信號(hào),以通知被控發(fā)送器結(jié)束數(shù)據(jù)發(fā)送,并釋放數(shù)據(jù)線SDA,以便主控接收器發(fā)送一個(gè)停止信號(hào)P。
數(shù)據(jù)有效性
IIC總線進(jìn)行數(shù)據(jù)傳送時(shí),時(shí)鐘信號(hào)為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定;只有在時(shí)鐘線上的信號(hào)為低電平期間,數(shù)據(jù)線上的高電平或低電平狀態(tài)才允許變化。?
即:數(shù)據(jù)在時(shí)鐘線SCL的上升沿到來之前就需準(zhǔn)備好。并在在下降沿到來之前必須穩(wěn)定。
數(shù)據(jù)的傳達(dá)
在IIC總線上傳送的每一位數(shù)據(jù)都有一個(gè)時(shí)鐘脈沖相對應(yīng)(或同步控制),即在SCL串行時(shí)鐘的配合下,在SDA上逐位地串行傳送每一位數(shù)據(jù)。數(shù)據(jù)位的傳輸是邊沿觸發(fā)。
延時(shí)時(shí)間
?
IIC總線的數(shù)據(jù)傳送
IIC總線上的每一個(gè)設(shè)備都可以作為主設(shè)備或者從設(shè)備,而且每一個(gè)設(shè)備都會(huì)對應(yīng)一個(gè)唯一的地址(地址通過物理接地或者拉高),主從設(shè)備之間就通過這個(gè)地址來確定與哪個(gè)器件進(jìn)行通信,在通常的應(yīng)用中,我們把CPU帶I2C總線接口的模塊作為主設(shè)備,把掛接在總線上的其他設(shè)備都作為從設(shè)備。
也就是說,主設(shè)備在傳輸有效數(shù)據(jù)之前要先指定從設(shè)備的地址,地址指定的過程和上面數(shù)據(jù)傳輸?shù)倪^程一樣,只不過大多數(shù)從設(shè)備的地址是7位的,然后協(xié)議規(guī)定再給地址添加一個(gè)最低位用來表示接下來數(shù)據(jù)傳輸?shù)姆较?#xff0c;0表示主設(shè)備向從設(shè)備寫數(shù)據(jù),1表示主設(shè)備向從設(shè)備讀數(shù)據(jù)。
- 主設(shè)備往從設(shè)備中寫數(shù)據(jù)。數(shù)據(jù)傳輸格式如下:
淡藍(lán)色部分表示數(shù)據(jù)由主機(jī)向從機(jī)傳送,粉紅色部分則表示數(shù)據(jù)由從機(jī)向主機(jī)傳送。
寫用0來表示(低電平),讀用1來表示(高電平)。
- 主設(shè)備從從設(shè)備中讀數(shù)據(jù)。數(shù)據(jù)傳輸格式如下:
在從機(jī)產(chǎn)生響應(yīng)時(shí),主機(jī)從發(fā)送變成接收,從機(jī)從接收變成發(fā)送。之后,數(shù)據(jù)由從機(jī)發(fā)送,主機(jī)接收,每個(gè)應(yīng)答由主機(jī)產(chǎn)生,時(shí)鐘信號(hào)仍由主機(jī)產(chǎn)生。若主機(jī)要終止本次傳輸,則發(fā)送一個(gè)非應(yīng)答信號(hào),接著主機(jī)產(chǎn)生停止條件。
- ?主設(shè)備往從設(shè)備中寫數(shù)據(jù),然后重啟起始條件,緊接著從從設(shè)備中讀取數(shù)據(jù);或者是主設(shè)備從從設(shè)備中讀數(shù)據(jù),然后重啟起始條件,緊接著主設(shè)備往從設(shè)備中寫數(shù)據(jù)。數(shù)據(jù)傳輸格式如下:
在多主的通信系統(tǒng)中,總線上有多個(gè)節(jié)點(diǎn),它們都有自己的尋址地址,可以作為從節(jié)點(diǎn)被別的節(jié)點(diǎn)訪問,同時(shí)它們都可以作為主節(jié)點(diǎn)向其它的節(jié)點(diǎn)發(fā)送控制字節(jié)和傳送數(shù)據(jù)。但是如果有兩個(gè)或兩個(gè)以上的節(jié)點(diǎn)都向總線上發(fā)送啟動(dòng)信號(hào)并開始傳送數(shù)據(jù),這樣就形成了沖突。要解決這種沖突,就要進(jìn)行仲裁的判決,這就是I2C總線上的仲裁。
I2C總線上的仲裁分兩部分:SCL線的同步和SDA線的仲裁。
這部分就暫時(shí)不介紹了,想要了解:可以參考鏈接淺談I2C總線或I2C總線協(xié)議圖解。
?
IIC底層驅(qū)動(dòng)程序分析
現(xiàn)擬采用PB6、PB7來模擬IIC時(shí)序,其中:PB6為時(shí)鐘線,PB7為數(shù)據(jù)線。
首先進(jìn)行一些必要的宏定義:
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //IO操作函數(shù) #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //輸入SDA //IIC所有操作函數(shù) void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //發(fā)送IIC開始信號(hào) void IIC_Stop(void); //發(fā)送IIC停止信號(hào) void IIC_Send_Byte(u8 txd); //IIC發(fā)送一個(gè)字節(jié) u8 IIC_Read_Byte(unsigned char ack);//IIC讀取一個(gè)字節(jié) u8 IIC_Wait_Ack(void); //IIC等待ACK信號(hào) void IIC_Ack(void); //IIC發(fā)送ACK信號(hào) void IIC_NAck(void); //IIC不發(fā)送ACK信號(hào) 由于IIC是半雙工通信方式,因而數(shù)據(jù)線SDA可能會(huì)數(shù)據(jù)輸入,也可能是數(shù)據(jù)輸出,需要定義IIC_SDA來進(jìn)行輸出、READ_SDA來進(jìn)行輸入,與此同時(shí)就要對IO口進(jìn)行模式配置:SDA_IN()和SDA_OUT()。
而時(shí)鐘線SCL一直是輸出的,所以就沒有數(shù)據(jù)線SDA麻煩了。
void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); } void IIC_Start(void) { SDA_OUT(); IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;???? delay_us(4); IIC_SCL=0;???? } void IIC_Stop(void) { SDA_OUT();???? IIC_SCL=0; IIC_SDA=0;???? delay_us(4); IIC_SCL=1; IIC_SDA=1;???? delay_us(4); } u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;???? return 0; } void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;???????????? for(t=0;t<8;t++) { if((txd&0x80)>>7) IIC_SDA=1; else IIC_SDA=0; txd<<=1; delay_us(2); ???? IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();???????? for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();???????? else IIC_Ack(); ???????? return receive; } 這里是通過普通IO口(PB6、PB7)來模擬IIC時(shí)序的程序,其實(shí)本質(zhì)上都是嚴(yán)格按照IIC的時(shí)序圖進(jìn)行的,認(rèn)真讀,仔細(xì)對比,應(yīng)該是沒有什么困難的。
就提一下:IIC_Read_Byte()函數(shù),這個(gè)函數(shù)的參數(shù)表示讀取一個(gè)字節(jié)之后,需要給對方應(yīng)答信號(hào)或非應(yīng)答信號(hào)。
?
普通IO口模擬IIC時(shí)序讀取24C02
24C02芯片介紹
EEPROM (Electrically Erasable Programmable read only memory),帶電可擦可編程只讀存儲(chǔ)器——一種掉電后數(shù)據(jù)不丟失的存儲(chǔ)芯片。?
24Cxx芯片是EEPROM芯片的一種,它是基于IIC總線的存儲(chǔ)器件,遵循二線制協(xié)議,由于其具有接口方便,體積小,數(shù)據(jù)掉電不丟失等特點(diǎn),在儀器儀表及工業(yè)自動(dòng)化控制中得到大量的應(yīng)用。24Cxx在電路的作用主要是在掉電的情況下保存數(shù)據(jù)。
本文使用的是24C02芯片,總?cè)萘渴?k個(gè)bit(256個(gè)字節(jié))。這里芯片名稱里的02代表著總?cè)萘俊?/p>
24C02芯片的引腳分布和具體的作用見下圖:
?
24C02芯片的引腳說明| 引腳名稱 | 說明 |
| A0-A2 | 地址輸入線 |
| SDA | 數(shù)據(jù)線 |
| SCL | 時(shí)鐘線 |
| WP | 寫保護(hù) |
| GND、VCC | 提供電源 |
下圖是本文中24C02和STM32的引腳連接圖:
從圖中可以看出:A0、A1、A2都為0。
對于并聯(lián)在一條IIC總線上的每個(gè)IC都有唯一的地址。那么看一下從器件地址,可以看出對于不同大小的24Cxx,具有不同的從器件地址。由于24C02為2k容量,也就是說只需要參考圖中第一行的內(nèi)容:
根據(jù)圖中的內(nèi)容:如果是寫24C02的時(shí)候,從器件地址為10100000(0xA0);讀24C02的時(shí)候,從器件地址為10100001(0xA1)。
24C02芯片的時(shí)序圖
這部分的內(nèi)容應(yīng)結(jié)合上文:I2C總線的數(shù)據(jù)傳送的內(nèi)容一起理解。
24C02字節(jié)寫時(shí)序
對24C02芯片進(jìn)行寫字節(jié)操作的時(shí)候,步驟如下:
開始位,后面緊跟從器件地址位(0xA0),等待應(yīng)答,這是為了在IIC總線上確定24C02的從地址位置;確定操作24C02的地址,等待應(yīng)答,也就是將字節(jié)寫入到24C02中256個(gè)字節(jié)中的位置;確定需要寫入24C02芯片的字節(jié),等待應(yīng)答,停止位。24C02字節(jié)讀時(shí)序
對24C02芯片進(jìn)行讀字節(jié)操作的時(shí)候,步驟如下:
開始位,后面緊跟從器件地址位(0xA0),等待應(yīng)答,這是為了在IIC總線上確定24C02的從地址位置;確定操作24C02的地址,等待應(yīng)答,也就是從24C02中256個(gè)字節(jié)中讀取字節(jié)的位置;再次開始位,后面緊跟從器件地址位(0xA1),等待應(yīng)答;獲取從24C02芯片中讀取的字節(jié),發(fā)出非應(yīng)答信號(hào),停止位。讀取24C02芯片程序
#define AT24C01 127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 4095 #define AT24C64 8191 #define AT24C128 16383 #define AT24C256 32767 #define EE_TYPE AT24C02 void AT24CXX_Init(void) { IIC_Init(); } u8 AT24CXX_ReadOneByte(u16 ReadAddr) { u8 temp=0; IIC_Start(); if(EE_TYPE>AT24C16)???????????? { IIC_Send_Byte(0XA0); IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr>>8);???? IIC_Wait_Ack(); }else ????IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr%256); IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); IIC_Wait_Ack(); temp=IIC_Read_Byte(0); ???? IIC_Stop();???????? return temp; } void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite) { IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr>>8);???? }else { IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); } IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr%256); IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); IIC_Wait_Ack(); IIC_Stop();???? delay_ms(10); } void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) { u8 t; for(t=0;t<Len;t++) { AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff); } } u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) { u8 t; u32 temp=0; for(t=0;t<Len;t++) { temp<<=8; temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); } return temp; } u8 AT24CXX_Check(void) { u8 temp; temp=AT24CXX_ReadOneByte(255); if(temp==0X55)return 0; else { AT24CXX_WriteOneByte(255,0X55); temp=AT24CXX_ReadOneByte(255); if(temp==0X55)return 0; } return 1; } void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead) { while(NumToRead) { *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); NumToRead--; } } void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite) { while(NumToWrite--) { AT24CXX_WriteOneByte(WriteAddr,*pBuffer); WriteAddr++; pBuffer++; } } const u8 TEXT_Buffer[]={"WarShipSTM32 IIC TEST"}; #define SIZE sizeof(TEXT_Buffer) int main(void) { u8 key; u16 i=0; u8 datatemp[SIZE]; delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); uart_init(115200); LED_Init(); LCD_Init(); KEY_Init(); AT24CXX_Init(); POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"WarShip STM32"); LCD_ShowString(30,70,200,16,16,"IIC TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2015/1/15"); LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read"); while(AT24CXX_Check()) { LCD_ShowString(30,150,200,16,16,"24C02 Check Failed!"); delay_ms(500); LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500); LED0=!LED0; } LCD_ShowString(30,150,200,16,16,"24C02 Ready!"); POINT_COLOR=BLUE; while(1) { key=KEY_Scan(0); if(key==KEY1_PRES) { LCD_Fill(0,170,239,319,WHITE); LCD_ShowString(30,170,200,16,16,"Start Write 24C02...."); AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE); LCD_ShowString(30,170,200,16,16,"24C02 Write Finished!"); } if(key==KEY0_PRES) { LCD_ShowString(30,170,200,16,16,"Start Read 24C02.... "); AT24CXX_Read(0,datatemp,SIZE); LCD_ShowString(30,170,200,16,16,"The Data Readed Is: "); LCD_ShowString(30,190,200,16,16,datatemp); } i++; delay_ms(10); if(i==20) { LED0=!LED0; i=0; } } } ?
IIC總結(jié)
進(jìn)行數(shù)據(jù)傳送時(shí),在SCL為高電平期間,SDA線上電平必須保持穩(wěn)定,只有SCL為低時(shí),才允許SDA線上電平改變狀態(tài)。并且每個(gè)字節(jié)傳送時(shí)都是高位在前;對于應(yīng)答信號(hào),ACK=0時(shí)為有效應(yīng)答位,說明從機(jī)已經(jīng)成功接收到該字節(jié),若為1則說明接受不成功;如果從機(jī)需要延遲下一個(gè)數(shù)據(jù)字節(jié)開始傳送的時(shí)間,可以通過把SCL電平拉低并保持來強(qiáng)制主機(jī)進(jìn)入等待狀態(tài);主機(jī)完成一次通信后還想繼續(xù)占用總線在進(jìn)行一次通信,而又不釋放總線,就要利用重啟動(dòng)信號(hào)。它既作為前一次數(shù)據(jù)傳輸?shù)慕Y(jié)束,又作為后一次傳輸?shù)拈_始;總線沖突時(shí),按“低電平優(yōu)先”的仲裁原則,把總線判給在數(shù)據(jù)線上先發(fā)送低電平的主器件;在特殊情況下,若需禁止所有發(fā)生在I2C總線上的通信,可采用封鎖或關(guān)閉總線,具體操作為在總線上的任一器件將SCL鎖定在低電平即可;SDA仲裁和SCL時(shí)鐘同步處理過程沒有先后關(guān)系,而是同時(shí)進(jìn)行的。 ?
轉(zhuǎn)載于:https://www.cnblogs.com/CodeWorkerLiMing/p/10830459.html
總結(jié)
以上是生活随笔為你收集整理的【STM32】IIC的基本原理(实例:普通IO口模拟IIC时序读取24C02)(转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。