地方門戶網(wǎng)站建設要求福州百度分公司
IIC簡介
物理層
連接多個devices
它是一個支持設備的總線?!翱偩€”指多個設備共用的信號線。在一個 I2C 通訊總線中,可連接多個 I2C 通訊設備,支持多個通訊主機及多個通訊從機。
兩根線
一個 I2C 總線只使用兩條總線線路,一條雙向串行數(shù)據(jù)線(SDA)
,一條串行時鐘線(SCL)
。數(shù)據(jù)線即用來表示數(shù)據(jù),時鐘線用于數(shù)據(jù)收發(fā)同步。
設備地址
每個連接到總線的設備都有一個獨立的地址,主機可以利用這個地址進行不同設備之間的訪問。
上拉電阻和高阻態(tài)
總線通過上拉電阻接到電源。當 I2C 設備空閑時,會輸出高阻態(tài),而當所有設備都空閑,都輸出高阻態(tài)時,由上拉電阻把總線拉成高電平。
仲裁
多個主機同時使用總線時,為了防止數(shù)據(jù)沖突,會利用仲裁方式?jīng)Q定由哪個設備占用總線。
三種速度模式
具有三種傳輸模式:標準模式傳輸速率為 100kbit/s ,快速模式為 400kbit/s ,高速模式下可達 3.4Mbit/s,但目前大多 I2C 設備尚不支持高速模式。
最大devices限制
連接到相同總線的 IC 數(shù)量受到總線的最大電容 400pF 限制 。
協(xié)議層
I2C 的協(xié)議定義了通訊的起始和停止信號、數(shù)據(jù)有效性、響應、仲裁、時鐘同步和地址廣播等環(huán)節(jié)。
I2C 基本讀寫過程
主機寫數(shù)據(jù)到從機
主機由從機中讀數(shù)據(jù)
I2C 通訊復合格式
數(shù)據(jù)由主機傳輸至從機
數(shù)據(jù)由從機傳輸至主機
: 傳輸開始信號
: 傳輸方向選擇位,1 為讀,0 為寫
: 應答(ACK)或非應答(NACK)信號
停止傳輸信號
1.S是起始信號,由主機產(chǎn)生,掛載在總線上的設備都會收到,準備接收主機下一個數(shù)據(jù)
2.設備聆聽地址信息,選擇從機: 起始信號之后,主機會廣播從機地址,在 I2C 總線上,每個設備的地址都是唯一的,當主機廣播的地址與某個設備地址相同時,這個設備就被選中了,沒被選中的設備將會忽略之后的數(shù)據(jù)信號。根據(jù) I2C 協(xié)議,這個從機地址可以是 7 位或 10 位。
3.傳輸方向:主機廣播傳輸方向,0是主機到從機(主機往從機寫),1為從機到主機(從機往主機寫)
4.從機應答:回復ack與nack,只有收到ack之后,主機繼續(xù)發(fā)送或者接收數(shù)據(jù)
寫數(shù)據(jù)
主機首先在 IIC 總線上發(fā)送起始信號,那么這時總線上的從機都會等待接收由主機發(fā)出的數(shù)據(jù)。主機接著發(fā)送從機地+0(寫操作)組成的 8bit 數(shù)據(jù),所有從機接收到該 8bit 數(shù)據(jù)后,自行檢驗是否是自己的設備的地址,假如是自己的設備地址,那么從機就會發(fā)出應答信號。主機在總線上接收到有應答信號后,才能繼續(xù)向從機發(fā)送數(shù)據(jù)。注意:IIC 總線上傳送的數(shù)據(jù)信號是廣義的,既包括地址信號,又包括真正的數(shù)據(jù)信號。
讀數(shù)據(jù)
若配置的方向傳輸位為“讀數(shù)據(jù)”方向,即第二幅圖的情況,廣播完地址,接收到應答信號后,從機開始向主機返回數(shù)據(jù)(DATA),數(shù)據(jù)包大小也為 8 位,從機每發(fā)送完一個數(shù)據(jù),都會等待主機的應答信號(ACK),重復這個過程,可以返回 N 個數(shù)據(jù),這個 N 也沒有大小限制。當主機希望停止接收數(shù)據(jù)時,就向從機返回一個非應答信號(NACK),則從機自動停止數(shù)據(jù)傳輸。
信號電平
起始 停止信號
當 SCL 線是高電平時 SDA 線從高電平向低電平切換,這個情況表示通訊的起始。當 SCL 是高電平時 SDA 線由低電平向高電平切換,表示通訊的停止。起始和停止信號一般由主機產(chǎn)生。
數(shù)據(jù)有效性 SCL的高電平
SCL為高電平的時候 SDA表示的數(shù)據(jù)有效,SCL為低電平時數(shù)據(jù)變換
地址及數(shù)據(jù)方向
I2C 總線上的每個設備都有自己的獨立地址,主機發(fā)起通訊時,通過 SDA 信號線發(fā)送設備地址(SLAVE_ADDRESS)來查找從機。I2C 協(xié)議規(guī)定設備地址可以是 7 位或 10 位,實際中 7 位的地址應用比較廣泛。緊跟設備地址的一個數(shù)據(jù)位用來表示數(shù)據(jù)傳輸方向。
讀數(shù)據(jù)方向時,主機會釋放對 SDA 信號線的控制,由從機控制 SDA 信號線,主機接收信號,寫數(shù)據(jù)方向時,SDA 由主機控制,從機接收信號。
大端(高有效位存在低地址),波形高位先行
ACK NACK
主站發(fā)送起始信號,地址,讀寫信號之后釋放SDA控制權(quán),從站開始自動控制SDA信號發(fā)送ACK NACK
整體控制邏輯
整體控制邏輯負責協(xié)調(diào)整個 I2C 外設,控制邏輯的工作模式根據(jù)我們配置的“控制寄存器(CR1/CR2)”的參數(shù)而改變。在外設工作時,控制邏輯會根據(jù)外設的工作狀態(tài)修改“狀態(tài)寄存器(SR1 和 SR2)”,我們只要讀取這些寄存器相關(guān)的寄存器位,就可以了解 I2C的工作狀態(tài)。除此之外,控制邏輯還根據(jù)要求,負責控制產(chǎn)生 I2C 中斷信號、DMA 請求及各種 I2C 的通訊信號(起始、停止、響應信號等)。
IIC使用 讀寫EEPROM為例
要點
(1) 配置通訊使用的目標引腳為開漏模式;
(2) 使能 I2C 外設的時鐘;
(3) 配置 I2C 外設的模式、地址、速率等參數(shù)并使能 I2C 外設;
(4) 編寫基本 I2C 按字節(jié)收發(fā)的函數(shù);
(5) 編寫讀寫 EEPROM 存儲內(nèi)容的函數(shù);
硬件IIC
配置IIC復用的GPIO
#配置 GPIO 復用
GPIO_InitTypeDef GPIO_InitStructure;/* 使能與 I2C 有關(guān)的時鐘 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB)/* 配置GPIO為開漏輸出模式 */
//配置Gpio初始化結(jié)構(gòu)體略
GPIO_Init(GPIOB, &GPIO_InitStructure);
1.配置IIC模式
I2C_InitTypeDef I2C_InitStructure;/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;/*!< 指定工作模式,可選 I2C 模式及 SMBUS 模式 *//* 高電平數(shù)據(jù)穩(wěn)定,低電平數(shù)據(jù)變化 SCL 時鐘線的占空比 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /*指定時鐘占空比,可選 low/high = 2:1 及 16:9 模式*//*!< 指定自身的 I2C 設備地址 */
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;/*!< 指定自身的 I2C 設備地址 */I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ; /*!< 使能或關(guān)閉響應(一般都要使能) *//* I2C 的尋址模式 */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /*!< 指定地址的長度,可為 7 位及 10 位 */I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /*!< 設置 SCL 時鐘頻率,此值要低于 400000*/I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
1.1作為主機端,進行數(shù)據(jù)發(fā)送
即作為 I2C 通訊的主機端時,對外部發(fā)送數(shù)據(jù)的過程
以寫一個E2ROM為例
(1) 控制產(chǎn)生起始信號(S),當發(fā)生起始信號后,它產(chǎn)生事件“EV5”,并會對 SR1 寄存器的“SB”位置 1,表示起始信號已經(jīng)發(fā)送;
(2) 緊接著發(fā)送設備地址并等待應答信號,若有從機應答,則產(chǎn)生事件“EV6”及“EV8”,這時 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 為 1 表示地址已經(jīng)發(fā)送,TXE 為 1 表示數(shù)據(jù)寄存器為空;
(3) 以上步驟正常執(zhí)行并對 ADDR 位清零后,我們往 I2C 的“數(shù)據(jù)寄存器 DR”寫入要發(fā)送的數(shù)據(jù),這時TXE位會被重置0,表示數(shù)據(jù)寄存器非空,I2C外設通過SDA信號線一位位把數(shù)據(jù)發(fā)送出去后,又會產(chǎn)生“EV8”事件,即 TXE 位被置 1,重復這個過程,就可以發(fā)送多個字節(jié)數(shù)據(jù)了;
(4) 當我們發(fā)送數(shù)據(jù)完成后,控制 I2C 設備產(chǎn)生一個停止信號§,這個時候會產(chǎn)生EV8_2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通訊結(jié)束。
假如我們使能了 I2C 中斷,以上所有事件產(chǎn)生時,都會產(chǎn)生 I2C 中斷信號,進入同一個中斷服務函數(shù),到 I2C 中斷服務程序后,再通過檢查寄存器位來判斷是哪一個事件。
IIC STM32固件庫函數(shù)鏈接: IIC STM32固件庫函數(shù)鏈接
1.1.1發(fā)送單字節(jié)數(shù)據(jù)
AT24C02的單字節(jié)時序規(guī)定,向它寫入數(shù)據(jù)的時候,第一個字節(jié)為內(nèi)存地址,第二個字節(jié)是要寫入的數(shù)據(jù)內(nèi)容。在發(fā)送device地址之后,需要發(fā)送eeprom的內(nèi)部存儲地址和需要存的數(shù)據(jù)
#define EEPROM_I2Cx I2C1/* 具體修改的寄存器位置,查看固件庫函數(shù)*/uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr){/* 產(chǎn)生 I2C 起始信號 *///I2Cx->CR1 |= I2C_CR1_START; 操作I2C中的CR1寄存器I2C_GenerateSTART(I2C1, ENABLE);/*設置超時等待時間*/I2CTimeout = I2CT_FLAG_TIMEOUT;//I2CTimeout 常數(shù)/* 檢測 EV5 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){/* 超時,報錯*/if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);}/* 發(fā)送 EEPROM 設備地址 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);/* 檢測 EV6 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);}/* 發(fā)送要寫入的 EEPROM 內(nèi)部地址(即 EEPROM 內(nèi)部存儲器的地址) *//* EEPROM 內(nèi)部存儲器讀寫的規(guī)則是第一個數(shù)據(jù)為要讀寫的地址,第二個數(shù)據(jù)是具體讀寫的數(shù)據(jù) */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV8 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);}/* 發(fā)送一字節(jié)要寫入的數(shù)據(jù) */I2C_SendData(EEPROM_I2Cx, *pBuffer);I2CTimeout = I2CT_FLAG_TIMEOUT;I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV8 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);}/* 發(fā)送停止信號 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);}
1.1.2發(fā)送多字節(jié)數(shù)據(jù)
等待存儲設備準備好
這個函數(shù)主要實現(xiàn)是向 EEPROM 發(fā)送它設備地址,檢測 EEPROM 的響應,若EEPROM 接收到地址后返回應答信號,則表示 EEPROM 已經(jīng)準備好,可以開始下一次通訊。函數(shù)中檢測響應是通過讀取 STM32 的 SR1 寄存器的 ADDR 位及 AF 位來實現(xiàn)的,當I2C 設備響應了地址的時候,ADDR 會置 1,若應答失敗,AF 位會置 1。
//等待 EEPROM 到準備狀態(tài)void I2C_EE_WaitEepromStandbyState(void){vu16 SR1_Tmp = 0;do {/* 發(fā)送起始信號 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);/* 讀 I2C1 SR1 寄存器 */SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);/* 發(fā)送 EEPROM 地址 + 寫方向 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);}// SR1 位 1 ADDR:1 表示地址發(fā)送成功,0 表示地址發(fā)送沒有結(jié)束// 等待地址發(fā)送成功while (!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));/* 清除 AF 位 */I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);/* 發(fā)送停止信號 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
循環(huán)單字節(jié)發(fā)送
uint8_t I2C_EE_ByetsWrite(uint8_t* pBuffer,uint8_t WriteAddr,uint16_t NumByteToWrite)
{uint16_t i;uint8_t res;/*每寫一個字節(jié)調(diào)用一次 I2C_EE_ByteWrite 函數(shù),前文的單字節(jié)寫入*/for (i=0; i<NumByteToWrite; i++){/*等待 EEPROM 準備完畢*/I2C_EE_WaitEepromStandbyState();/*按字節(jié)寫入數(shù)據(jù)*/res = I2C_EE_ByteWrite(pBuffer++,WriteAddr++);}return res;
}
1.1.3 EEPROM 的頁寫入
EEPROM 定義了一種頁寫入時序,只要告訴 EEPROM 第一個內(nèi)存地址 address1,后面的數(shù)
據(jù)按次序?qū)懭氲?address2、address3… 這樣可以節(jié)省通訊的時間,加快速度。
根據(jù)頁寫入時序,第一個數(shù)據(jù)被解釋為要寫入的內(nèi)存地址 address1,后續(xù)可連續(xù)發(fā)送 n個數(shù)據(jù),這些數(shù)據(jù)會依次寫入到內(nèi)存中。其中 AT24C02 型號的芯片頁寫入時序最多可以一次發(fā)送 8 個數(shù)據(jù)(即 n = 8 ),該值也稱為頁大小,某些型號的芯片每個頁寫入時序最多可傳輸 16 個數(shù)據(jù)。
但是這種方式還是比較慢,拘泥于頁的大小。
//在 EEPROM 的一個寫循環(huán)中可以寫多個字節(jié),但一次寫入的字節(jié)數(shù)不能超過 EEPROM 頁的大小,AT24C02 //每頁有 8 個字節(jié)
//NumByteToWrite:要寫的字節(jié)數(shù)要求 NumByToWrite 小于頁大小uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,uint8_t NumByteToWrite)
{ I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);}/* 產(chǎn)生 I2C 起始信號 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV5 事件并清除標志 */while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);}/* 發(fā)送 EEPROM 設備地址 */I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV6 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);}/* 發(fā)送要寫入的 EEPROM 內(nèi)部地址(即 EEPROM 內(nèi)部存儲器的地址) */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV8 事件并清除標志*/while (! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);}/* 循環(huán)發(fā)送 NumByteToWrite 個數(shù)據(jù) */while (NumByteToWrite--){/* 發(fā)送緩沖區(qū)中的數(shù)據(jù) */I2C_SendData(EEPROM_I2Cx, *pBuffer);/* 指向緩沖區(qū)中的下一個數(shù)據(jù) */pBuffer++;I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV8 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);}}/* 發(fā)送停止信號 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
1.1.4 EEPROM 的頁寫入plus
利用 EEPROM 的頁寫入方式,可以改進前面的“多字節(jié)寫入”函數(shù),加快傳輸速度
// AT24C01/02 每頁有 8 個字節(jié)#define I2C_PageSize 8void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite){u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0;/*mod 運算求余,若 writeAddr 是 I2C_PageSize 整數(shù)倍,運算結(jié)果 Addr 值為 0*//*判斷目標地址(起始地址)是否為一頁的開始*/Addr = WriteAddr % I2C_PageSize;/*差 count 個數(shù)據(jù)值,剛好可以對齊到頁地址*/count = I2C_PageSize - Addr;/*計算出要寫多少整數(shù)頁*/NumOfPage = NumByteToWrite / I2C_PageSize;/*mod 運算求余,計算出剩余不滿一頁的字節(jié)數(shù)*/NumOfSingle = NumByteToWrite % I2C_PageSize;// Addr=0,則 WriteAddr 剛好按頁對齊 aligned,也就是從這頁的第一個byte開始// 這樣就很簡單了,直接寫就可以,寫完整頁后, 把剩下的不滿一頁的寫完即可if (Addr == 0) {//從某頁的第一位寫起/* 如果 NumByteToWrite < I2C_PageSize *//* 總字數(shù)還不滿一頁 */if (NumOfPage == 0) {//總頁數(shù)不滿一頁I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* 如果 NumByteToWrite > I2C_PageSize */else {/*先把整數(shù)頁都寫了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);//寫一頁I2C_EE_WaitEepromStandbyState();//等待rom準備好WriteAddr += I2C_PageSize; //寫完一頁之后,下一頁的地址pBuffer += I2C_PageSize; //寫完一頁之后,要傳輸?shù)脑吹刂芬惨?/span>}/*若有多余的不滿一頁的數(shù)據(jù),把它寫完*/if (NumOfSingle!=0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}}從某頁的第一位寫起// 如果 WriteAddr 不是按 I2C_PageSize 對齊// 那就算出對齊到頁地址還需要多少個數(shù)據(jù),先把這幾個數(shù)據(jù)寫完,剩下開始的地址就已經(jīng)對齊else{//不是從某頁的第一位寫起/* 如果 NumByteToWrite < I2C_PageSize */if (NumOfPage== 0){//總數(shù)不滿一頁if (NumOfSingle > count) {//count:還需要寫多少個字節(jié)才能頁面對齊//NumOfSingle:當前頁面空閑的字節(jié)數(shù)// temp 的數(shù)據(jù)要寫到寫一頁temp = NumOfSingle - count;I2C_EE_PageWrite(pBuffer, WriteAddr, count);//寫count數(shù)目的到當前頁I2C_EE_WaitEepromStandbyState();//等待準備完畢WriteAddr += count;//目標地址移動pBuffer += count;I2C_EE_PageWrite(pBuffer, WriteAddr, temp);//在下一頁寫完剩下的I2C_EE_WaitEepromStandbyState();}else{ /*若 count 比 NumOfSingle 大*/I2C_EE_PageWrite(pBuffer, WriteAddr, NumByteToWrite);I2C_EE_WaitEepromStandbyState();}}/* 如果 NumByteToWrite > I2C_PageSize *//* 如果 不止寫一頁 */else { /* 如果 不止寫一頁 *//*地址不對齊多出的 count 分開處理,不加入這個運算*/NumByteToWrite -= count;NumOfPage = NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;/*先把 WriteAddr 所在頁的剩余字節(jié)寫了*/if (count != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, count);I2C_EE_WaitEepromStandbyState();/*WriteAddr 加上 count 后,地址就對齊到頁了*/WriteAddr += count;pBuffer += count;}/*把整數(shù)頁都寫了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitEepromStandbyState();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}/*若有多余的不滿一頁的數(shù)據(jù),把它寫完*/if (NumOfSingle != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}/*把整數(shù)頁都寫了*/}/* 如果 不止寫一頁 */ }
1.2作為主機端,進行數(shù)據(jù)接收
即作為 I2C 通訊的主機端時,從外部接收數(shù)據(jù)的過程
(1) 同主發(fā)送流程,起始信號(S)是由主機端產(chǎn)生的,控制發(fā)生起始信號后,它產(chǎn)生事件“EV5”,并會對 SR1 寄存器的“SB”位置 1,表示起始信號已經(jīng)發(fā)送;
(2) 緊接著發(fā)送設備地址并等待應答信號,若有從機應答,則產(chǎn)生事件“EV6”這時SR1 寄存器的“ADDR”位被置 1,表示地址已經(jīng)發(fā)送。
(3) 從機端接收到地址后,開始向主機端發(fā)送數(shù)據(jù)。當主機接收到這些數(shù)據(jù)后,會產(chǎn)生“EV7”事件,SR1 寄存器的 RXNE被置 1,表示接收數(shù)據(jù)寄存器非空,我們讀取該寄存器后,可對數(shù)據(jù)寄存器清空,以便接收下一次數(shù)據(jù)。此時我們可以控制I2C 發(fā)送應答信號(ACK)或非應答信號(NACK),若應答,則重復以上步驟接收數(shù)據(jù),若非應答,則停止傳輸;
(4) 發(fā)送非應答信號后,產(chǎn)生停止信號P,結(jié)束傳輸。
uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr,u16 NumByteToRead){I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);}/* 產(chǎn)生 I2C 起始信號 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV5 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);}/* 發(fā)送 EEPROM 設備地址 */I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV6 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);} /*通過重新設置 PE 位清除 EV6 事件 */I2C_Cmd(EEPROM_I2Cx, ENABLE);/* 發(fā)送要讀取的 EEPROM 內(nèi)部地址(即 EEPROM 內(nèi)部存儲器的地址) *//* EEPROM 內(nèi)部存儲器讀寫的規(guī)則是第一個數(shù)據(jù)為要讀寫的地址,第二個數(shù)據(jù)是具體讀寫的數(shù)據(jù) */I2C_SendData(EEPROM_I2Cx, ReadAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV8 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);}/* 產(chǎn)生第二次 I2C 起始信號 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT; /* 檢測 EV5 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);}/* 發(fā)送 EEPROM 設備地址 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 檢測 EV6 事件并清除標志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);}/* 讀取 NumByteToRead 個數(shù)據(jù)*/while (NumByteToRead){/*若 NumByteToRead=1,表示已經(jīng)接收到最后一個數(shù)據(jù)了,發(fā)送非應答信號,結(jié)束傳輸*/if (NumByteToRead == 1){/* 發(fā)送非應答信號 */I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);/* 發(fā)送停止信號 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);}I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);}{/*通過 I2C,從設備中讀取一個字節(jié)的數(shù)據(jù) */*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);/* 存儲數(shù)據(jù)的指針指向下一個地址 */pBuffer++;/* 接收數(shù)據(jù)自減 */NumByteToRead--;} } /* 讀取 NumByteToRead 個數(shù)據(jù)*//* 使能應答,方便下一次 I2C 傳輸 */I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);}
1.3作為從機端,進行數(shù)據(jù)接收
1.4作為從機端,進行數(shù)據(jù)接收
GPIO模擬IIC
我之前使用這套方法讀取一個外置的RTC模塊,參考正點原子的例程,這邊就不贅述了?;蛘咧笥袡C會把例程貼出來哈
1.配置GPIO
2.編寫微秒級軟延時
3.根據(jù)時序編寫各個函數(shù)
DMA 與IIC
可以使用DMA,對IIC的DR中要傳輸或者接收的數(shù)據(jù)進行搬運,DMA請求(當被使能時)僅用于數(shù)據(jù)傳輸。發(fā)送時數(shù)據(jù)寄存器變空或接收時數(shù)據(jù)寄存器變滿,則產(chǎn)生DMA請求。DMA請求必須在當前字節(jié)傳輸結(jié)束之前被響應。當為相應DMA通道設置的數(shù)據(jù)傳輸量已經(jīng)完成時,DMA控制器發(fā)送傳輸結(jié)束信號ETO到I2C接口,并且在中斷允許時產(chǎn)生一個傳輸完成中斷。
主發(fā)送器:在EOT中斷服務程序中,需禁止DMA請求,然后在等到BTF事件后設置停止條件。
主接收器:當要接收的數(shù)據(jù)數(shù)目大于或等于2時,DMA控制器發(fā)送一個硬件信號EOT_1,它對應DMA傳輸(字節(jié)數(shù)-1)。如果在I2C_CR2寄存器中設置了LAST位,硬件在發(fā)送完EOT_1后的下一個字節(jié),將自動發(fā)送NACK。在中斷允許的情況下,用戶可以在DMA傳輸完成的中斷服務程序中產(chǎn)生一個停止條件。
意思是說當DMA產(chǎn)生EOT標志后,(如果開啟了EOT相關(guān)中斷就進中斷程序,沒有開啟就進行軟件查詢做后續(xù)處理)關(guān)閉DMA請求,然后等待BTF事件,之后執(zhí)行STOP操作。 這里的BTF事件就是I2C數(shù)據(jù)收發(fā)過程中的數(shù)據(jù)字節(jié)是否傳輸完成的的事件。
IIC通訊過程 標志位
配置DMA
對DMA進行配置
在IIC的配置中開啟DMA
通過設置I2C_CR2寄存器中的DMAEN位可以激活DMA模式。只要TxE位被置位,數(shù)據(jù)將由DMA從預置的存儲區(qū)裝載進I2C_DR寄存器。為I2C分配一個DMA通道,須執(zhí)行以下步驟(x是通道號)。
利用DMA發(fā)送
-
在DMA_CPARx寄存器中設置I2C_DR寄存器地址。數(shù)據(jù)將在每個TxE事件后從存儲器傳
送至這個地址。 -
在DMA_CMARx寄存器中設置存儲器地址。數(shù)據(jù)在每個TxE事件后從這個存儲區(qū)傳送至
I2C_DR。 -
在DMA_CNDTRx寄存器中設置所需的傳輸字節(jié)數(shù)。在每個TxE事件后,此值將被遞減。
-
利用DMA_CCRx寄存器中的PL[0:1]位配置通道優(yōu)先級。
-
設置DMA_CCRx寄存器中的DIR位。
-
根據(jù)應用要求可以配置在整個傳輸完成一半或全部完成時發(fā)出中斷請求。
-
通過設置DMA_CCTx寄存器上的EN位激活通道。
當DMA控制器中設置的數(shù)據(jù)傳輸數(shù)目已經(jīng)完成時,DMA控制器給I2C接口發(fā)送一個傳輸結(jié)束的EOT/ EOT_1信號。在中斷允許的情況下,將產(chǎn)生一個DMA中斷。
如果使用DMA進行發(fā)送時,不要設置I2C_CR2寄存器的ITBUFEN位。
利用DMA接收
通過設置I2C_CR2寄存器中的DMAEN位可以激活DMA接收模式。每次接收到數(shù)據(jù)字節(jié)時,將由DMA把I2C_DR寄存器的數(shù)據(jù)傳送到設置的存儲區(qū)(參考DMA說明)。設置DMA通道進行I2C接收,須執(zhí)行以下步驟(x是通道號):
- 在DMA_CPARx寄存器中設置I2C_DR寄存器的地址。數(shù)據(jù)將在每次RxNE事件后從此地
址傳送到存儲區(qū)。 - 在DMA_CMARx寄存器中設置存儲區(qū)地址。數(shù)據(jù)將在每次RxNE事件后從I2C_DR寄存器
傳送到此存儲區(qū)。 - 在DMA_CNDTRx寄存器中設置所需的傳輸字節(jié)數(shù)。在每個RxNE事件后,此值將被遞
減。 - 用DMA_CCRx寄存器中的PL[0:1]配置通道優(yōu)先級。
- 清除DMA_CCRx寄存器中的DIR位,根據(jù)應用要求可以設置在數(shù)據(jù)傳輸完成一半或全部
完成時發(fā)出中斷請求。 - 設置DMA_CCRx寄存器中的EN位激活該通道。
當DMA控制器中設置的數(shù)據(jù)傳輸數(shù)目已經(jīng)完成時,DMA控制器給I2C接口發(fā)送一個傳輸結(jié)束的
EOT/ EOT_1信號。在中斷允許的情況下,將產(chǎn)生一個DMA中斷。
注: 如果使用DMA進行接收時,不要設置I2C_CR2寄存器的ITBUFEN位