#申请原创# @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即可解决该问题。
|
解决上篇文章未解难题,逐步排查,找到问题根源并解决问题