[研电赛技术支持] 【国货之光】GD32E230F4使用硬件IIC+DMA读写24C04

[复制链接]
 楼主| 呐咯密密 发表于 2021-9-1 16:56 | 显示全部楼层 |阅读模式
本帖最后由 呐咯密密 于 2021-9-1 16:59 编辑

#申请原创# @21小跑堂

在很久很久以前,我就写过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,外设情况如下:
37105612f29ba091e9.png 78047612f2a7e7db76.png
flash只有16KB,封装是LGA20,资源少,封装小。这样就导致价格会很便宜,但是我没用过如此小的flash和资源如此少的MCU,这就导致我后面出现了事故。
我们的项目其实只用了一路SPI,一路485,一路IIC和两个外部中断,所用资源实在不多,但是用在这块片子上就闲的很拥挤了。所用IO如下所示:
97361612f2bda28483.png
3脚PA0用于后续的休眠唤醒,所剩资源仅仅只有PA1,PA2,PA3。因为考虑PCB版面走线和布局问题,我们直接取消外部晶振,将PF0和PF1作为IIC使用。
这里就出现了第一个问题,我们需要在收到RX引脚的第一个下降沿触发外部中断,为了走线方便,将烧录口SWDIO和RX对接,在烧录完程序之后SWDIO会用作普通GPIO,结果导致打板回来后无法烧录代码,把485芯片摘掉则无此问题,于是又要重新打板贴片,我就趁着这功夫调试了一下硬件IIC。
开始调试代码:
首先初始化所用的外设时钟:
  1. void rcu_config(void)
  2. {
  3.     /* enable GPIOA,F clock */
  4.     rcu_periph_clock_enable(RCU_GPIOA);
  5.         rcu_periph_clock_enable(RCU_GPIOF);
  6.     /* enable I2C0 clock */
  7.     rcu_periph_clock_enable(RCU_I2C0);
  8.     /* enable DMA0 clock */
  9.     rcu_periph_clock_enable(RCU_DMA);
  10. }
复制代码
然后初始化IIC。
  1. void i2c_config(void)
  2. {
  3.     /* connect PF1 to I2C0_SCL */
  4.     /* connect PF0 to I2C0_SDA */
  5.     gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_0);
  6.     gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_1);
  7.     /* configure GPIO pins of I2C */
  8.     gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0);
  9.     gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
  10.     gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);
  11.     gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_1);

  12.     /* configure I2C clock */
  13.     i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2);
  14.     /* configure I2C address */
  15.     i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C0_SLAVE_ADDRESS7);
  16.     /* enable I2C0 */
  17.     i2c_enable(I2C0);
  18.     /* enable acknowledge */
  19.     i2c_ack_config(I2C0, I2C_ACK_ENABLE);
  20. }
复制代码
上面的GPIO的初始化一般没什么好说的,IO设置为开漏输出模式,因为IIC总线有读和写两个操作,开漏是最好的,这里说的是最好,其实设置为推挽输出也是可以的,这里有必要提一下,因为上周在论坛看到有的兄弟在调试SPI时,拿到的例程,GPIO设置为推挽输出,导致很疑惑,设置为输出如何去读SPI的数据。
84028612f30278f711.png
这里有一个冷知识:只要使能了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:
  1. void eeprom_buffer_write_dma_timeout(uint8_t* p_buffer, uint8_t write_address, uint16_t number_of_byte)
  2. {
  3.     uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0;
  4.     address = write_address % I2C_PAGE_SIZE;
  5.     count = I2C_PAGE_SIZE - address;
  6.     number_of_page =  number_of_byte / I2C_PAGE_SIZE;
  7.     number_of_single = number_of_byte % I2C_PAGE_SIZE;

  8.     /* if write_address is I2C_PAGE_SIZE aligned  */
  9.     if(0 == address){
  10.         while(number_of_page--){
  11.             eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE);
  12.             eeprom_wait_standby_state_timeout();
  13.             write_address +=  I2C_PAGE_SIZE;
  14.             p_buffer += I2C_PAGE_SIZE;
  15.         }
  16.         if(0 != number_of_single){
  17.             eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single);
  18.             eeprom_wait_standby_state_timeout();
  19.         }
  20.     }else{
  21.         /* if write_address is not I2C_PAGE_SIZE aligned */
  22.         if(number_of_byte < count){
  23.             eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_byte);
  24.             eeprom_wait_standby_state_timeout();
  25.         }else{
  26.             number_of_byte -= count;
  27.             number_of_page =  number_of_byte / I2C_PAGE_SIZE;
  28.             number_of_single = number_of_byte % I2C_PAGE_SIZE;
  29.             
  30.             if(0 != count){
  31.                 eeprom_page_write_dma_timeout(p_buffer, write_address, count);
  32.                 eeprom_wait_standby_state_timeout();
  33.                 write_address += count;
  34.                 p_buffer += count;
  35.             }
  36.             /* write page */
  37.             while(number_of_page--){
  38.                 eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE);
  39.                 eeprom_wait_standby_state_timeout();
  40.                 write_address +=  I2C_PAGE_SIZE;
  41.                 p_buffer += I2C_PAGE_SIZE;
  42.             }
  43.             /* write single */
  44.             if(0 != number_of_single){
  45.                 eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single);
  46.                 eeprom_wait_standby_state_timeout();
  47.             }
  48.         }
  49.     }  
  50. }
复制代码
IIC使用DMA读24C02:
  1. uint8_t eeprom_page_write_dma_timeout(uint8_t* p_buffer, uint8_t write_address, uint8_t number_of_byte)
  2. {
  3.     dma_parameter_struct dma_init_struct;
  4.     uint8_t state = I2C_START;
  5.     uint16_t timeout = 0;
  6.     uint8_t i2c_timeout_flag = 0;

  7.     while(!(i2c_timeout_flag)){
  8.         switch(state){
  9.         case I2C_START:
  10.             /* i2c master sends start signal only when the bus is idle */
  11.             while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)){
  12.                 timeout++;
  13.             }
  14.             if(timeout < I2C_TIME_OUT){
  15.                 i2c_start_on_bus(I2CX);
  16.                 timeout = 0;
  17.                 state = I2C_SEND_ADDRESS;
  18.             }else{
  19.                 i2c_bus_reset();
  20.                 timeout = 0;
  21.                 state = I2C_START;
  22.                 printf("i2c bus is busy in PAGE WRITE!\n");
  23.             }
  24.             break;
  25.         case I2C_SEND_ADDRESS:
  26.             /* i2c master sends START signal successfully */
  27.             while((!i2c_flag_get(I2CX, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)){
  28.                 timeout++;
  29.             }
  30.             if(timeout < I2C_TIME_OUT){
  31.                 i2c_master_addressing(I2CX, eeprom_address, I2C_TRANSMITTER);
  32.                 timeout = 0;
  33.                 state = I2C_CLEAR_ADDRESS_FLAG;
  34.             }else{
  35.                 timeout = 0;
  36.                 state = I2C_START;
  37.                 printf("i2c master sends start signal timeout in PAGE WRITE!\n");
  38.             }
  39.             break;
  40.         case I2C_CLEAR_ADDRESS_FLAG:
  41.             /* address flag set means i2c slave sends ACK */
  42.             while((!i2c_flag_get(I2CX, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)){
  43.                 timeout++;
  44.             }
  45.             if(timeout < I2C_TIME_OUT){
  46.                 i2c_flag_clear(I2CX, I2C_FLAG_ADDSEND);
  47.                 timeout = 0;
  48.                 state = I2C_TRANSMIT_DATA;
  49.             }else{
  50.                 timeout = 0;
  51.                 state = I2C_START;
  52.                 printf("i2c master clears address flag timeout in PAGE WRITE!\n");
  53.             }
  54.             break;
  55.         case I2C_TRANSMIT_DATA:
  56.             /* wait until the transmit data buffer is empty */
  57.             while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)){
  58.                 timeout++;
  59.             }
  60.             if(timeout < I2C_TIME_OUT){
  61.                 /* send the EEPROM's internal address to write to : only one byte address */
  62.                 i2c_data_transmit(I2CX, write_address);
  63.                 timeout = 0;
  64.             }else{
  65.                 timeout = 0;
  66.                 state = I2C_START;
  67.                 printf("i2c master sends EEPROM's internal address timeout in PAGE WRITE!\n");
  68.             }
  69.             /* wait until BTC bit is set */
  70.             while(!i2c_flag_get(I2CX, I2C_FLAG_BTC));
  71.             dma_deinit(DMA_CH1);
  72.             dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
  73.             dma_init_struct.memory_addr = (uint32_t)p_buffer;
  74.             dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  75.             dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
  76.             dma_init_struct.number = number_of_byte;
  77.             dma_init_struct.periph_addr = (uint32_t)&I2C_DATA(I2CX);
  78.             dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
  79.             dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
  80.             dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
  81.             dma_init(DMA_CH1, &dma_init_struct);
  82.             /* enable I2CX DMA */
  83.             i2c_dma_enable(I2CX, I2C_DMA_ON);
  84.             /* enable DMA0 channel1 */
  85.             dma_channel_enable(DMA_CH1);
  86.             /* wait until full transfer finish flag is set */
  87.             while(!dma_flag_get(DMA_CH1, DMA_FLAG_FTF));
  88.             /* wait until BTC bit is set */
  89.             while((!i2c_flag_get(I2CX, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)){
  90.                 timeout++;
  91.             }
  92.             if(timeout < I2C_TIME_OUT){
  93.                 timeout = 0;
  94.                 state = I2C_STOP;
  95.             }else{
  96.                 timeout = 0;
  97.                 state = I2C_START;
  98.                 printf("i2c master sends data timeout in PAGE WRITE!\n");
  99.             }
  100.             break;
  101.         case I2C_STOP:
  102.             /* send a stop condition to I2C bus */
  103.             i2c_stop_on_bus(I2CX);
  104.             /* i2c master sends STOP signal successfully */
  105.             while((I2C_CTL0(I2CX) & 0x0200) && (timeout < I2C_TIME_OUT)){
  106.                 timeout++;
  107.             }
  108.             if(timeout < I2C_TIME_OUT){
  109.                 timeout = 0;
  110.                 state = I2C_END;
  111.                 i2c_timeout_flag = I2C_OK;
  112.             }else{
  113.                 timeout = 0;
  114.                 state = I2C_START;
  115.                 printf("i2c master sends stop signal timeout in PAGE WRITE!\n");
  116.             }
  117.             break;
  118.         default:
  119.             state = I2C_START;
  120.             i2c_timeout_flag = I2C_OK;
  121.             timeout = 0;
  122.             printf("i2c master sends start signal in PAGE WRITE!\n");
  123.             break;
  124.         }
  125.     }
  126.     return I2C_END;
  127. }
复制代码
使用这两个函数只需传入需要操作的数组,页的地址和读写的数据量便可,这里贴一下测试的函数:
  1. uint8_t i2c_24c02_test(void)
  2. {
  3.     uint16_t i;

  4.     printf("\r\nAT24C02 writing...\r\n");
  5.    
  6.     /* initialize i2c_buffer_write */
  7.     for(i = 0; i < BUFFER_SIZE; i++){
  8.         i2c_buffer_write[i] = i;
  9.         printf("0x%02X ", i2c_buffer_write[i]);
  10.         if(15 == i%16){
  11.             printf("\r\n");
  12.         }
  13.     }
  14.     /* EEPROM data write */
  15.     eeprom_buffer_write_dma_timeout(i2c_buffer_write, EEP_FIRST_PAGE, BUFFER_SIZE);
  16.     printf("AT24C02 reading...\r\n");
  17.     /* EEPROM data read */
  18.     eeprom_buffer_read_dma_timeout(i2c_buffer_read, EEP_FIRST_PAGE, BUFFER_SIZE);
  19.     /* compare the read buffer and write buffer */
  20.     for(i = 0; i < BUFFER_SIZE; i++){
  21.         if(i2c_buffer_read[i] != i2c_buffer_write[i]){
  22.             printf("0x%02X ", i2c_buffer_read[i]);
  23.             printf("Err:data read and write aren't matching.\n\r");
  24.             return I2C_FAIL;
  25.         }
  26.         printf("0x%02X ", i2c_buffer_read[i]);
  27.         if(15 == i%16){
  28.             printf("\r\n");
  29.         }
  30.     }
  31.     printf("I2C-AT24C02 test passed!\n\r");
  32.     return I2C_OK;
  33. }
复制代码
参数定义:
  1. #define EEPROM_BLOCK0_ADDRESS    0xA0
  2. #define BUFFER_SIZE              256

  3. uint16_t eeprom_address;
  4. uint8_t i2c_buffer_write[BUFFER_SIZE];
  5. uint8_t i2c_buffer_read[BUFFER_SIZE];
  6. uint8_t i2c_buffer_read1[BUFFER_SIZE];

  7. #define I2C_TIME_OUT   (uint16_t)(5000)
  8. #define I2C_TIME_OUT1  (uint32_t)(200000)
  9. #define EEP_FIRST_PAGE 0x00
  10. #define I2C_OK         1
  11. #define I2C_FAIL       0
  12. #define I2C_END        1
  13. #define I2CX           I2C0
复制代码

一开始向发送数组中填充256个数据,然后调用写函数将256个数据写进24C02,因为24C02只有一页,所以页数设置为0,写完后再读出数据,校验写入和读出的数据是否一致。
这里我便遇到了第二个坑,写完之后调试不通过,代码卡死在IIC的时钟初始化i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2);,继续进入这个函数,发下卡死在:

25871612f36703d5de.png
这里仅仅是一个数据的运算,卡死在这里明显不合理。这时候我想到了上周调试报错:\output\Project.axf: Error: L6406E: No space in execution regions with .ANY selector matching usart.o(.text.RS_485_SEND).当时是内存溢出了,于是我看了一下工程的map文件:
36783612f37880329a.png
果然,已经快要顶不住了,于是果断修改keil的编译优化选项,将优化等级提升为1级,内存缩小很多,问题便可解除,因为之前没用过这么下的MCU,而且这次编译没报错,若不是上次报错让我找到原因,这次不知又要卡多久。
97627612f382554538.png 98618612f383b3eb7f.png
至此代码都是只能读写24c02,也就是操作一页。如果是更大的EEPROM器件,那么该如何操作。这里稍微吐槽一下:为啥我会用IIC来操作24C04,因为我的硬件板子上焊接的就是24C04,而为啥是24C04,并不是24C02不够用,只是因为04比02便宜,市场都畸形了吗?而我为啥要吐槽,是因为下文遇到的第三个问题。
扩充到24C04:
既然知道如何写一页,那么即使再大的容量,我们也有办法去操作,以24C04为例,只是比24C02多了一页,地址为0x01;那么我们增加一个宏定义:
69893612f3a9041f75.png
定义一下第二页的地址,然后定义一个数组去接收我们读到的数据:
42778612f3ab9a3918.png
最后在测试程序中添加读写第二页的操作:
51678612f3aeca69eb.png
测试的时候再次翻车,第一次debug没问题,第二次debug的时候程序卡死在第一个写EEPROM的函数,我猜测难道又是数据超了,但是只增加了一个数组啊,代码量应该不会超标啊,于是把添加的代码全部删除,结果还是失败。
解决方法:烧录之前先擦除全部flash,再烧录便可成功。
60866612f3bc2c5177.png
这个原因是为啥,我还没找到,希望有知道的大佬可以给个答案,如果后续知道答案我再补上。


文至于此,问题都已解决,最后把源码贴出来,需要的自己索取。
游客,如果您要查看本帖隐藏内容请回复





LEDyyds 发表于 2021-9-1 17:00 | 显示全部楼层
硬件IIC啊,太香了,正好用得到。非常感谢!
yjwpm 发表于 2021-9-4 10:56 | 显示全部楼层
看看
两只袜子 发表于 2021-9-6 16:25 来自手机 | 显示全部楼层
国货之光
zxs2888 发表于 2021-9-6 18:46 | 显示全部楼层
学习了                       
yixifeng1990 发表于 2021-9-7 08:48 | 显示全部楼层
正在用E230,先看看
caigang13 发表于 2021-9-7 09:27 来自手机 | 显示全部楼层
写的详细,谢谢分享经验。
海滨消消 发表于 2021-9-8 16:39 来自手机 | 显示全部楼层
写的详细
mylinss 发表于 2021-9-8 17:29 | 显示全部楼层
学习学习
wifi99 发表于 2021-9-23 15:48 | 显示全部楼层
好的
Garho 发表于 2021-9-26 16:36 | 显示全部楼层
大牛。厉害了
ZRJ8951 发表于 2021-11-2 08:13 来自手机 | 显示全部楼层
kankan
yanzhengxin1 发表于 2021-11-5 15:39 来自手机 | 显示全部楼层
值得学习,挺详细
西西泸 发表于 2021-11-8 14:04 | 显示全部楼层
感谢
nanyangyu 发表于 2021-11-8 16:16 | 显示全部楼层
很好的资料,值得好好学习
nanyangyu 发表于 2021-11-8 16:17 | 显示全部楼层
michaelsrh 发表于 2021-11-27 10:37 | 显示全部楼层
硬件IIC,学习学习
slovak 发表于 2021-12-1 18:59 来自手机 | 显示全部楼层
这个不错的,需要好好学习一下。
提刀剁骨头 发表于 2021-12-4 14:25 | 显示全部楼层
mark一下
荆棘鸟sm 发表于 2021-12-20 14:47 | 显示全部楼层
学习一下
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

567

主题

4081

帖子

56

粉丝
快速回复 在线客服 返回列表 返回顶部