发新帖本帖赏金 40.00元(功能说明)我要提问
12下一页
返回列表
打印

【技术分享】GD32硬件I2C调试中的问题与解决过程-续

[复制链接]
4085|28
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
blust5|  楼主 | 2023-5-6 14:48 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
#申请原创#   @21小跑堂  @21小跑堂

书接上回( https://bbs.21ic.com/icview-3300590-1-1.html?fromuser=blust5 ):
使用GD32303C-EVAL开发板和MPL3115A2模块测量气压或高度数据,两者间使用硬件I2C进行通讯。
上次调试发现官方例程(单一I2C读写功能)可以正常读写MPL芯片的寄存器,而我建立的由官方例程融合(多个功能模块组合)的工程无法正常读写,会造成程序卡死在一个地方。


上次定位到了I2C异常的原因是屏幕初始化函数exmc_lcd_init()导致的。
为了再次确认确实是这个函数导致的问题,再进行一次反向验证:在有问题的工程里,将I2C的操作地址修改为0xA0(EEPROM的操作地址),进行同样的验证。
验证结果一致,即注释掉屏幕初始化函数exmc_lcd_init()即可正常读写,加上这个函数就会出问题。
由此,已经从各方面印证了问题就是出在屏幕初始化函数exmc_lcd_init()这里。
那么这个函数里都有什么呢?
/*!

    \brief      lcd peripheral initialize

    \param[in]  none

    \param[out] none

    \retval     none

*/

void exmc_lcd_init(void)

{

    exmc_norsram_parameter_struct lcd_init_struct;

    exmc_norsram_timing_parameter_struct lcd_timing_init_struct;



    /* EXMC clock enable */

    rcu_periph_clock_enable(RCU_EXMC); // EXMC初始化语句1



    /* GPIO clock enable */

    rcu_periph_clock_enable(RCU_GPIOD);

    rcu_periph_clock_enable(RCU_GPIOE);



    /* configure EXMC_D[0~15]*/

    /* PD14(EXMC_D0), PD15(EXMC_D1),PD0(EXMC_D2), PD1(EXMC_D3), PD8(EXMC_D13), PD9(EXMC_D14), PD10(EXMC_D15) */

    gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1| GPIO_PIN_8 | GPIO_PIN_9 |

                                                         GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15);



    /* PE7(EXMC_D4), PE8(EXMC_D5), PE9(EXMC_D6), PE10(EXMC_D7), PE11(EXMC_D8), PE12(EXMC_D9),

       PE13(EXMC_D10), PE14(EXMC_D11), PE15(EXMC_D12) */

    gpio_init(GPIOE, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 |

                                                         GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 |

                                                         GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);



    /* configure PE2(EXMC_A23) */

    gpio_init(GPIOE, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2);

   

    /* configure NOE and NWE */

    gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4 | GPIO_PIN_5);



    /* configure EXMC NE0 */

    gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);



    lcd_timing_init_struct.asyn_access_mode = EXMC_ACCESS_MODE_A;

    lcd_timing_init_struct.syn_data_latency = EXMC_DATALAT_2_CLK;

    lcd_timing_init_struct.syn_clk_division = EXMC_SYN_CLOCK_RATIO_DISABLE;

    lcd_timing_init_struct.bus_latency = 2;

    lcd_timing_init_struct.asyn_data_setuptime = 18;

    lcd_timing_init_struct.asyn_address_holdtime = 3;

    lcd_timing_init_struct.asyn_address_setuptime = 8;



    lcd_init_struct.norsram_region = EXMC_BANK0_NORSRAM_REGION0;

    lcd_init_struct.write_mode = EXMC_ASYN_WRITE;

    lcd_init_struct.extended_mode = DISABLE;

    lcd_init_struct.asyn_wait = DISABLE;

    lcd_init_struct.nwait_signal = DISABLE;

    lcd_init_struct.memory_write = ENABLE;

    lcd_init_struct.nwait_config = EXMC_NWAIT_CONFIG_BEFORE;

    lcd_init_struct.wrap_burst_mode = DISABLE;

    lcd_init_struct.nwait_polarity = EXMC_NWAIT_POLARITY_LOW;

    lcd_init_struct.burst_mode = DISABLE;

    lcd_init_struct.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;

    lcd_init_struct.memory_type = EXMC_MEMORY_TYPE_SRAM;

    lcd_init_struct.address_data_mux = DISABLE;

    lcd_init_struct.read_write_timing = &lcd_timing_init_struct;

    lcd_init_struct.write_timing = &lcd_timing_init_struct;



    exmc_norsram_init(&lcd_init_struct); // EXMC初始化语句2



    exmc_norsram_enable(EXMC_BANK0_NORSRAM_REGION0); // EXMC初始化语句3

}

可以看到,程序里有EXMC模块的初始化和屏幕驱动使用的GPIO端口的初始化。
EXMC是什么呢?这个是外部存储器控制器。GD32F30x用户手册上关于该模块的简介是这样的:
外部存储器控制器EXMC,用来访问各种片外存储器,通过配置寄存器,EXMC可以把AMBA协议转换为专用的片外存储器通信协议,包括SRAM,ROM,NOR Flash,NAND Flash,PC卡。用户还可以调整配置寄存器中的时间参数来提高通信效率。EXMC的访问空间被划分为许多个块(Bank),每个块支持特定的存储器类型,用户可以通过对Bank的控制寄存器配置来控制外部存储器。
这个模块应该是用来驱动TFT屏的驱动芯片的,通过EXMC模块将需要显示的数据(字符或图片、颜色、位置等信息)写入到TFT屏的驱动芯片里,类似操作外部存储器一样。

首先确认是哪一个初始化步骤影响到了I2C通讯。
先屏蔽掉EXMC相关的三个初始化语句:前面的EXMC时钟初始化语句和最后的两条EXMC初始化语句。
void exmc_lcd_init(void)

{

    exmc_norsram_parameter_struct lcd_init_struct;

    exmc_norsram_timing_parameter_struct lcd_timing_init_struct;



    /* EXMC clock enable */

//    rcu_periph_clock_enable(RCU_EXMC); // EXMC初始化语句1



/*    省略    */



//    exmc_norsram_init(&lcd_init_struct); // EXMC初始化语句2



//    exmc_norsram_enable(EXMC_BANK0_NORSRAM_REGION0); // EXMC初始化语句3

}
屏蔽掉这三个语句之后进行调试验证,确认I2C可以正常读写。
由此可以确认I2C读写故障是由EXMC模块的初始化导致,而不是exmc_lcd_init()函数里的GPIO初始化部分导致的。

下面再来一一验证这三句EXMC初始化代码具体是哪一句造成的异常结果。
先取消EXMC初始化语句1:即rcu_periph_clock_enable(RCU_EXMC);的注释,然后进行调试验证,发现问题复现了。
为了确认,再次注释语句1,然后进行调试验证,果然又恢复正常了。
反复两到三次,可确认该语句会导致I2C异常复现。

同样的方式对后面两个语句进行验证,发现并不会导致I2C异常复现。
于是将后面两个语句取消注释,再次对语句1进行验证,确认了只是该语句导致的I2C读写异常,其他语句均不会影响I2C读写操作。

这句代码是起到什么作用呢?这个代码是官方库文件,用来启动外设模块的时钟的。
/*!

    \brief      enable the peripherals clock

    \param[in]  periph: RCU peripherals, refer to rcu_periph_enum

                only one parameter can be selected which is shown as below:

      \arg        RCU_GPIOx (x=A,B,C,D,E,F,G): GPIO ports clock

      \arg        RCU_AF : alternate function clock

      \arg        RCU_CRC: CRC clock

      \arg        RCU_DMAx (x=0,1): DMA clock

      \arg        RCU_ENET: ENET clock(CL series available)

      \arg        RCU_ENETTX: ENETTX clock(CL series available)

      \arg        RCU_ENETRX: ENETRX clock(CL series available)

      \arg        RCU_USBD: USBD clock(HD,XD series available)

      \arg        RCU_USBFS: USBFS clock(CL series available)

      \arg        RCU_EXMC: EXMC clock

      \arg        RCU_TIMERx (x=0,1,2,3,4,5,6,7,8,9,10,11,12,13,TIMER8..13 are not available for HD series): TIMER clock

      \arg        RCU_WWDGT: WWDGT clock

      \arg        RCU_SPIx (x=0,1,2): SPI clock

      \arg        RCU_USARTx (x=0,1,2): USART clock

      \arg        RCU_UARTx (x=3,4): UART clock

      \arg        RCU_I2Cx (x=0,1): I2C clock

      \arg        RCU_CANx (x=0,1,CAN1 is only available for CL series): CAN clock

      \arg        RCU_PMU: PMU clock

      \arg        RCU_DAC: DAC clock

      \arg        RCU_RTC: RTC clock

      \arg        RCU_ADCx (x=0,1,2,ADC2 is not available for CL series): ADC clock

      \arg        RCU_SDIO: SDIO clock(not available for CL series)

      \arg        RCU_CTC: CTC clock

      \arg        RCU_BKPI: BKP interface clock

    \param[out] none

    \retval     none

*/

void rcu_periph_clock_enable(rcu_periph_enum periph)

{

    RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));

}
通过传入的参数,可以知道这个语句是使能EXMC模块的时钟的。
再来进行深度验证一下。
exmc_lcd_init()函数里rcu_periph_clock_enable(RCU_EXMC);语句的位置更换为rcu_periph_clock_disable(RCU_EXMC);语句,发现I2C读写正常。
恢复exmc_lcd_init()函数里的内容(即正常运行EXMC时钟使能语句),同时在main()函数里exmc_lcd_init()后面增加rcu_periph_clock_disable(RCU_EXMC);语句,即按如下操作:
i2c_gpio_config(); /* configure GPIO */

    i2c_config(); /* configure I2C */

    i2c_eeprom_init(); /* initialize EEPROM  */



    exmc_lcd_init();    /* configure the EXMC access mode */

    delay_1ms(50);

    rcu_periph_clock_disable(RCU_EXMC);

    MPL_Init();
再次进行验证,发现I2C读写操作正常。
结论如下:无论之前是否操作过EXMC模块的时钟使能寄存器,只要最终是关闭状态,I2C读写就是正常的;而只要是打开状态,I2C读写流程就会异常。

EXMC模块的时钟为什么会影响到I2C的读写操作呢?估计要到用户手册上去查找问题,或者联系GD32的FAE了。
先来查看一下用户手册。
GD32F303的系统架构图如下:
可以发现EXMC模块使用的总线是AHB,和I2C模块(使用总线为APB1)并没有在一个区域,应该不是总线问题导致的互相影响。

再来看一下GD32F303的时钟单元。下图是GD32F303的时钟树结构:
可以看到,系统时钟CK_SYS先经过了AHB预分频器,然后给到EXMC模块的时钟CK_EXMC;同时AHB预分频器之后还给到APB1预分频器,然后才给到PCLK1,提供给APB1总线上的外设使用。
难道是EXMC模块的时钟使能操作影响到了AHB时钟的分频,导致I2C总线的传输速度收到影响,从而导致数据读写操作失败?
从理论上来讲,应该不会发生这种情况的,后端的时钟使用与否不会影响到前面分频器的工作。

继续详细查看用户手册ing。

忽然间,在芯片手册里,发现了GPIO引脚定义表格,发现PB7引脚的第二功能同时具有I2C0_SDA功能和EXMC_NADV功能!
难道是引脚冲突了?EXMC_NADV功能只能用PB7,而例程里的I2C0初始化部分,也确实用了PB7。
/*!

    \brief      configure the GPIO ports

    \param[in]  none

    \param[out] none

    \retval     none

*/

void i2c_gpio_config(void)

{

    /* enable GPIOB clock */

    rcu_periph_clock_enable(RCU_GPIOB);

    /* enable I2C0 clock */

    rcu_periph_clock_enable(RCU_I2C0);



    /* connect PB6 to I2C0_SCL */

    /* connect PB7 to I2C0_SDA */

    gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7);

}
验证一下看看。

这个怎么验证呢?通过remap功能,将I2C0引脚重定义到其他引脚上,再去验证两者的功能是否正常。
先看一下I2C0引脚可以重定义到什么引脚上。
可以看到,I2C0的两个引脚可以通过remap重定义到PB8和PB9上。
修改I2C引脚初始化函数(即i2c_gpio_config()函数),增加I2C0引脚remap,启动AF时钟,同时将引脚初始化改为PB8和PB9。
void i2c_gpio_config(void)

{

    /* enable GPIOB clock */

    rcu_periph_clock_enable(RCU_GPIOB);

    /* enable I2C0 clock */

    rcu_periph_clock_enable(RCU_I2C0);

    rcu_periph_clock_enable(RCU_AF);



    /* connect PB8 to I2C0_SCL */

    /* connect PB9 to I2C0_SDA */

    gpio_pin_remap_config(GPIO_I2C0_REMAP, ENABLE);

    gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_8 | GPIO_PIN_9);

}
由于开发板上PB8和PB9没有使用,没加上拉电阻,所以需要焊两颗4.7K的电阻上拉到3.3V,同时将杜邦线转移到PB8和PB9上。
然后调试验证,I2C读写正常,屏幕显示正常。

至此,硬件I2C读写异常问题完美解决。
导致问题出现的原因是开发板在不同模块的例程上都用到了PB7,从而导致两个功能无法同时使用。
进行引脚功能remap即可解决该问题。

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 40.00 元 2023-05-11
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2023-5-11 17:09 回复TA
解决上篇文章未解难题,逐步排查,找到问题根源并解决问题 
沙发
loutin| | 2023-5-10 20:03 | 只看该作者
如何直接利用GD32的硬件I2C操控

使用特权

评论回复
板凳
usysm| | 2023-5-10 20:19 | 只看该作者
I2C的传输速率由什么决定?              

使用特权

评论回复
地板
mikewalpole| | 2023-5-10 20:33 | 只看该作者
GD32的硬件I2C稳定吗              

使用特权

评论回复
5
juliestephen| | 2023-5-10 21:41 | 只看该作者
如何检测GD32硬件I2C的开始条件和结束条件

使用特权

评论回复
6
blust5|  楼主 | 2023-5-11 08:23 | 只看该作者
loutin 发表于 2023-5-10 20:03
如何直接利用GD32的硬件I2C操控

我用的是例程里的库函数

使用特权

评论回复
7
blust5|  楼主 | 2023-5-11 08:24 | 只看该作者
usysm 发表于 2023-5-10 20:19
I2C的传输速率由什么决定?

速率可以自己配置的,当然有一个上限,只要不超过这个上限就能自由配置

使用特权

评论回复
8
blust5|  楼主 | 2023-5-11 08:24 | 只看该作者
mikewalpole 发表于 2023-5-10 20:33
GD32的硬件I2C稳定吗

我刚开始用,但是以我这几天用的情况来看,没遇到不稳定的情况

使用特权

评论回复
9
blust5|  楼主 | 2023-5-11 08:25 | 只看该作者
juliestephen 发表于 2023-5-10 21:41
如何检测GD32硬件I2C的开始条件和结束条件

按照I2C标准的起始位和停止位的时序检测就行了吧,都是一样的

使用特权

评论回复
10
cashrwood| | 2023-5-11 16:50 | 只看该作者
为什么很多应用,还要模拟IIC              

使用特权

评论回复
11
yorkbarney| | 2023-5-11 17:04 | 只看该作者
硬件iic应答失败如何自动恢复              

使用特权

评论回复
12
blust5|  楼主 | 2023-5-11 17:07 | 只看该作者
cashrwood 发表于 2023-5-11 16:50
为什么很多应用,还要模拟IIC

因为很多MCU的硬件IIC没有想象中的稳定

使用特权

评论回复
13
blust5|  楼主 | 2023-5-11 17:07 | 只看该作者
yorkbarney 发表于 2023-5-11 17:04
硬件iic应答失败如何自动恢复

没遇到过,可以尝试重新复位IIC模块

使用特权

评论回复
14
primojones| | 2023-5-11 17:11 | 只看该作者
I2c总线一般拿来实现什么功能?

使用特权

评论回复
15
i1mcu| | 2023-5-11 17:18 | 只看该作者
硬件I2C中断方式和查询方式有什么区别

使用特权

评论回复
16
lzmm| | 2023-5-11 17:31 | 只看该作者
GD32硬件I2C DMa有坑吗

使用特权

评论回复
17
pixhw| | 2023-5-11 17:36 | 只看该作者
软件模拟IIC还是使用硬件IIC,哪个更好

使用特权

评论回复
18
minzisc| | 2023-5-11 17:40 | 只看该作者
硬件I2C能与软件I2C一起用一个I2C通道么

使用特权

评论回复
19
mnynt121| | 2023-5-11 17:47 | 只看该作者
硬件IIC需要外围上拉电阻吗              

使用特权

评论回复
20
blust5|  楼主 | 2023-5-11 17:50 | 只看该作者
primojones 发表于 2023-5-11 17:11
I2c总线一般拿来实现什么功能?

一般一些传感器芯片和存储芯片会是I2C总线通讯

使用特权

评论回复
发新帖 本帖赏金 40.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:业精于勤荒于嬉,行成于思毁于随。

72

主题

2829

帖子

11

粉丝