这篇帖子是准备在上个月就写的,但是由于设计PCB时未考虑硬件IIC的问题,导致IIC的引脚不在硬件GPIO上,又重新打板贴片,耽搁了两周。
我的硬件:

这里先容我的硬件其实有点摧残浪费蹂躏感情,先容这个的缘故原由是我在调试的过程中碰着几个问题,均和我的硬件有关,虽然问题和IIC无关,但是我还是想分享一下,往后有朋友碰着也可规避。
MCU利用的是GDF32E230F4,外设情形如下:
flash只有16KB,封装是LGA20,资源少,封装小。这样就导致价格会很便宜,但是我没用过如此小的flash和资源如此少的MCU,这就导致我后面涌现了事件。
我们的项目实在只用了一起SPI,一起485,一起IIC和两个外部中断,所用资源实在不多,但是用在这块电影上就闲的很拥挤了。所用IO如下所示:
3脚PA0用于后续的休眠唤醒,所剩资源仅仅只有PA1,PA2,PA3。由于考虑PCB版面走线和布局问题,我们直接取消外部晶振,将PF0和PF1作为IIC利用。
这里就涌现了第一个问题,我们须要在收到RX引脚的第一个低落沿触发外部中断,为了走线方便,将烧录口SWDIO和RX对接,在烧录完程序之后SWDIO会用作普通GPIO,结果导致打板回来后无法烧录代码,把485芯片摘掉则无此问题,于是又要重新打板贴片,我就趁着这功夫调试了一下硬件IIC。
开始调试代码:
首先初始化所用的外设时钟:
void rcu_config(void){ / enable GPIOA,F clock / rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOF); / enable I2C0 clock / rcu_periph_clock_enable(RCU_I2C0); / enable DMA0 clock / rcu_periph_clock_enable(RCU_DMA);
然后初始化IIC。
void i2c_config(void){ / connect PF1 to I2C0_SCL / / connect PF0 to I2C0_SDA / gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_0); gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_1); / configure GPIO pins of I2C / gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0); gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_0); gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1); gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_1); / configure I2C clock / i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2); / configure I2C address / i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C0_SLAVE_ADDRESS7); / enable I2C0 / i2c_enable(I2C0); / enable acknowledge / i2c_ack_config(I2C0, I2C_ACK_ENABLE);
上面的GPIO的初始化一样平常没什么好说的,IO设置为开漏输出模式,由于IIC总线有读和写两个操作,开漏是最好的,这里说的是最好,实在设置为推挽输出也是可以的,这里有必要提一下,由于上周在论坛看到有的兄弟在调试SPI时,拿到的例程,GPIO设置为推挽输出,导致很迷惑,设置为输出如何去读SPI的数据。
这里有一个冷知识:只要使能了GPIO,无论你设置GPIO的模式为输入还是输出,在GPIO上有数据来时,GPIO的数据寄存器都能将数据存进寄存器内部,此时去读数据寄存器就能获取到数据,道理便是这么个道理,可能我描述的不是很准确,但是一定是可以的。
初始化GPIO之后便是调用库函数对IIC进行初始化。
i2c_clock_config()函数的三个参数,第一个便是选定哪个I2C,第二个是设置I2C的速率,这里因此宏定义的办法定义的,速率为100000,第三个设置快速模式下的占空比,如果是速率在100KHz及以下,利用参数I2C_DTCY_2,如果是100KHz-1MHz,则利用I2C_DTCY_16_9。最高只支持1M。
IIC利用DMA写24C02:
void eeprom_buffer_write_dma_timeout(uint8_t p_buffer, uint8_t write_address, uint16_t number_of_byte){ uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0; address = write_address % I2C_PAGE_SIZE; count = I2C_PAGE_SIZE - address; number_of_page =number_of_byte / I2C_PAGE_SIZE; number_of_single = number_of_byte % I2C_PAGE_SIZE; / if write_address is I2C_PAGE_SIZE aligned/ if(0 == address){ while(number_of_page--){ eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE); eeprom_wait_standby_state_timeout(); write_address +=I2C_PAGE_SIZE; p_buffer += I2C_PAGE_SIZE; } if(0 != number_of_single){ eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single); eeprom_wait_standby_state_timeout(); } }else{ / if write_address is not I2C_PAGE_SIZE aligned / if(number_of_byte < count){ eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_byte); eeprom_wait_standby_state_timeout(); }else{ number_of_byte -= count; number_of_page =number_of_byte / I2C_PAGE_SIZE; number_of_single = number_of_byte % I2C_PAGE_SIZE; if(0 != count){ eeprom_page_write_dma_timeout(p_buffer, write_address, count); eeprom_wait_standby_state_timeout(); write_address += count; p_buffer += count; } / write page / while(number_of_page--){ eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE); eeprom_wait_standby_state_timeout(); write_address +=I2C_PAGE_SIZE; p_buffer += I2C_PAGE_SIZE; } / write single / if(0 != number_of_single){ eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single); eeprom_wait_standby_state_timeout(); } } }}
IIC利用DMA读24C02:
uint8_t eeprom_page_write_dma_timeout(uint8_t p_buffer, uint8_t write_address, uint8_t number_of_byte){ dma_parameter_struct dma_init_struct; uint8_t state = I2C_START; uint16_t timeout = 0; uint8_t i2c_timeout_flag = 0; while(!(i2c_timeout_flag)){ switch(state){ case I2C_START: / i2c master sends start signal only when the bus is idle / while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ i2c_start_on_bus(I2CX); timeout = 0; state = I2C_SEND_ADDRESS; }else{ i2c_bus_reset(); timeout = 0; state = I2C_START; printf("i2c bus is busy in PAGE WRITE!\n"); } break; case I2C_SEND_ADDRESS: / i2c master sends START signal successfully / while((!i2c_flag_get(I2CX, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ i2c_master_addressing(I2CX, eeprom_address, I2C_TRANSMITTER); timeout = 0; state = I2C_CLEAR_ADDRESS_FLAG; }else{ timeout = 0; state = I2C_START; printf("i2c master sends start signal timeout in PAGE WRITE!\n"); } break; case I2C_CLEAR_ADDRESS_FLAG: / address flag set means i2c slave sends ACK / while((!i2c_flag_get(I2CX, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ i2c_flag_clear(I2CX, I2C_FLAG_ADDSEND); timeout = 0; state = I2C_TRANSMIT_DATA; }else{ timeout = 0; state = I2C_START; printf("i2c master clears address flag timeout in PAGE WRITE!\n"); } break; case I2C_TRANSMIT_DATA: / wait until the transmit data buffer is empty / while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ / send the EEPROM's internal address to write to : only one byte address / i2c_data_transmit(I2CX, write_address); timeout = 0; }else{ timeout = 0; state = I2C_START; printf("i2c master sends EEPROM's internal address timeout in PAGE WRITE!\n"); } / wait until BTC bit is set / while(!i2c_flag_get(I2CX, I2C_FLAG_BTC)); dma_deinit(DMA_CH1); dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.memory_addr = (uint32_t)p_buffer; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_struct.number = number_of_byte; dma_init_struct.periph_addr = (uint32_t)&I2C_DATA(I2CX); dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA_CH1, &dma_init_struct); / enable I2CX DMA / i2c_dma_enable(I2CX, I2C_DMA_ON); / enable DMA0 channel1 / dma_channel_enable(DMA_CH1); / wait until full transfer finish flag is set / while(!dma_flag_get(DMA_CH1, DMA_FLAG_FTF)); / wait until BTC bit is set / while((!i2c_flag_get(I2CX, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ timeout = 0; state = I2C_STOP; }else{ timeout = 0; state = I2C_START; printf("i2c master sends data timeout in PAGE WRITE!\n"); } break; case I2C_STOP: / send a stop condition to I2C bus / i2c_stop_on_bus(I2CX); / i2c master sends STOP signal successfully / while((I2C_CTL0(I2CX) & 0x0200) && (timeout < I2C_TIME_OUT)){ timeout++; } if(timeout < I2C_TIME_OUT){ timeout = 0; state = I2C_END; i2c_timeout_flag = I2C_OK; }else{ timeout = 0; state = I2C_START; printf("i2c master sends stop signal timeout in PAGE WRITE!\n"); } break; default: state = I2C_START; i2c_timeout_flag = I2C_OK; timeout = 0; printf("i2c master sends start signal in PAGE WRITE!\n"); break; } } return I2C_END;}
利用这两个函数只需传入须要操作的数组,页的地址和读写的数据量便可,这里贴一下测试的函数:
uint8_t i2c_24c02_test(void){ uint16_t i; printf("\r\nAT24C02 writing...\r\n"); / initialize i2c_buffer_write / for(i = 0; i < BUFFER_SIZE; i++){ i2c_buffer_write[i] = i; printf("0x%02X ", i2c_buffer_write[i]); if(15 == i%16){ printf("\r\n"); } } / EEPROM data write / eeprom_buffer_write_dma_timeout(i2c_buffer_write, EEP_FIRST_PAGE, BUFFER_SIZE); printf("AT24C02 reading...\r\n"); / EEPROM data read / eeprom_buffer_read_dma_timeout(i2c_buffer_read, EEP_FIRST_PAGE, BUFFER_SIZE); / compare the read buffer and write buffer / for(i = 0; i < BUFFER_SIZE; i++){ if(i2c_buffer_read[i] != i2c_buffer_write[i]){ printf("0x%02X ", i2c_buffer_read[i]); printf("Err:data read and write aren't matching.\n\r"); return I2C_FAIL; } printf("0x%02X ", i2c_buffer_read[i]); if(15 == i%16){ printf("\r\n"); } } printf("I2C-AT24C02 test passed!\n\r"); return I2C_OK;
参数定义:
#define EEPROM_BLOCK0_ADDRESS 0xA0#define BUFFER_SIZE 256uint16_t eeprom_address;uint8_t i2c_buffer_write[BUFFER_SIZE];uint8_t i2c_buffer_read[BUFFER_SIZE];uint8_t i2c_buffer_read1[BUFFER_SIZE];#define I2C_TIME_OUT (uint16_t)(5000)#define I2C_TIME_OUT1(uint32_t)(200000)#define EEP_FIRST_PAGE 0x00#define I2C_OK 1#define I2C_FAIL 0#define I2C_END 1#define I2CX I2C0
一开始向发送数组中添补256个数据,然后调用写函数将256个数据写进24C02,由于24C02只有一页,以是页数设置为0,写完后再读出数据,校验写入和读出的数据是否同等。
这里我便碰着了第二个坑,写完之后调试不通过,代码卡去世在IIC的时钟初始化i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2);,连续进入这个函数,发下卡去世在:
这里仅仅是一个数据的运算,卡去世在这里明显不合理。这时候我想到了上周调试报错:\output\Project.axf: Error: L6406E: No space in execution regions with .ANY selector matching usart.o(.text.RS_485_SEND).当时是内存溢出了,于是我看了一下工程的map文件:
果真,已经快要顶不住了,于是果断修正keil的编译优化选项,将优化等级提升为1级,内存缩小很多,问题便可解除,由于之前没用过这么下的MCU,而且这次编译没报错,若不是上次报错让我找到缘故原由,这次不知又要卡多久。
至此代码都是只能读写24c02,也便是操作一页。如果是更大的EEPROM器件,那么该如何操作。这里轻微吐槽一下:为啥我会用IIC来操作24C04,由于我的硬件板子上焊接的便是24C04,而为啥是24C04,并不是24C02不足用,只是由于04比02便宜,市场都畸形了吗?而我为啥要吐槽,是由于下文碰着的第三个问题。
扩充到24C04:
既然知道如何写一页,那么纵然再大的容量,我们也有办法去操作,以24C04为例,只是比24C02多了一页,地址为0x01;那么我们增加一个宏定义:
定义一下第二页的地址,然后定义一个数组去吸收我们读到的数据:
末了在测试程序中添加读写第二页的操作:
测试的时候再次翻车,第一次debug没问题,第二次debug的时候程序卡去世在第一个写EEPROM的函数,我预测难道又是数据超了,但是只增加了一个数组啊,代码量该当不会超标啊,于是把添加的代码全部删除,结果还是失落败。
办理方法:烧录之前先擦除全部flash,再烧录便可成功。
这个缘故原由是为啥,我还没找到,希望有知道的大佬可以给个答案,如果后续知道答案我再补上。
查看完全资源包请移步至文末原贴。
原标题:【国货之光】GD32E230F4利用硬件IIC+DMA读写24C04原作者:呐咯密密本文为21ic有奖征文作品,详情请见21ic论坛活动专区:第二届万元红包——蓝V达人有奖征文活动,如果您也有兴趣参与征文,欢迎进入论坛参与活动~










