本工程运行于野火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