PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(5)

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

三、问题解决

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

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

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

cpp 复制代码
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函数代码是这样的:

cpp 复制代码
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为例,看一下具体代码的作用:

cpp 复制代码
    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),也是类似的。

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

cpp 复制代码
#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的修改)。

更多内容请看下回。

相关推荐
Zwawa2 小时前
传感器模块都在交出什么:从模拟电压到数字协议
c语言·单片机·嵌入式
小何好运暴富开心幸福4 小时前
嵌入式历史与生态
stm32·单片机·mcu
Brilliantwxx5 小时前
【STM32】 工程实战 WS2812B 全彩 LED 灯控制(DMA 驱动)
stm32·单片机·嵌入式硬件·mcu
会周易的程序员6 小时前
STVM OSAL 层架构设计文档
c++·物联网·嵌入式·虚拟机·iot·软plc·iec61131
蓝天居士8 小时前
PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(6)
mcu·嵌入式
圆周率智能 iMLite AI9 小时前
嵌入式高尔夫地图如何压到单洞 100KB?离线图源的数据组织与压缩思路
mcu·边缘计算·嵌入式开发·rtos·离线地图·高尔夫·高尔夫地图
Jason_zhao_MR10 小时前
电力数据采集核心板选型指南
linux·嵌入式硬件·fpga开发·嵌入式
G.E.M.小白1 天前
【Linux嵌入式蜂鸣器驱动开发】原理图分析、寄存器寻址、完整驱动+应用+Makefile
嵌入式·imx6ull·linux驱动开发·蜂鸣器驱动
用户810629480811 天前
热压机控制系统设计:PLC 状态机、PID 调节与压力闭环控制
嵌入式