本工程運行於野火MINI開發板,主控型號為STM32F103RC,讀寫對象為AT24C02 2Kbit容量的EEPROM
STM32的硬核I2C十分復雜,而且網上有說有缺陷,這就有意思了,值得一探究竟
I2C通信中各設備有主從之分,那么此處先從簡單的主模式下手,做一個簡單的讀寫EEPROM實驗
從AT24C02的規格書中看到,對它的操作有以下幾種
寫操作
BYTE WRITE
PAGE WRITE
ACKNOWLEDGE POLLING
讀操作
CURRENT ADDRESS READ
RANDOM READ
SEQUENTIAL READ
簡單梳理一下,POLLING操作適用於寫完之后等待存儲芯片搬運數據完成。PAGE WRITE和RANDOM READ既是基本的單字節讀寫。為了提高效率,寫操作支持8字節的頁寫,完整的頁寫需要地址邊緣對齊,不然寫進去的內容會從一頁的末端回滾到頁首,覆蓋原來的數據。而順序讀即完成一字節讀后回應ACK即可繼續讀下一個地址的數據,直到所有數據讀取完成回應NACK即可,讀的回滾是達到器件末地址后從首地址繼續
初始化I2C和GPIO
void mini_i2c_init(void) { // structure define I2C_InitTypeDef i2c_struct; GPIO_InitTypeDef gpio_struct; // clock enable USER_I2C_RCC_CMD(USER_I2C_RCC, ENABLE); GPIO_RCC_CMD(USER_I2C_GPIO_RCC, ENABLE); // GPIO initialization gpio_struct.GPIO_Mode = GPIO_Mode_AF_OD; gpio_struct.GPIO_Speed = GPIO_Speed_50MHz; gpio_struct.GPIO_Pin = USER_I2C_SCL | USER_I2C_SDA; GPIO_Init(USER_I2C_GPIO, &gpio_struct); // I2C initialization i2c_struct.I2C_Mode = I2C_Mode_I2C; i2c_struct.I2C_DutyCycle = I2C_DutyCycle_2; i2c_struct.I2C_OwnAddress1 = USER_I2C_OWN_ADDR; i2c_struct.I2C_Ack = I2C_Ack_Enable; i2c_struct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; i2c_struct.I2C_ClockSpeed = USER_I2C_SPEED; I2C_Init(USER_I2C, &i2c_struct); I2C_Cmd(USER_I2C, ENABLE); }
為了方便調試,配置按鈕並開啟外部中斷
void mini_pb_config(void) { // struct define GPIO_InitTypeDef gpio_struct; EXTI_InitTypeDef exti_struct; NVIC_InitTypeDef nvic_struct; // enable clock // enable AFIO!!! GPIO_RCC_CMD(AFIO_RCC, ENABLE); GPIO_RCC_CMD(PB_K1_RCC, ENABLE); GPIO_RCC_CMD(PB_K2_RCC, ENABLE); // GPIO initialization gpio_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpio_struct.GPIO_Pin = PB_K1; GPIO_Init(PB_K1_GPIO, &gpio_struct); GPIO_EXTILineConfig(PB_K1_PORTSOURCE, PB_K1_PINSOURCE); gpio_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpio_struct.GPIO_Pin = PB_K2; GPIO_Init(PB_K2_GPIO, &gpio_struct); GPIO_EXTILineConfig(PB_K2_PORTSOURCE, PB_K2_PINSOURCE); // EXTI initialization exti_struct.EXTI_Mode = EXTI_Mode_Interrupt; exti_struct.EXTI_Trigger = EXTI_Trigger_Rising; exti_struct.EXTI_Line = PB_K1_EXTILINE; exti_struct.EXTI_LineCmd = ENABLE; EXTI_Init(&exti_struct); exti_struct.EXTI_Mode = EXTI_Mode_Interrupt; exti_struct.EXTI_Trigger = EXTI_Trigger_Rising; exti_struct.EXTI_Line = PB_K2_EXTILINE; exti_struct.EXTI_LineCmd = ENABLE; EXTI_Init(&exti_struct); // NVIC initialization nvic_struct.NVIC_IRQChannel = PB_K1_IRQ; nvic_struct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_struct); nvic_struct.NVIC_IRQChannel = PB_K2_IRQ; nvic_struct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_struct); }
然后可以在中斷服務函數調用I2C讀寫操作
void PB_K1_IRQ_HANDLER(void) { if(EXTI_GetITStatus(PB_K1_EXTILINE) != RESET) { EXTI_ClearITPendingBit(PB_K1_EXTILINE); mini_i2c_sequential_read(0x00, i2c_rx_data, 8); LED_D4_TOGGLE; } } void PB_K2_IRQ_HANDLER(void) { if(EXTI_GetITStatus(PB_K2_EXTILINE) != RESET) { EXTI_ClearITPendingBit(PB_K2_EXTILINE); mini_i2c_page_write(0x00, page_data); LED_D5_TOGGLE; } } void DEBUG_UART_IRQ_HANDLER(void) { if(USART_GetITStatus(DEBUG_UART, USART_IT_RXNE) != RESET) { USART_ClearITPendingBit(DEBUG_UART, USART_IT_RXNE); uint16_t data = USART_ReceiveData(DEBUG_UART); USART_SendData(DEBUG_UART, data); } }
字節寫功能的實現最為簡單,依次調用開始,設備地址,字地址,數據操作的庫函數,並且在每次操作完檢查對應的事件或是標志位
void mini_i2c_byte_write(uint8_t word_addr, uint8_t data) { // start I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); // device address I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET); // EEPROM address I2C_SendData(USER_I2C, word_addr); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET); I2C_SendData(USER_I2C, data); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); I2C_GenerateSTOP(USER_I2C, ENABLE); }
頁寫操作即將原來的寫一次數據改為寫八次數據,並在最后一次寫數據之后檢查EV8_2
// AT24C02 accept 8-byte page write void mini_i2c_page_write(uint8_t word_addr, uint8_t *page_data) { // S I2C_GenerateSTART(USER_I2C, ENABLE); // EV5: SB=1, cleared by reading SR1 register followed by writing DR register with Address while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); // Address I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter); // EV6: ADDR=1, cleared by reading SR1 register followed by reading SR2 while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET); // word address I2C_SendData(USER_I2C, word_addr); for(uint8_t i=0;i<8;i++) { // EV8: TxE=1, shift register not empty, data regiter empty, cleared by writing DR register while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == RESET); I2C_SendData(USER_I2C, page_data[i]); } // EV8_2: TxE=1, BTF=1, Program Stop request. TxE and BTF are cleared by hardware by the Stop condition while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET); I2C_GenerateSTOP(USER_I2C, ENABLE); }
相對之下,讀操作則復雜的多,且有兩種操作方式,這里使用的是方式二,關於每種方式的使用場景,參考手冊中有如下說明
Method 2: This method is for the case when the I2C is used with interrupts that do not have the highest priority in the application or when the I2C is used with polling
本實驗使用的就是POLLING的方式,所用用方式二,麻煩之處是對應讀一個字節,兩個字節和三個字節及以上還需要不同處理
void mini_i2c_sequential_read(uint8_t word_addr, uint8_t *data, uint32_t nbyte) { // start I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); // device address I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET); // register address I2C_SendData(USER_I2C, word_addr); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); // restart I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); // device address I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Receiver); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_ADDR) == RESET); if(nbyte == 1) { // EV6_3 I2C_AcknowledgeConfig(USER_I2C, DISABLE); USER_I2C->SR2; I2C_GenerateSTOP(USER_I2C, ENABLE); // EV7 while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET); *data = I2C_ReceiveData(USER_I2C); I2C_AcknowledgeConfig(USER_I2C, ENABLE); } else if(nbyte == 2) { // set pos flag I2C_NACKPositionConfig(USER_I2C, I2C_NACKPosition_Next); // EV6 USER_I2C->SR2; // EV6_1 I2C_AcknowledgeConfig(USER_I2C, DISABLE); // EV7_3 while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); I2C_GenerateSTOP(USER_I2C, ENABLE); *data = I2C_ReceiveData(USER_I2C); *(data+1) = I2C_ReceiveData(USER_I2C); I2C_AcknowledgeConfig(USER_I2C, ENABLE); } else { // EV6 USER_I2C->SR2; for(uint32_t i=0;i<nbyte-3;i++) { // EV7, using BTF instea of RXNE, refering Discovering STM32 Microcontroller while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); *(data+i) = I2C_ReceiveData(USER_I2C); } // EV7_2 while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); I2C_AcknowledgeConfig(USER_I2C, DISABLE); *(data+nbyte-3) = I2C_ReceiveData(USER_I2C); I2C_GenerateSTOP(USER_I2C, ENABLE); *(data+nbyte-2) = I2C_ReceiveData(USER_I2C); // EV7 while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET); *(data+nbyte-1) = I2C_ReceiveData(USER_I2C); I2C_AcknowledgeConfig(USER_I2C, ENABLE); } }
代碼中有少量注釋,可參照參考手冊
讀寫變量定義如下
uint8_t page_data[8] = {0x08, 0x07, 0x01, 0x06, 0x02, 0x05, 0x03, 0x04}; uint8_t i2c_rx_data[8];
寫入截圖
讀取截圖
且可以連續操作,功能驗證OK