在很久很久以前,我就写过GD32E230替换STM32F031的帖子,主要介绍了USART和SPI的外设移植开发,当时IIC使用的是软件i2c,没有介绍的价值。在使用IIC时,大多数我们都是采用软件的方式,因为软件的方式及其简单,一套组合拳几乎可以拿到任意MCU去使用。而STM32的硬件IIC也不稳定,经常容易卡死,我在STM32F031时侥幸将硬件IIC调试成功,但是后来使用STM32F103时却无法成功,真是个菜狗。但是由于项目需求,读写IIC的时间很难空出来,必须将时间腾出来给其他外设,我的软件IIC只能作废,需要重新编写硬件IIC的代码,并且需要带上DMA,将时间缩减到最小。于是就有了今天帖子。(这里准备添加STM32F031的硬件IIC链接的,结果找了一圈发现自己没写。以后有时间给大伙带上吧)
这篇帖子是准备在上个月就写的,但是因为设计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,再烧录便可成功。

这个原因是为啥,我还没找到,希望有知道的大佬可以给个答案,如果后续知道答案我再补上。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3160886-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

【国货之光】GD32E230F4使用硬件IIC+DMA读写24C04相关推荐

  1. STM32 HAL 硬件IIC+DMA+简单图形库控制OLED

    目录 前言 一.建立工程 二.编写和移植 前期准备 驱动部分修改 三.使用和验证 结论 (2022年1月22日重制)本文主要是移植带简单图形库的程序,如果只是实现DMA控制,建议看[0.96寸 OLE ...

  2. 【STM32】CubeMX+HAL库之 硬件IIC+DMA控制OLED(兼容SSD1306SH1106驱动)

    [STM32]CubeMX+HAL库之 硬件IIC+DMA控制1.3寸OLED 前言 目前网上大多数驱动OLED屏都采用软件IIC,因为HAL库的升级使得硬件IIC的稳定性得到了保障,所以想采用硬件I ...

  3. 使用STM32+硬件IIC+DMA驱动GT系列触摸屏(GT911)

    使用STM32+硬件IIC+DMA驱动GT系列触摸屏(GT911) 初始化代码 /** @brief GT911 初始化程序* @param None* @retval None*/ void GT9 ...

  4. STM32CubeMx HAL库使用硬件IIC读写AT24C02

    介绍使用硬件IIC接口读写AT24C02,STM32自带硬件IIC,比较好用,没必要千篇一律的使用模拟IIC.作为一个IIC的使用例子,可以适当修改用于其他IIC接口设备通信控制. 一.测试环境 ST ...

  5. STM32硬件IIC读写EEPROM

    前面一篇写了软件模拟IIC读写EEPROM. 本篇介绍硬件IIC读写EEPROM.平台是STM32F103+AT24C04N.SDA和SCL接5K上拉电阻到3.3v. 首先介绍AT24C04N的基本特 ...

  6. STM32H7系列使用硬件IIC读写EEPROM(HAL库配置源码)

    记得在使用STM32F103系列时,好多人说其硬件IIC通讯不稳定.后来我们用到了STM32H743的单片机,使用了其硬件IIC对EEPROM进行读写.硬件IIC使用并不麻烦,而且相比于I/O模拟的I ...

  7. STM32使用IIC总线通讯协议在OLED屏幕上显示字符串、汉字、图像(硬件IIC)

    参考:基于STM32-Oled(IIC)的使用 作者:奋斗的小殷 发布时间: 2021-05-07 13:09:26 网址:https://blog.csdn.net/boybs/article/de ...

  8. GD32F303调试小记(三)之IIC(硬件IIC+PCF8563实时时钟)

    前言 前面的文章介绍了在单片机中常用的两种通信协议(USART和SPI),并给出了GD32F303对应的配置流程.这次介绍第三种常见的通信协议IIC.这此使用GD32的硬件IIC通信PCF8563实时 ...

  9. STM32G0 硬件SPI+DMA+LL库,最高通讯速率32MBit/s

    硬件SPI+DMA+LL库代码连接STM32G0硬件SPI+DMA+LL库,最高通讯速率32MBit/s-嵌入式文档类资源-CSDN下载 近期在写0.96寸OLED显示屏驱动的程序,之前试过用软件模拟 ...

最新文章

  1. fusioncompute中cpu可以设置的qos参数有哪些?_kubernetes 中 Qos 的设计与实现
  2. JavaScript 类型总览(图)
  3. 父与子的编程之旅:与小卡特一起学Python.pdf
  4. windows 7 睡眠和休眠的区别
  5. php foreach 单箭头,PHP Foreach循环具有单个元素
  6. 2017杭电ACM集训队单人排位赛 - 1(ALL题解)
  7. jq.$post传递参数给php,通过URL参数post传递的实现方式 PHP/Javascript
  8. 使用内存映射提高BufferedRandoAccessFile性能(测试可用)
  9. 完全二叉树叶子结点的算法
  10. VMware Workstation Pro 16序列号
  11. 做软件测试有发展前景吗?
  12. 优达学诚--对安然事件数据集的分析
  13. ElasticSearch实战系列十一: ElasticSearch错误问题解决方案
  14. matlab和Excel的交互 非xlsread和xlswrite(1-Excel基础)
  15. git拉取代码出现Unpacking objects
  16. “专精特新”背后的京东动力
  17. UltraEdit的上百种语法高亮文件下载,具体添加请看其中说明!(推荐)
  18. 马士兵java面试八股文及答案
  19. cv_bridge与python版本问题导致编译错误error: return-statement with no value, in function returning ‘void*’ [-fpe
  20. ABAP HANA BP主数据批导

热门文章

  1. 数学难题html5小游戏答案,数学难题大全及答案
  2. geogebra动态数学软件,实用工具
  3. ML:机器学习算法中—因子模型(多变量)、时序模型/时间序列模型(主要以单变量)算法对比的简介、带有时序性的因子模型概述、案例应用之详细攻略
  4. 2018年,中国空气质量在全球的排名,你一定想不到!
  5. codewars 7kyu Vowel Count
  6. 慧算账解读新《代理记账管理办法》
  7. 免费代理池(proxy_pool)搭建教程
  8. linux学生入门,Linux入门之《Linux从入门到精通》
  9. 笔记-最优控制理论1
  10. 一个技术青年的网络失足