调试PY32403单片机OTA功能时遇到的memset函数会导致I2C错误的问题的根因分析 📅 2026/6/30 22:27:22 一、问题背景1. 问题概述笔者近期在调试普冉PY32F403 MCU的OTA功能时遇到了一个诡异的问题当在函数中调用memset函数时会导致I2C读取或写入出现错误。此问题一度令读者十分费解、甚至感到茫然。最终弄清楚根本原因后是一个十分低级的失误所导致的。在此将此问题的发现、探查分析、调试测试、最终解决的完整过程记录下来。2. 问题详述笔者是在调试PY32F403的OTA功能的时候、在Boot程序一般分为Boot和App程序中遇到此问题的。Boot程序中大致机制如下1主函数main的while循环中会调用handle_recv_image_data函数逐帧接收升级数据升级文件拆包后的各帧数据烧录到App分区的Flash地址中2接收到最后一帧数据并校验通过后跳转到App分区从而完成OTA IAP升级。示例代码如下int main(int argc, char *argv[]) { …… ota_check_flag(); while (1) { if (recv_cmd 1) { recv_cmd 0; handle_recv_image_data(); …… } //end of if (recv_cmd 1) } //return 0; }当收到最后一帧包数据后handle_recv_image_data函数中会写入EEPROM中的一个标志并马上读EEPROM的该标志。如果正确则跳转到App区。但实测发现当接收完最后一帧数据后前边都没有问题就是在写入和接着读取EEPROM中的这个关键标志时出错了读取的数据并不等于写入的数据。这样就导致了调用不到APP_Bootloader_Go函数从而无法调转到App区。二、问题分析1. 初步排查既然问题出在了handle_recv_image_data函数中的写入和读取EEPROM这一段EEPROM_WriteBuffer(EEPROM_ADDR_UPDATE_FLAG, update_flag, LEN_UPDATE_FLAG); HAL_Delay(1); EEPROM_ReadBuffer(EEPROM_ADDR_UPDATE_FLAG, flag, LEN_UPDATE_FLAG); if (0x5A flag) APP_Bootloader_Go(APP1_START_ADDR);那么就先看看具体是什么原因导致的不能读取到正确值。经过仿真器调试跟踪最终跟踪到EEPROM_ReadBuffer函数中发现是其中的HAL_I2C_Mem_Read函数返回了2、而非正常的0。这个2代表什么意思呢代码中HAL_I2C_Mem_Read函数的返回值赋给了state其类型是HAL_StatusTypeDef。该类型在Drivers\PY32F403_HAL_Driver\Inc\py32f403_hal_def.h中定义如下/** * brief HAL Status structures definition */ typedef enum { HAL_OK 0x00U, HAL_ERROR 0x01U, HAL_BUSY 0x02U, HAL_TIMEOUT 0x03U } HAL_StatusTypeDef;可见其意义是I2C总线忙。2. 进一步探查那么到底是什么原因导致I2C总线出现了BUSY呢笔者一开始怀疑是Flash的写操作导致的后来笔者在代码中封掉了Flash写操作的相关代码结果问题依旧。这样反倒让笔者放心下来因为一旦证明是Flash操作引起的I2C总线问题那接下来就不好查了。现在来看还好至少Flash操作与I2C并无冲突。笔者索性将handle_recv_image_data函数中的代码全部封掉只保留EEPROM写入和读取的这一段发现没有问题功能是正常的。既然与Flash操作并无关系EEPROM自身的读取和写入也正常那就一定说明是handle_recv_image_data函数中的其它代码引发的问题。笔者将handle_recv_image_data函数中的代码一段一段进行注释/放开定位最终定位到是其中的一段串口发送相关的函数引发的该问题。该函数代码片段如下int ack_update_packet(void) { int tx_idx 0; //size1 sizeof(uart_tx_buf); memset(uart_tx_buf, 0x00, sizeof(uart_tx_buf)); //memset(uart_tx_buf, 0x00, 256); …… uart_send(uart_tx_buf, tx_idx); return 0; }难道是串口发送与EEPROM存在潜在冲突笔者再次一段一段定位最终定位到是memset那一行所引起的问题。一开始EEPROM的工作无论读写也都是正常的但只要一经过memset那一行之后不正常了。这也挺匪夷所思的memset函数是标准的库函数它怎么会引起I2C总线的问题呢笔者又进行了以下进一步定位1将memset函数放在main函数的主循环中看看是否同样会引发问题2将memset函数中的大小进行调整看看是否无论何种大小只要调用memset函数就会引发问题上边ack_update_packet函数代码中也可以看到笔者的调试痕迹。经过以上实验发现1在main函数的主循环中每次EEPROM的读写都是正常的并不存在问题2将数据大小改为1、256都没有问题只是在大小为sizeof(uart_tx_buf)的时候才会出现问题。这就更奇怪了uart_tx_buf是在main.c中定义的全局数组变量大小就是256。代码如下unsigned char uart_tx_buf[UART_TX_BUFFER_SIZE];UART_TX_BUFFER_SIZE的定义在main.h中如下#define UART_TX_BUFFER_SIZE 256如此看来sizeof(uart_tx_buf)和256的值是一回事。那为什么一个行一个不行呢3. 进一步分析带着以上疑问笔者通过仿真器跟踪到了ack_update_packet函数中查看sizeof(uart_tx_buf)的值究竟是多少从上边代码也可以看出调试痕迹。经过调试发现sizeof(uart_tx_buf)的大小居然不是256、而是1100这是为什么呢笔者到此才恍然大悟原来是这样的笔者在定义uart_tx_buf数组的同时也定义了uart_rx_buf数组但它的大小不是256、而是1100因为接收的数据要比发送的数据多得多。这里这个1100正是笔者在main.h中所定义的代码如下#define UART_RX_BUFFER_SIZE 1100为什么uart_tx_buf数组的大小会变成了uart_rx_buf数组的大小了呢main.c中不是定义的uart_tx_buf的大小是UART_TX_BUFFER_SIZE即256么笔者搜索了一下uart_tx_buf的相关定义及引用发现其定义及引用在工程代码中一共有3处main.c中的定义及另一处的引用是正确的只有ack_update_packet函数所属的这个.c文件中的引用出问题了是这样的extern unsigned char uart_tx_buf[UART_RX_BUFFER_SIZE];看到了么这里uart_tx_buf数组的大小写成了UART_RX_BUFFER_SIZE也就是1100。这就是问题的根源在调用sizeof(uart_tx_buf)的时候产生了数组越界进而引发了I2C的异常系统工作不正常。三、问题解决既然问题明确了那么就把这个错误改正过来extern unsigned char uart_tx_buf[UART_RX_BUFFER_SIZE]; --- extern unsigned char uart_tx_buf[UART_TX_BUFFER_SIZE];修正之后再次进行调试I2C可以正常读写了。再把之前封掉的都恢复也可以调用到APP_Bootloader_Go函数进行App的跳转了问题解决。四、问题总结此问题虽然看似怪异实则是笔者的一个低级失误所导致的。这就应了日常工程中的一个经验越是离奇的bug其背后的根因往往越低级。但是到这里还不能算完还要倒着追溯。虽然这个问题是笔者的一个低级失误所导致但extern引用uart_tx_buf数组的时候大小与定义不一致这个错误通过KEIL进行编译的时候为什么不报错误、哪怕是警告呢这可能是KEIL或者普冉SDK的一个不足吧。通过此问题在此也给自己和大家提个醒今后再修改移植代码的时候一定要谨慎、谨慎、再谨慎防止出现这种很容易马虎和忽略的问题尤其越是相近的定义及代码越应该小心。