I2C 通訊協議:(Inter-Integrated Circuit)是由Phiilps 公司開發的,由於它引腳少,硬件實現簡單,可擴展性強,不需要USART、CAN 等通訊協議的外部收發設備,現在被廣泛地使用在系統內多個集成電路(IC)間的通訊,其常用的連接方式如下:
物理層:
(1) 它是一個支持設備的總線。“總線”指多個設備共用的信號線。在一個I2C 通訊總線中,可連接多個I2C 通訊設備,支持多個通訊主機及多個通訊從機。
(2) 一個I2C 總線只使用兩條總線線路,一條雙向串行數據線(SDA) ,一條串行時鍾線(SCL)。數據線即用來表示數據,時鍾線用於數據收發同步。
(3) 每個連接到總線的設備都有一個獨立的地址,主機可以利用這個地址進行不同設備之間的訪問。
(4) 總線通過上拉電阻接到電源。當I2C 設備空閑時,會輸出高阻態,而當所有設備都空閑,都輸出高阻態時,由上拉電阻把總線拉成高電平。
(5) 多個主機同時使用總線時,為了防止數據沖突,會利用仲裁方式決定由哪個設備占用總線。
(6) 具有三種傳輸模式:標准模式傳輸速率為100kbit/s ,快速模式為400kbit/s ,高速模式下可達3.4Mbit/s,但目前大多I C 設備尚不支持高速模式。
(7) 連接到相同總線的IC 數量受到總線的最大電容400pF 限制
協議層:
- 起始信號:由主機的IIC接口產生的傳輸起始信號,這時連接到總線上的所有從機都會收到這個信號;
- 地址廣播:起始信號產生后,所有從機都會開始等待主機接下來廣播的從機地址信號,以選中從機設備,沒有被選中的將會忽略之后的數據信號;
- 傳輸方向:0表示主機向從機寫數據,1表示主機向從機讀數據;
- 應答信號:從機接收到匹配的地址之后,從機或主機會返回一個應答(ACK)或非應答(NACK)信號,只有接收到信號,主機才能繼續發送或接受數據;
- 寫數據/讀數據:主機每發送完一個數據包之后。都要重新等待從機的應答信號,並重復這個步驟;/從機每發送玩一個數據包之后,都會重新等待主機的應答信號,並重復這個步驟;
- 停止信號:數據傳輸完成后(得到NACK信號后),主機向從機發送一個停止傳輸信號,表示不再傳輸。/當主機希望停止接受數據時,就會返回一個NACK信號給從機,從機就會自動停止數據傳輸;
除了基本的讀寫,I2C通訊更常用的是復合格式,即第三幅圖的情況,該傳輸過程有兩次起始信號(S)。一般在第一次傳輸中,主機通過SLAVE_ADDRESS 尋找到從設備后,發送一段“數據”,這段數據通常用於表示從設備內部的寄存器或存儲器地址(注意區分它與 SLAVE_ADDRESS 的區別);在第二次的傳輸中,對該地址的內容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫地址,第二次則是讀寫的實際內容。
bsp_iic_ee.h文件如下:
#ifndef __I2C_EE_H #define __I2C_EE_H #include "stm32f10x.h" #define I2Cx_EEPROM I2C1 #define I2C_EEPROM_CLK RCC_APB1Periph_I2C1 #define I2C_EEPROM_GPIO_CLK RCC_APB2Periph_GPIOB #define I2C_EPPROM_SCL_PORT GPIOB//時鍾線 #define I2C_EEPROM_SCL_PIN GPIO_Pin_6 #define I2C_EEPROM_SDA_PORT GPIOB//數據線 #define I2C_EEPROM_SDA_PIN GPIO_Pin_7 #define I2C_Speed 400000//通訊速度kbits/s #define I2Cx_OWN_ADDRESS7 0X0A //主機地址可自行配置,但在總線上這個地址要唯一 #define I2C_PageSize 8//每頁8個字節 #define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)//等待超時時間 #define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT)) #define EEPROM_DEBUG_ON 0 #define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)//##args表示參數個數可變 #define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg) #define EEPROM_DEBUG(fmt,arg...) do{\ if(EEPROM_DEBUG_ON)\ printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\ }while(0) /* * AT24C02 2kb = 2048bit = 2048/8 B = 256 B * 32 pages of 8 bytes each * * Device Address * 1 0 1 0 A2 A1 A0 R/W * 1 0 1 0 0 0 0 0 = 0XA0//寫地址 * 1 0 1 0 0 0 0 1 = 0XA1 //讀地址 */ /* EEPROM Addresses defines */ #define EEPROM_Block0_ADDRESS 0xA0 /* E2 = 0 */ //#define EEPROM_Block1_ADDRESS 0xA2 /* E2 = 0 */ //#define EEPROM_Block2_ADDRESS 0xA4 /* E2 = 0 */ //#define EEPROM_Block3_ADDRESS 0xA6 /* E2 = 0 */ void I2C_EE_Init(void); void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite); uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr); uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite); uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead); void I2C_EE_WaitEepromStandbyState(void); #endif
bsp_iic_ee.c文件如下:
#include "bsp_iic_ee.h" #include "bsp_usart.h" uint16_t EEPROM_ADDRESS;//從設備地址 static __IO uint32_t I2CTimeout = I2CT_LONG_TIMEOUT; //定義超時變量 static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);//聲明超時用戶回調函數 static void I2C_GPIO_Config(void)//引腳配置 { GPIO_InitTypeDef GPIO_InitStructure; //開啟I2C,GPIO時鍾 RCC_APB1PeriphClockCmd(I2C_EEPROM_CLK, ENABLE ); RCC_APB2PeriphClockCmd(I2C_EEPROM_GPIO_CLK, ENABLE ); //配置SCL GPIO_InitStructure.GPIO_Pin = I2C_EEPROM_SCL_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //這里必須復用開漏輸出 GPIO_Init(I2C_EPPROM_SCL_PORT , &GPIO_InitStructure); //配置SDA GPIO_InitStructure.GPIO_Pin = I2C_EEPROM_SDA_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //也必須復用開漏輸出 GPIO_Init(I2C_EEPROM_SDA_PORT , &GPIO_InitStructure); } static void I2C_Mode_Config(void)//模式配置 { I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//此處未分主從,直接選這個 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//占空比 I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7; //主機地址 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;//使能響應 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//一般使用7位地址 I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;//速度 I2C_Init(I2Cx_EEPROM , &I2C_InitStructure);//初始化配置 I2C_Cmd(I2Cx_EEPROM , ENABLE); //使能I2C1 } void I2C_EE_Init(void) { I2C_GPIO_Config(); I2C_Mode_Config(); #ifdef EEPROM_Block0_ADDRESS EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;//塊0被選中 #endif #ifdef EEPROM_Block1_ADDRESS EEPROM_ADDRESS = EEPROM_Block1_ADDRESS; #endif #ifdef EEPROM_Block2_ADDRESS EEPROM_ADDRESS = EEPROM_Block2_ADDRESS; #endif #ifdef EEPROM_Block3_ADDRESS EEPROM_ADDRESS = EEPROM_Block3_ADDRESS; #endif }
//將緩沖區的數據寫入到I2C_EE中 void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite) { u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0; Addr = WriteAddr % I2C_PageSize; count = I2C_PageSize - Addr; NumOfPage = NumByteToWrite / I2C_PageSize; NumOfSingle = NumByteToWrite % I2C_PageSize; /* If WriteAddr is I2C_PageSize aligned */ if(Addr == 0) { /* If NumByteToWrite < I2C_PageSize */ if(NumOfPage == 0) { I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); I2C_EE_WaitEepromStandbyState(); } /* If NumByteToWrite > I2C_PageSize */ else { while(NumOfPage--) { I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize); I2C_EE_WaitEepromStandbyState(); WriteAddr += I2C_PageSize; pBuffer += I2C_PageSize; } if(NumOfSingle!=0) { I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); I2C_EE_WaitEepromStandbyState(); } } } /* If WriteAddr is not I2C_PageSize aligned */ else { /* If NumByteToWrite < I2C_PageSize */ if(NumOfPage== 0) { I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); I2C_EE_WaitEepromStandbyState(); } /* If NumByteToWrite > I2C_PageSize */ else { NumByteToWrite -= count; NumOfPage = NumByteToWrite / I2C_PageSize; NumOfSingle = NumByteToWrite % I2C_PageSize; if(count != 0) { I2C_EE_PageWrite(pBuffer, WriteAddr, count); I2C_EE_WaitEepromStandbyState(); WriteAddr += count; pBuffer += count; } while(NumOfPage--) { I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize); I2C_EE_WaitEepromStandbyState(); WriteAddr += I2C_PageSize; pBuffer += I2C_PageSize; } if(NumOfSingle != 0) { I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); I2C_EE_WaitEepromStandbyState(); } } } } //寫一個字節給EE,讀讀英文解釋吧,這就是主機發送的過程 uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr) { /* Send STRAT condition */ I2C_GenerateSTART(I2Cx_EEPROM , ENABLE); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV5 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_MODE_SELECT)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0); } I2CTimeout = I2CT_FLAG_TIMEOUT; /* Send EEPROM address for write */ I2C_Send7bitAddress(I2Cx_EEPROM , EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Test on EV6 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1); } /* Send the EEPROM's internal address to write to */ I2C_SendData(I2Cx_EEPROM , WriteAddr); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2); } /* Send the byte to be written */ I2C_SendData(I2Cx_EEPROM , *pBuffer); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3); } /* Send STOP condition */ I2C_GenerateSTOP(I2Cx_EEPROM , ENABLE); return 1; } //頁寫入,但NumByteToWrite不能超過8 uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite) { I2CTimeout = I2CT_LONG_TIMEOUT; while(I2C_GetFlagStatus(I2Cx_EEPROM , I2C_FLAG_BUSY)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4); } /* Send START condition */ I2C_GenerateSTART(I2Cx_EEPROM , ENABLE); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV5 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_MODE_SELECT)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5); } /* Send EEPROM address for write */ I2C_Send7bitAddress(I2Cx_EEPROM , EEPROM_ADDRESS, I2C_Direction_Transmitter); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV6 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6); } /* Send the EEPROM's internal address to write to */ I2C_SendData(I2Cx_EEPROM , WriteAddr); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(! I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7); } /* While there is data to be written */ while(NumByteToWrite--) { /* Send the current byte */ I2C_SendData(I2Cx_EEPROM , *pBuffer); /* Point to the next byte to be written */ pBuffer++; I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while (!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8); } } /* Send STOP condition */ I2C_GenerateSTOP(I2Cx_EEPROM , ENABLE); return 1; } //讀緩存區里的數據,主機接受的過程 uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead) { I2CTimeout = I2CT_LONG_TIMEOUT; //*((u8 *)0x4001080c) |=0x80; while(I2C_GetFlagStatus(I2Cx_EEPROM , I2C_FLAG_BUSY)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9); } /* Send START condition */ I2C_GenerateSTART(I2Cx_EEPROM , ENABLE); //*((u8 *)0x4001080c) &=~0x80; I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV5 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_MODE_SELECT)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10); } /* Send EEPROM address for write */ I2C_Send7bitAddress(I2Cx_EEPROM , EEPROM_ADDRESS, I2C_Direction_Transmitter); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV6 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11); } /* Clear EV6 by setting again the PE bit */ I2C_Cmd(I2Cx_EEPROM , ENABLE); /* Send the EEPROM's internal address to write to */ I2C_SendData(I2Cx_EEPROM , ReadAddr); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12); } /* Send STRAT condition a second time */ I2C_GenerateSTART(I2Cx_EEPROM , ENABLE); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV5 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_MODE_SELECT)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13); } /* Send EEPROM address for read */ I2C_Send7bitAddress(I2Cx_EEPROM , EEPROM_ADDRESS, I2C_Direction_Receiver); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV6 and clear it */ while(!I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14); } /* While there is data to be read */ while(NumByteToRead) { if(NumByteToRead == 1) { /* Disable Acknowledgement */ I2C_AcknowledgeConfig(I2Cx_EEPROM , DISABLE); /* Send STOP Condition */ I2C_GenerateSTOP(I2Cx_EEPROM , ENABLE); } /* Test on EV7 and clear it */ I2CTimeout = I2CT_LONG_TIMEOUT; while(I2C_CheckEvent(I2Cx_EEPROM , I2C_EVENT_MASTER_BYTE_RECEIVED)==0) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3); } { /* Read a byte from the EEPROM */ *pBuffer = I2C_ReceiveData(I2Cx_EEPROM ); /* Point to the next location where the byte read will be saved */ pBuffer++; /* Decrement the read bytes counter */ NumByteToRead--; } } /* Enable Acknowledgement to be ready for another reception */ I2C_AcknowledgeConfig(I2Cx_EEPROM , ENABLE); return 1; } //此函數內容是,在發送從設備地址之后,檢測EE的響應,若EE發送ACK則准備好了,可以開始下一次通訊 void I2C_EE_WaitEepromStandbyState(void) { vu16 SR1_Tmp = 0; do { /* Send START condition */ I2C_GenerateSTART(I2Cx_EEPROM , ENABLE); /* Read I2C1 SR1 register */ SR1_Tmp = I2C_ReadRegister(I2Cx_EEPROM , I2C_Register_SR1); /* Send EEPROM address for write */ I2C_Send7bitAddress(I2Cx_EEPROM , EEPROM_ADDRESS, I2C_Direction_Transmitter); }while(!(I2C_ReadRegister(I2Cx_EEPROM , I2C_Register_SR1) & 0x0002)); /* Clear AF flag */ I2C_ClearFlag(I2Cx_EEPROM , I2C_FLAG_AF); /* STOP condition */ I2C_GenerateSTOP(I2Cx_EEPROM , ENABLE); } //能定位是哪里超時了 static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode) { /* Block communication and all processes */ EEPROM_ERROR("I2C等待超時!errorCode = %d",errorCode); return 0; }
main.c文件如下:
#include "stm32f10x.h" #include "bsp_led.h" #include "bsp_usart.h" #include "bsp_iic_ee.h" #include <string.h> #define EEP_Firstpage 0x00 uint8_t I2c_Buf_Write[256]; uint8_t I2c_Buf_Read[256]; uint8_t I2C_Test(void); int main(void) { LED_GPIO_Config(); blue(ON); USART_Config(); printf("\r\n 這是一個IIC外設讀寫實驗 \r\n"); I2C_EE_Init(); printf("\r\n 這是一個IIC外設讀寫實驗 \r\n"); if(I2C_Test() ==1) { green(ON); } else { red(ON); } while (1) { } } uint8_t I2C_Test(void) { uint16_t i; printf("寫入的數據\n\r"); for ( i=0; i<=255; i++ ) { I2c_Buf_Write[i] = i; printf("0x%02X ", I2c_Buf_Write[i]); if(i%16 == 15) printf("\n\r"); } //將數據寫入到EEPROM I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256); EEPROM_INFO("\n\r寫成功\n\r"); EEPROM_INFO("\n\r讀出的數據\n\r"); I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256); for (i=0; i<256; i++) { if(I2c_Buf_Read[i] != I2c_Buf_Write[i]) { EEPROM_ERROR("0x%02X ", I2c_Buf_Read[i]); EEPROM_ERROR("錯誤,讀入數據與寫入數據不一致\n\r"); return 0; } printf("0x%02X ", I2c_Buf_Read[i]); if(i%16 == 15) printf("\n\r"); } EEPROM_INFO("I2C(AT24C02)讀寫測試成功\n\r"); return 1; }