blust5 发表于 2023-5-5 10:51

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

#申请原创#      @21小跑堂   @21小跑堂
最近一个项目需要测量海拔高度,在淘宝上买了一个气压高度计模块,芯片是MPL3115A2,I2C通讯接口的气压计模块。到货后自己把排针焊上,准备测试。 之前已经在用GD32303C-EVAL开发板在做其他相关的开发工作了,包括屏幕显示、串口中断收发等功能,都是用的官方例程进行组合,然后做相应改动来进行的。首先用官方的屏显例程,理解例程里的显示接口,根据需要修改成自己的显示内容;然后将串口中断收发例程融合进去,并自己添加了串口接收环形缓冲区功能。并成功将串口接收到的内容显示到屏幕上。由于以上过程相对简单,且和本篇主题相关性不大,于是略过详细内容。 下一步开始的工作就是I2C接口驱动的移植,这里使用I2C_EEPROM例程进行融合,然后修改芯片地址即可(开发板上的EEPROM芯片是AT24Cxx,地址是0xA0,我手上的MPL3115A2气压计芯片地址是0xC0),可直接使用开发板上的I2C引脚,并且不需要将EEPROM芯片断开,通过地址即可进行选择读取。 在进行了一系列的融合与地址修改之后,并编写了MPL芯片的初始化程序,编译,通过,下载,运行,发现死机了……#define DATA_STATUS_ADDR       0x06
#define DATA_START_ADDR      0x01
#define SYSMOD_ADDR            0x11
#define CTRL_REG1_ADDR         0x26
#define CTRL_REG2_ADDR         0x27

void MPL_Init(void)
{
        uint8_t reg_data;
        reg_data = 0x03;
        eeprom_byte_write(®_data, CTRL_REG1_ADDR);
}
通过断点追踪调试,发现程序卡在了I2C程序的一个状态位等待的语句上,一直卡在这里不往下运行了。一开始怀疑哪里配置没有搞对,然后开始一步一步排查问题。 首先是直接运行官方例程,即直接读取和写入EEPROM,发现运行正常,读取和写入过程都是正常执行,且结果也符合预期。确认了例程没有问题。 然后进行下一步验证,直接在官方例程里将芯片地址由0xA0改为0xC0。at24cxx.c文件:i2c.h文件:同时根据芯片手册调整寄存器地址,进行读写验证。由于大部分寄存器初始值都是0,这里读取0x0C寄存器进行验证,这个寄存器是设备ID,其值固定为0xC4。原例程里i2c_24c02_test()函数是用来验证EEPROM芯片的读写的,其内容如下。/*!

    \brief      I2C read and write functions

    \paramnone

    \param none

    \retval   I2C_OK or I2C_FAIL

*/

uint8_t i2c_24c02_test(void)

{

    uint16_t i;

    uint8_t i2c_buffer_write;

    uint8_t i2c_buffer_read;

   

    printf("\r\nAT24C02 writing...\r\n");

   

    /* initialize i2c_buffer_write */

    for(i = 0;i < BUFFER_SIZE;i++){

      i2c_buffer_write=i;

      printf("0x%02X ",i2c_buffer_write);

      if(15 == i%16){

            printf("\r\n");

      }

    }

    /* EEPROM data write */

    eeprom_buffer_write(i2c_buffer_write,EEP_FIRST_PAGE, BUFFER_SIZE);

    printf("AT24C02 reading...\r\n");

    /* EEPROM data read */

    eeprom_buffer_read(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 != i2c_buffer_write){

            printf("0x%02X ", i2c_buffer_read);

            printf("Err:data read and write aren't matching.\n\r");

            return I2C_FAIL;

      }

      printf("0x%02X ", i2c_buffer_read);

      if(15 == i%16){

            printf("\r\n");

      }

    }

    printf("I2C-AT24C02 test passed!\n\r");

    return I2C_OK;

}
现在修改其内容,将其原本内容删除,增加寄存器读取内容。uint8_t i2c_24c02_test(void)

{

    uint16_t i;

    uint8_t i2c_buffer_write;

    uint8_t i2c_buffer_read;

   

    i2c_buffer_read = 0x00;

    eeprom_buffer_read(i2c_buffer_read,0x0C, 1);

   

    return I2C_OK;

}
然后进行单步运行验证,发现程序运行完eeprom_buffer_read(i2c_buffer_read,0x0C, 1);语句之后,i2c_buffer_read的值变成了0xC4,表明程序正确运行了。 为了保险起见,又再次验证了写操作。再次修改i2c_24c02_test(void)函数,其中0x26为MPL芯片的工作模式配置寄存器地址。uint8_t i2c_24c02_test(void)

{

    uint16_t i;

    uint8_t i2c_buffer_write;

    uint8_t i2c_buffer_read;

   

    i2c_buffer_write = 0x00;

    eeprom_byte_write(i2c_buffer_write,0x26);

    eeprom_buffer_read(i2c_buffer_read,0x26, 1);

    i2c_buffer_write = 0x03;

    eeprom_byte_write(i2c_buffer_write,0x26);

    eeprom_buffer_read(i2c_buffer_read,0x26, 1);

   

    return I2C_OK;

}
通过单步运行发现,可以成功写入寄存器,并进行读取验证。由此确认,硬件没有问题,开发板和MPL芯片模块都是正常的,且两者的硬件连接也没有问题。那么问题就是出在软件上了。由于出问题的工程也是由例程进行移植的,下一步要怎么进行问题定位呢?首先对源代码进行对比,查看差异点。使用BCompare软件对两个工程的代码进行对比,结果如下:首先把各个目录的含义简单说一下。          .settings:工程配置文件夹,无需比较          firmware:官方驱动库文件,;两个工程一致          GD ARM MCU Release:编译时生成的相关文件,无需比较          inc:工程头文件(.h文件),需要对比          ldscripts:FLASH配置文件,两个工程一致          src:工程源文件(.c文件),需要对比后面四个是工程相关文件,无需比较。 通过对比,可以清晰的看到哪些文件时一样的,哪些文件不一样。双击不一样的文件,可打开查看不一样的内容。左边的目录多了cycle_buffer.c和cycle_buffer.h两个文件,这两个是增加的串口环形缓冲区的封装接口,跟I2C功能不相关,也跟单片机的底层驱动不相关,因此可以忽略。然后查看systick.h文件:左上角是全文的对比导图,红色是有区别的地方,蓝色是注释里有区别的地方,可以很直观的看到全文的异同点。这里可以看到有问题的工程只是多了一个全局变量的定义,不会影响I2C功能。对比gd32f30x_it.c文件,发现区别点是增加了变量定义、while(1);和while(1){}的区别、SysTick_Handler()函数的区别,以及多了串口中断函数,基本确认都和I2C功能无关。 picture.c文件的差异点只是因为没有调用一个图片的显示,从而注释掉了一个图片常量数组,也和I2C功能无关。 由此确认,功能异常是在main.c文件里的。到这里就没法通过对比定位问题点了,因为有问题的工程前面调试其他功能对该文件做了许多修改。那么要怎么定位问题点呢?既然确认了问题出在main.c文件里,那么下一步可以针对这个文件进行单步调试,逐步验证出问题的函数语句在哪里。从程序运行的起点:main()函数入口进行对比。发现I2C初始化之前,不一样的地方只是多了一部分变量定义和串口中断初始化,变量定义不会影响程序运行,下面验证串口中断初始化是否会影响I2C功能。注释掉该部分内容,再次调试程序,发现问题依然存在,基本排除串口中断初始化内容的影响。再往下的部分,例程里已经没有其他内容了,直接到了EEPROM读写验证程序调用。而有问题的工程还进行了很多的初始化和屏幕显示内容。为了定位问题点,先将测试函数移动到所有初始化内容之前进行验证。改写MPL_Init()函数:void MPL_Init(void)

{

uint8_t reg_data, get_data=0;



eeprom_buffer_read(&get_data, 0x0C, 1);

reg_data = 0x03;

eeprom_byte_write(®_data, CTRL_REG1_ADDR);

eeprom_buffer_read(&get_data, CTRL_REG1_ADDR, 1);

}
将MPL_Init()函数在main()函数里往前移动,直接移动到I2C初始化完成之后:在MPL_Init()里设置断点,进行单步运行验证:运行完438行的代码之后,变量get_data的值变成了0xC4,即读取成功了;而继续往下运行,到运行完441行的代码之后,get_data的值变成了0x03,即对CTRL_REG1_ADDR寄存器的写入和读取操作成功了。由此确认前面的初始化没有问题,应该是后面的其他初始化或代码运行影响到了I2C的功能。 下面就是继续往下验证,一步步的往下移动MPL_Init()的位置,进行调试、验证,看放到哪里之后会出问题。 通过这种方法定位,发现exmc_lcd_init()函数执行完之后,I2C的功能就会出问题。那么这个函数都做了什么呢?这个函数初始化了屏幕驱动的GPIO口,以及EXMC模块。 目前已经定位到了问题点,至于具体引发该问题的原因,还需要继续详细寻找和确认,我暂时还没有解决。有经验的技术大拿们有空可以指点一下迷津。

369122197 发表于 2023-5-5 12:22

I2C时序应该是要微调的

blust5 发表于 2023-5-5 15:05

369122197 发表于 2023-5-5 12:22
I2C时序应该是要微调的

时序应该是没问题,因为屏蔽掉那句初始化语句之后就可以正常读写操作了
页: [1]
查看完整版本: 【技术分享】GD32硬件I2C调试中的问题与解决过程