I2C協議(中)——硬體I2C的簡單讀寫實驗
I2C_InitTypeDef
圖14-1
- I2C_ClockSpeed:即時鐘頻率,不得高於400kHz;
- I2C_Mode:可選擇I2C模式、SMBus主從模式;
- I2C_DutyCycle:SCL線時鐘的占空比,可選2:1和16:9;
- I2C_OwnAddress1:I2C設備自身的地址;
- I2C_Ack:應答使能,使能後可以發送響應信號;
- I2C_AcknowledgedAddress:地址長度,可設置7位或10位。
讀寫實驗
我們通過串口把讀和寫的數據列印出來,以便我們校對程序運行。這個實驗分成寫數據和讀數據兩部分介紹。
如圖14-2,為EEPROM的晶元原理圖,我們使用它進行讀寫實驗。
圖14-2從原理圖中可以知道,SCL和SDA分別連接到了PB6和PB7兩個引腳。晶元的設備地址共有7位,高四位為1010(b),低三位的值由A0、A1、A2三條信號線的電平決定,如此圖中的連接,則此設備地址為1010000(b),即0x50。地址後還跟著一個讀寫位,共8位,當讀寫位為0時表示寫數據,則值為0xA0,一般稱其為「寫地址」,當讀寫位為1時表示讀數據,值為0xA1,一般稱其為「讀地址」。
先進行I2C的GPIO配置,開啟I2C和GPIO埠的時鐘,選擇SCL和SDA的對應引腳,並設置引腳模式為復用開漏輸出。具體的配置代碼就不放出了,專欄里的其他文章有類似的配置代碼,可參考。另外,關於串口的初始化配置在之前的文章也有說明。
接下來配置I2C,其實和GPIO配置一樣,都是對相應初始化結構體的配置。對結構體成員逐一進行配置。
/** * @brief I2C工作模式配置 * @param 無 * @retval 無 */static void I2C_Mode_Config(void){ I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C模式 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // SCL線的時鐘占空比 I2C_InitStructure.I2C_OwnAddress1 = 0x0A; // I2C自身設備地址 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // I2C應答使能 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位定址模式 I2C_InitStructure.I2C_ClockSpeed = 400000; // I2C時鐘頻率 I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE);}
寫數據
/** * @brief 寫一個位元組到I2C EEPROM中 * @param * @arg WriteAddr:寫地址 * @arg pBuffer:緩衝區指針 * @retval 無 */uint32_t I2C_ByteWrite(u8 WriteAddr, u8 pBuffer) { //產生起始信號 I2C_GenerateSTART(I2C1, ENABLE); //檢測EV5事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR); //發送設備地址 I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); //檢測EV6事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR); //發送要寫入的內存單元地址 I2C_SendData(I2C1, WriteAddr); //檢測EV8事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR); //發送要寫入的數據 I2C_SendData(I2C1, pBuffer); //檢測EV8_2事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR); //產生停止信號 I2C_GenerateSTOP(I2C1, ENABLE);}
這段寫入一個位元組的程序是按照I2C協議(上)——基礎介紹里的時序完成的。每進行一個動作都會產生相應事件,我們需要檢測對應的事件以確保動作完成。
讀數據
而讀數據比寫數據多了一次起始信號,第一次的起始信號是為了寫入設備地址和內存單元地址,第二次才是讀數據方向。
/** * @brief 讀一個位元組到I2C EEPROM中 * @param * @arg WriteAddr:寫地址 * @arg pBuffer:緩衝區指針 * @arg numByteToRead:讀取數據量 * @retval 無 */uint32_t I2C_Read(u8 WriteAddr, u8* pBuffer, u8 numByteToRead) { //產生起始信號 I2C_GenerateSTART(I2C1, ENABLE); //檢測EV5事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR); //發送設備地址 I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); //檢測EV6事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR); //發送要操作的內存單元地址 I2C_SendData(I2C1, WriteAddr); //檢測EV8事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR); //第二次起始信號 I2C_GenerateSTART(I2C1, ENABLE); //檢測EV5事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR); //發送設備地址 I2C_Send7bitAddress(I2C1, 0xA1, I2C_Direction_Receiver); //檢測EV6事件 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == ERROR); while(numByteToRead) { if(numByteToRead == 1) { I2C_AcknowledgeConfig(I2C1, DISABLE); } while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) == ERROR); *pBuffer = I2C_ReceiveData(I2C1); pBuffer++; numByteToRead--; } //產生停止信號 I2C_GenerateSTOP(I2C1, ENABLE); }
在調試時發現,stm32頻率太高,I2C跟不上,導致在讀數據過程中卡住了。這涉及到AcknowledgePolling,即應答輪詢,我們需要在寫完數據後檢測I2C匯流排的狀態,等待EEPROM準備完畢。
/** * @brief 等待EEPROM準備完畢 * @param 無 * @retval 無 */ void I2C_WaitEepromStandbyState(void) { do { I2C_GenerateSTART(I2C1, ENABLE); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_SB) == RESET); I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); }while(I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR) == RESET); I2C_GenerateSTOP(I2C1, ENABLE); }
EEPROM提供了頁寫入的方式,可以連續讀取多個位元組,需要注意的是,在讀取數據時地址必須對齊。
-----------------------------------------------------------------------
文章首發於知乎專欄 - stm32,轉載請私信,並註明原文出處。
推薦閱讀: