☰
PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(5)
2026/10/10 2:50:59 网站建设 项目流程

接前一篇文章:PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(4)

三、问题解决

上一回通过AI工具Cursor定位到了根因:

这里详细说明一下具体情况。

实际上问题是出在了APP_DeInitInterface这个函数上。笔者工程中的APP_DeInitInterface函数代码如下:

static void APP_DeInitInterface(void) { #if 0 CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_USART3EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); #else CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); CLEAR_BIT(RCC->APBENR2, RCC_APBENR2_USART1EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); SET_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); CLEAR_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); #endif #if 0 CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_I2C1EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C1RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C1RST); #else CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_I2C2EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C2RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C2RST); #endif CLEAR_BIT(RCC->CFGR, RCC_CFGR_SW); while (0 != READ_BIT(RCC->CFGR, RCC_CFGR_SWS)); CLEAR_BIT(RCC->CR, RCC_CR_PLLON); }

而普冉官方例程中的APP_DeInitInterface函数代码是这样的:

static void APP_DeInitInterface(void) { __enable_irq(); switch (guc_InterfaceDetection) { case INTERFACE_USART1: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); CLEAR_BIT(RCC->APBENR2, RCC_APBENR2_USART1EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); SET_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); CLEAR_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); break; case INTERFACE_USART2: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_USART2EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART2RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART2RST); break; case INTERFACE_USART3: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_USART3EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); break; case INTERFACE_USART4: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOCEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_USART4EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOCRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART4RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOCRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART4RST); break; case INTERFACE_I2C: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_I2C1EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C1RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_I2C1RST); break; } }

看起来似乎差异不大、无关紧要,实际上这里才是导致问题的紧关节要之处。

先来看官方例程。官方例程中是根据不同的接口使用不同的反初始化函数。OTA的文件可能是通过UART1、UART2、UART3、UART4或者I2C1传输的,这就意味着使用哪个接口,之前就要初始化哪个接口,与之对应地,这里就要去初始化该接口。以UART1为例,看一下具体代码的作用:

case INTERFACE_USART1: CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); CLEAR_BIT(RCC->APBENR2, RCC_APBENR2_USART1EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); SET_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST); CLEAR_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST); break;
  • CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN);

这一行的意思是关闭GPIOA外设时钟。

  • CLEAR_BIT(RCC->APBENR2, RCC_APBENR2_USART1EN);

这一行的意思是关闭USART1外设时钟。

  • SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST);

这一行的意思是置位GPIOA复位位,进入复位状态。

  • SET_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST);

这一行的意思是置位USART1复位位,进入复位状态。

  • CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOARST);

这一行的意思是清除GPIOA复位位,退出复位。

  • CLEAR_BIT(RCC->APBRSTR2, RCC_APBRSTR2_USART1RST);

这一行的意思是清除USART1复位位,退出复位。

综合来看,以上一段代码的作用就是关闭UART1的时钟以及复用为UART1功能的GPIO引脚的时钟,然后复位UART1和对应的GPIOA。

对于其它接口(UART2、3、4,I2C1),也是类似的。

再来看笔者工程对应的代码:

#if 0 CLEAR_BIT(RCC->IOPENR, RCC_IOPENR_GPIOBEN); CLEAR_BIT(RCC->APBENR1, RCC_APBENR1_USART3EN); SET_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); SET_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); CLEAR_BIT(RCC->IOPRSTR, RCC_IOPRSTR_GPIOBRST); CLEAR_BIT(RCC->APBRSTR1, RCC_APBRSTR1_USART3RST); #else …… #endif

重点看#if 0注释掉的部分。

笔者项目之前的硬件使用的是UART3,最新的版本使用的则是UART1。这里的代码仍然是去初始化UART3。这就会引起问题了。

上边AI工具讲得清楚:

调用发生在HAL_Init()(SysTick 已开)和UART1_Init()(USART1的RXNE中断已打开)之后。跳转函数只复位了USART3和GPIOB,实际用的是USART1,没有关系。

__disable_irq()只挡住全局中断,NVIC里USART1的使能和挂起标志都还在。从Boot区跳转到App区时,App进入main()的第一件事就是把中断打开,此时Uart1Handle还是BSS清零后的空指针。总线上一有数据,或者RXNE/ORE还挂着,就会立刻进App的USART1_IRQHandler,对Uart1Handle.Instance做访问,由于此时Uart1Handle.Instance还是NULL,因此就会进入HardFault_Handler中的死循环。OTA时总线是忙的,所以一重启就跑飞;安静上电有时能躲过,等第一帧串口数据到来再死。

笔者将错误(#if 0中的)代码注释掉,改为下边(#else中)的代码,问题解决(当然还有I2C的修改)。

更多内容请看下回。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询