STM32 I2C读写EEPROM(中断模式)
上一篇博客是使用I2C读写EERPOM的初级版本,这次在上次的基础上添加中断,使用DMA传输控制,新添加两个读写函数
采用中断方式和DMA可以充分利用单片机强大的硬件外设,提高整体运行效率,而且,在编程上也更为便捷
新的页写函数如下
// AT24C02 accept 8-byte page write void mini_i2c_page_write(uint8_t *page_data, uint8_t word_addr, uint8_t *data_num_pointer) { global_data_num_pointer = data_num_pointer; // 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, EEPROM_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); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET); mini_i2c_dma_config((uint32_t)page_data, *data_num_pointer, USER_I2C_DMA_DIR_TX); DMA_Cmd(USER_I2C_DMA_CH_TX, ENABLE); }
代码中注释掉的部分是原来的设计,在它下面是新的设计,首先配置好DMA,然后再开启它
void mini_i2c_dma_config(uint32_t mem_addr, uint32_t buffer_size, uint32_t direction) { dma_struct.DMA_MemoryBaseAddr = mem_addr; dma_struct.DMA_DIR = direction; dma_struct.DMA_BufferSize = buffer_size; if(direction == USER_I2C_DMA_DIR_TX) { DMA_Init(USER_I2C_DMA_CH_TX, &dma_struct); } else { DMA_Init(USER_I2C_DMA_CH_RX, &dma_struct); } }
DMA初始化结构体为全局变量, 在I2C初始化时已经初始化了该结构体的其他成员变量,此处需要配置一个读写的存储地址,数据量和方向
void mini_i2c_init(void) { // structure define I2C_InitTypeDef i2c_struct; GPIO_InitTypeDef gpio_struct; NVIC_InitTypeDef nvic_struct; // clock enable USER_I2C_RCC_CMD(USER_I2C_RCC, ENABLE); GPIO_RCC_CMD(AFIO_RCC, ENABLE); GPIO_RCC_CMD(USER_I2C_GPIO_RCC, ENABLE); USER_I2C_DMA_RCC_CMD(USER_I2C_DMA_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 DMA NVIC initialization NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); nvic_struct.NVIC_IRQChannel = USER_I2C_DMA_TX_IRQ; nvic_struct.NVIC_IRQChannelPreemptionPriority = USER_I2C_DMA_PREPRIO; nvic_struct.NVIC_IRQChannelSubPriority = USER_I2C_DMA_SUBPRIO; nvic_struct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_struct); nvic_struct.NVIC_IRQChannel = USER_I2C_DMA_RX_IRQ; NVIC_Init(&nvic_struct); // I2C DMA DMA_DeInit(USER_I2C_DMA_CH_TX); dma_struct.DMA_PeripheralBaseAddr = USER_I2C_DR_ADDR; dma_struct.DMA_MemoryBaseAddr = 0; // will be reconfigure dma_struct.DMA_DIR = DMA_DIR_PeripheralDST; // will be reconfigure dma_struct.DMA_BufferSize = 0xFFFF; // will be reconfigure dma_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; dma_struct.DMA_MemoryInc = DMA_MemoryInc_Enable; dma_struct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; dma_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; dma_struct.DMA_Mode = DMA_Mode_Normal; dma_struct.DMA_Priority = DMA_Priority_VeryHigh; dma_struct.DMA_M2M = DMA_M2M_Disable; DMA_Init(USER_I2C_DMA_CH_TX, &dma_struct); DMA_DeInit(USER_I2C_DMA_CH_RX); DMA_Init(USER_I2C_DMA_CH_RX, &dma_struct); DMA_ITConfig(USER_I2C_DMA_CH_TX, DMA_IT_TC, ENABLE); DMA_ITConfig(USER_I2C_DMA_CH_RX, DMA_IT_TC, ENABLE); // 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); I2C_DMACmd(USER_I2C, ENABLE); }
这段I2C初始化代码添加了中断和DMA部分,且如上所说,DMA初始化结构体为全局变量,其中有三个成员变量需要在具体的读写函数中配置
下面的I2C写函数移植的官方给的示例工程
void mini_i2c_write(uint8_t *data, uint8_t word_addr, uint16_t data_num) { uint8_t num_page = 0, num_single = 0, count = 0, addr = 0; addr = word_addr % EEPROM_PAGESIZE; count = EEPROM_PAGESIZE - addr; num_page = data_num / EEPROM_PAGESIZE; num_single = data_num % EEPROM_PAGESIZE; // if word_addr is EEPROM_PAGESIZE aligned if(addr == 0) { // if data_num < EEPROM_PAGESIZE if(num_page == 0) { global_data_num = num_single; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); } // if data_num > EEPROM_PAGESIZE else { while(num_page--) { global_data_num = EEPROM_PAGESIZE; mini_i2c_page_write(data, word_addr, &global_data_num); while(global_data_num > 0); mini_i2c_wait_standby(); data += EEPROM_PAGESIZE; word_addr += EEPROM_PAGESIZE; } if(num_single != 0) { global_data_num = num_single; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); } } } else { // if data_num < EEPROM_PAGESIZE if(num_page == 0) { // if the number of data to be written is more than // the remaining space in the current page if(data_num > count) { // first part global_data_num = count; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); // second part global_data_num = data_num - count; mini_i2c_page_write(data+count, word_addr+count, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); } else { global_data_num = num_single; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); } } // data_num > EEPROM_PAGESIZE else { data_num -= count; num_page = data_num / EEPROM_PAGESIZE; num_single = data_num % EEPROM_PAGESIZE; if(count != 0) { global_data_num = count; mini_i2c_page_write(data, word_addr, &global_data_num); while(global_data_num > 0); mini_i2c_wait_standby(); data += count; word_addr += count; } while(num_page--) { global_data_num = EEPROM_PAGESIZE; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); data += EEPROM_PAGESIZE; word_addr += EEPROM_PAGESIZE; } if(num_single != 0) { global_data_num = num_single; mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num)); while(global_data_num > 0); mini_i2c_wait_standby(); } } } }
I2C读函数也同样移植的官方示例工程
// method 1 void mini_i2c_read(uint8_t *data, uint8_t word_addr, uint8_t data_num) { I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Transmitter); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET); I2C_SendData(USER_I2C, word_addr); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Receiver); if(data_num == 1) { while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_ADDR) == RESET); I2C_AcknowledgeConfig(USER_I2C, DISABLE); USER_I2C->SR2; I2C_GenerateSTOP(USER_I2C, ENABLE); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET); *data = I2C_ReceiveData(USER_I2C); while(USER_I2C->CR1 & I2C_CR1_STOP); I2C_AcknowledgeConfig(USER_I2C, ENABLE); } else { while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == RESET); mini_i2c_dma_config((uint32_t)data, data_num, USER_I2C_DMA_DIR_RX); // Inform the DMA that the next End Of Transfer Signal will be the last one I2C_DMALastTransferCmd(USER_I2C, ENABLE); DMA_Cmd(USER_I2C_DMA_CH_RX, ENABLE); } }
I2C写函数中调用的时页写函数,每次写完一页后需要等待EERPOM操作完成,等待函数如下
// after write operation, EEPROM need some time void mini_i2c_wait_standby(void) { uint8_t busy = 1; uint16_t tmp_sr; while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BUSY)); while(busy) { I2C_GenerateSTART(USER_I2C, ENABLE); while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET); I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Transmitter); tmp_sr = USER_I2C->SR1; // check if the EEPROM responded do { tmp_sr = USER_I2C->SR1; } while((tmp_sr & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0); if(tmp_sr & I2C_SR1_ADDR) { USER_I2C->SR2; I2C_GenerateSTOP(USER_I2C, ENABLE); busy = 0; } else { I2C_ClearFlag(USER_I2C, I2C_FLAG_AF); } } }
在读写函数中开启DMA后,就等待DMA传输完成开启中断,然后在中断服务函数中关闭DMA且调用I2C结束函数
void USER_I2C_DMA_TX_IRQ_HANDLER(void) { if(DMA_GetITStatus(USER_I2C_DMA_IT_TX_TC) != RESET) { // GL is used to clear all flag DMA_ClearITPendingBit(USER_I2C_DMA_IT_TX_GL); DMA_Cmd(USER_I2C_DMA_CH_TX, DISABLE); while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET); I2C_GenerateSTOP(USER_I2C, ENABLE); USER_I2C->SR1; USER_I2C->SR2; *global_data_num_pointer = 0; } } void USER_I2C_DMA_RX_IRQ_HANDLER(void) { if(DMA_GetITStatus(USER_I2C_DMA_IT_RX_TC) != RESET) { DMA_ClearITPendingBit(USER_I2C_DMA_IT_RX_GL); I2C_GenerateSTOP(USER_I2C, ENABLE); DMA_Cmd(USER_I2C_DMA_CH_RX, DISABLE); } }
如上一个实验,I2C读写函数调用在按键触发的外部中断的服务函数中调用,此处如果不注意对中断分组和中断优先级的配置的话,会出错。比如我在调试中碰到的问题,未将按键中断的抢断优先级和I2C DMA的抢断优先级区分开,这样在按键中断中调用I2C写函数时,就会导致DMA传输完成后无法进入中断而导致出错