【IAP】FreeRTOS环境下Boot升级过程中出现系统自动复位,FLASH操作时关闭总中断解决

文章描述了在FreeRTOS多线程环境中,调用NVIC_SystemReset引发系统复位的问题,通过排查地址空间、任务执行环境和中断管理,最终确定问题可能在于多线程执行NVIC_SystemReset以及在FLASH操作时未保证原子性。解决方案涉及加锁、使用标志和挂起调度器以避免干扰。
摘要由CSDN通过智能技术生成

衔接 上篇 调好写入Flash失败后,又出现新问题:
由BOOT跳APP再跳APP,在升级接受升级文件过程中,突然出现系统复位,而且出现位置和时间都是随机的。

FreeRTOS多线程环境下调用NVIC_SystemReset()系统复位函数出现问题

定位问题

  1. 尝试改RTOS空间大小
    本以为是FreeRTOS空间给小了,尝试加大还是无果,最后怀疑应该不是RTOS问题,因为如果任务挂了应该会死机不会出现类似重新上电一样的复位。

  2. 怀疑BOOT和APP存储地址错误
    怀疑可能是写入地址出错,APP和BOOT地址交叉了,但最后比较地址发现APP区域和BOOT区域是隔开的,并没有影响,并且用JLINK读取芯片存储空间也是正常的,BOOT区域并没受影响,所以与地址空间配置无关

  3. 上网查阅资料系统复位需要配置什么,尤其是任务中系统复位
    因为之前由BOOT跳转APP在任务中和不在任务中有不同的配置,好像是要关闭中断,

void jumpToApp(uint32_t appBaseAddr)
{
    void (*firmwareFunc)(void);
    uint32_t fwStackVal = *((uint32_t *)(appBaseAddr));     /* the first word is for the stack pointer. */
    uint32_t fwEntryVal = *((uint32_t *)(appBaseAddr+4U));  /* the second works is for the boot function. */
    firmwareFunc = (void (*)(void))fwEntryVal;

    SCB->VTOR = appBaseAddr; /* The stack address is also the start address of vector. */
    __set_MSP(fwStackVal);
    __set_PSP(fwStackVal);
    firmwareFunc();
}
void jumpToAppInTask(uint32_t appBaseAddr)
{
    void (*firmwareFunc)(void);
		SysTick->CTRL = 0X00;//禁止SysTick
    SysTick->LOAD = 0;
    SysTick->VAL = 0;
    __disable_irq();
    uint32_t fwStackVal = *((uint32_t *)(appBaseAddr));     /* the first word is for the stack pointer. */
    uint32_t fwEntryVal = *((uint32_t *)(appBaseAddr+4U));  /* the second works is for the boot function. */
    firmwareFunc = (void (*)(void))fwEntryVal;

    SCB->VTOR = appBaseAddr; /* The stack address is also the start address of vector. */
    __set_MSP(fwStackVal);
    __set_PSP(fwStackVal);
    firmwareFunc();
}

所以怀疑APP跳BOOT可能也要有不同处理, 最后查到这篇
尝试了一下

__disable_irq();  
__set_FAULTMASK(1);	
NVIC_SystemReset();

还是没用, (不是很懂上面具体作用是什么,欢迎大佬赐教)
4. 发现代码漏洞
后面换了板子,升级成功2次后,第三次还是有类似问题,所以升级逻辑应该没问题,可能是跳转影响了,而BOOT程序就一个线程在跑,收发数据然后升级,不存在跳转,只有可能是APP程序影响到了BOOT。
但已经APP已经跳过来BOOT了,怎么还能影响,可能是FREERTOS的一些问题,后面证实确实是,但不知道具体原理是什么。参考了这篇文章

使用NVIC_SystemReset实现APP跳转到UBOOT时,连续多次跳转,容易出现MCU崩溃。

因为通信库的缘故,解析指令是多线程解析,加日志打印发现APP中复位确实存在多任务多次执行NVIC_SystemReset();的情况,所以可能是跳转去BOOT后,升级过程中随机调度起任务执行NVIC_SystemReset();导致,BOOT升级中复位。

最后屏蔽APP中的NVIC_SystemReset();函数,直接BOOT升级,一次成功,定位到问题。

解决问题

  1. 加锁
taskENTER_CRITICAL();
NVIC_SystemReset();
taskEXIT_CRITICAL();

多线程调度NVIC_SystemReset,加锁理论上应该可以解决,最后发现,还是BOOT升级中复位了,以为是复位NVIC_SystemReset();又释放了taskEXIT_CRITICAL();

static __INLINE void NVIC_SystemReset(void)
{
  __DSB();                                                     /* Ensure all outstanding memory accesses included
                                                                  buffered write are completed before reset */
  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                 SCB_AIRCR_SYSRESETREQ_Msk);                   /* Keep priority group unchanged */
  __DSB();                                                     /* Ensure completion of memory access */
  while(1);                                                    /* wait until reset */
}

但NVIC_SystemReset里面有个while怎么还会释放呢?我猜想可能是升级中RAM空间不稳定,导致临界区空间被压榨了,锁没了,其他线程又复原了然后进来执行但NVIC_SystemReset。
2. 加标志

	taskENTER_CRITICAL();

	if(isSystemReset == DRIVER_FALSE)
	{
		isSystemReset = DRIVER_TRUE;
		NVIC_SystemReset();
	}

	taskEXIT_CRITICAL();

加标志,保证只进来一次?结果还是不行,应该也是APP区域已经被擦除的缘故,变量没有保存,最后还是寄了。
3. 挂起调度器

	taskENTER_CRITICAL();
	vTaskSuspendAll();
	if(isSystemReset == DRIVER_FALSE)
	{
		isSystemReset = DRIVER_TRUE;
		NVIC_SystemReset();
	}
	xTaskResumeAll();
	taskEXIT_CRITICAL();

如果挂起调度器,不让任务调度,是不是可以避免多线程干扰,最后多次测试,BOOT升级过程中,没有再发生复位情况,与第一条猜想符合,这还不行就只能创线程,指令改标志位,保证串行执行系统复位了。
总之FreeRTOS里面升级还是不安全,上面一些原理欢迎大佬赐教

——————————————2024.1.18更新——————————————
在新板上验证,又出现升级到一半系统自动复位的问题了,怀疑是板子问题,但新旧板子Flash是一样的,然后换了2块旧版都能正常升级,一样的代码,
在新板上把APP跳BOOT和BOOT跳APP都屏蔽了,还是会升级到一半系统自动复位,而且是必发的,只是时机不同,说明上面的判断和猜想都错了,百思不得其解
后面对比老工程,发现在Boot程序的main函数开头有一段代码SystemInit();

/**
 * Initialize the system
 *
 * @param  none
 * @return none
 *
 * @brief  Setup the microcontroller system.
 *         Initialize the System.
 */
void SystemInit (void)
{
#if (CLOCK_SETUP)                       /* Clock Setup                        */
  LPC_SC->SCS       = SCS_Val;
  if (SCS_Val & (1 << 5)) {             /* If Main Oscillator is enabled      */
    while ((LPC_SC->SCS & (1<<6)) == 0);/* Wait for Oscillator to be ready    */
  }

  LPC_SC->CLKSRCSEL = CLKSRCSEL_Val;    /* Select Clock Source for sysclk/PLL0*/

#if (PLL0_SETUP)
  LPC_SC->PLL0CFG   = PLL0CFG_Val;
  LPC_SC->PLL0CON   = 0x01;             /* PLL0 Enable                        */
  LPC_SC->PLL0FEED  = 0xAA;
  LPC_SC->PLL0FEED  = 0x55;
  while (!(LPC_SC->PLL0STAT & (1<<10)));/* Wait for PLOCK0                    */
#endif

#if (PLL1_SETUP)
  LPC_SC->PLL1CFG   = PLL1CFG_Val;
  LPC_SC->PLL1CON   = 0x01;             /* PLL1 Enable                        */
  LPC_SC->PLL1FEED  = 0xAA;
  LPC_SC->PLL1FEED  = 0x55;
  while (!(LPC_SC->PLL1STAT & (1<<10)));/* Wait for PLOCK1                    */
#endif

  LPC_SC->CCLKSEL   = CCLKSEL_Val;      /* Setup Clock Divider                */
  LPC_SC->USBCLKSEL = USBCLKSEL_Val;    /* Setup USB Clock Divider            */
  LPC_SC->EMCCLKSEL = EMCCLKSEL_Val;    /* EMC Clock Selection                */
  LPC_SC->PCLKSEL   = PCLKSEL_Val;      /* Peripheral Clock Selection         */
  LPC_SC->PCONP     = PCONP_Val;        /* Power Control for Peripherals      */
  LPC_SC->CLKOUTCFG = CLKOUTCFG_Val;    /* Clock Output Configuration         */
#endif

#if (FLASH_SETUP == 1)                  /* Flash Accelerator Setup            */
  LPC_SC->FLASHCFG  = FLASHCFG_Val|0x03A;
#endif
#ifdef  __RAM_MODE__
  SCB->VTOR  = 0x10000000 & 0x3FFFFF80;
#else
  SCB->VTOR  = 0x00000000 & 0x3FFFFF80;
#endif
}

这是LPC1778芯片

索性死马当活马医,加到boot开头,升级试试发现居然一次就升级成功了~!
后面搜了下这个函数功能
这里引用两篇资料
里面有介绍SystemInit的功能:

SystemInit 函数完成系统时钟、RAM、中断向量表地址的初始化。

如果在Main函数中升级不加这个函数也可以,而我在FreeRTOS任务中升级就出现了上述问题,我怀疑是不是APP和BOOT两段代码的FreeRTOS任务都在RAM中打架,导致的升级过程中系统自动复位,而SystemInit 函数不仅初始化了中断向量地址,还初始化了RAM,所以才能正常运行?但是老板卡用新代码又跑不起来了,太诡异。
所以我现在2个方案都使用了(串行单独执行NVIC_SystemReset和SystemInit),因为少一个都跑不起来,可能是两方面原因都有
——————————————2024.1.20更新——————————————
结果还是复发了,并且我发现在系统上电,调用main函数之前,是有SystemInit这个函数的,所以没必要加SystemInit(终究还是阅历太浅TT)

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
				IMPORT  SystemInit
                IMPORT  __main
				LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

于是推倒重来,从头分析,先把BOOT跳APP,和APP跳BOOT全部屏蔽,结果还是会复发,这说明至少不完全是这方面原因。
后面怀疑是FreeRTOS栈太大,挤压了RAM空间,但经指点与这块关系不大,于是去检查FLASH,把FLASH写相关操作屏蔽后,惊人的居然成功了。但我仔细比对了地址、flash操作发现并没有什么区别,和老工程唯一区别就是一个在任务中执行,一个在main中执行。
于是上网搜索Flash相关操作,最终有了新发现

在这位博主分享的资料中,我发现

uint32_t flash_CopyRAMToFlash(uint32_t dstAddr, uint32_t srcAddr, uint32_t Len)
{
	NVIC_SETPRIMASK();//¹Ø±ÕϵͳÖжÏ
	param_table[0] = COPY_RAM_TO_FLASH;
	param_table[1] = dstAddr;
	param_table[2] = srcAddr;
	param_table[3] = Len;
	param_table[4] = FLASHCCLK;	/* Core clock frequency in kHz */

    iap_entry(param_table,result_table);
	NVIC_RESETPRIMASK();//´ò¿ªÏµÍ³ÖжÏ
	
	return result_table[0];
}

FLASH相关操作时有关闭总中断,完毕后开放总中断
上网了解相关资料(其实chatgpt)

> 在IAP升级运行过程中突然复位中断,是和FLASH操作有关吗,但是地址都对了在IAP升级运行过程中突然复位中断,是和FLASH操作有关吗,但是地址都对了?

在应用程序中进行 IAP(In-Application Programming)升级时,如果在升级过程中发生突然复位,有几个可能的原因与
FLASH 操作和复位中断有关。以下是一些可能的因素:

1.中断冲突: 在进行 IAP 操作时,通常需要禁用中断以确保操作的原子性。如果中断没有正确禁用或在关键时刻被启用,可能导致冲突,从而触发复位中断。
2.中断优先级配置: 如果中断优先级配置不正确,可能导致在 FLASH 操作期间触发的中断与复位中断产生冲突。确保在 IAP 过程中设置了适当的中断优先级。
3.错误的 FLASH 操作: 如果在 IAP 过程中发生错误,例如尝试在无效的地址写入数据,可能会触发复位中断。确保进行的 FLASH 操作是有效的,地址范围正确,并且符合设备规格。
4.堆栈溢出: 在进行 IAP 操作时,特别是涉及到递归调用或大量局部变量的情况下,可能会导致堆栈溢出。堆栈溢出可能导致复位中断的触发。
5.电源问题: 如果在 IAP 过程中发生电源问题,如电源波动或不稳定,可能导致设备复位。在升级过程中确保电源稳定性是重要的。

为了排除问题,你可以考虑在升级代码中添加调试信息,以及在复位中断处理函数中记录发生复位的原因。此外,确保在 IAP
过程中适当地禁用和启用中断,以及正确处理复位情况,可以帮助识别和解决问题。

> 中断冲突,也就是说,我在FLASH操作时加上关闭系统总中断,就可以避免中断冲突是吗?

是的,关闭系统总中断(全局中断禁止)是一种常见的方法,用于确保在进行关键的 FLASH 操作时不会被中断打断,从而避免中断冲突。这样可以保证
FLASH 操作是原子性的,不会在升级过程中被其他中断中断。

在许多嵌入式系统中,有专门的指令或函数来实现关闭和打开全局中断。例如,在 ARM Cortex-M
处理器中,可以使用以下汇编指令来关闭和打开全局中断:

CPSID I ; 禁止全局中断 ; 执行 FLASH 操作 CPSIE I ; 允许全局中断 或者,如果你使用的是 C
语言,可能有编译器提供的相应函数来实现中断的禁止和启用。例如:

c
__disable_irq(); // 禁止全局中断 // 执行 FLASH 操作
__enable_irq(); // 启用全局中断 确保在 FLASH 操作之前禁用全局中断,并在操作完成后重新启用它,可以帮助确保 FLASH 操作的原子性。这有助于避免中断引起的竞争条件和冲突。

这么看来,FLASH操作,确实有可能会因为中断干扰而导致复位,于是我在Flash操作前后加了__disable_irq和__enable_irq保证FLASH操作的原子性,结果真成功了,后面试了几次都没有复发,然后一去掉关闭总中断就复发了,所以说明这个确实是有用的,原因可能就在于上述第一条:中断冲突。

FreeRTOS学习(四)任务调度与切换
学习资料发现,FreeRTOS调度确实是通过 SVC ,SysTick 和 PendSV中断实现,而__disable_irq确实可以屏蔽这3个中断

__disable_irq 能防止FreeRTOS 任务切换是吗?

是的,__disable_irq 能够防止 FreeRTOS 任务切换。当你在 FreeRTOS 任务中使用 __disable_irq
指令时,它会全局地关闭中断,包括任务切换所需要的时钟中断。

在 FreeRTOS
中,任务切换是由系统的时钟中断触发的。通过禁用中断,你阻止了时钟中断的触发,从而防止了任务切换。这使得在禁用中断期间执行的代码能够在一个不会被其他任务打断的环境中运行。

需要注意的是,禁用中断可能会对系统的实时性产生一些负面影响,因为其他紧急事件或高优先级任务的响应可能会被延迟。因此,在使用
__disable_irq 或其他全局禁用中断的方法时,需要谨慎考虑系统的性能和实时性需求,确保在适当的时机启用中断。

__disable_irq 可以关闭系统的时钟中断是吗?

是的,__disable_irq
指令可以用于关闭系统的时钟中断。该指令是用来全局地禁用(关闭)中断的,在某些嵌入式系统中,它可以被用来确保在一段临界区内中断不会被响应。

系统的时钟中断通常用于触发任务切换、定时器操作等。通过使用
__disable_irq,你可以禁用中断,从而阻止时钟中断的触发。这样,你就可以在禁用中断的临界区内执行一些关键代码,而这段代码不会被时钟中断打断。

需要注意的是,全局禁用中断可能会影响系统的实时性,因为其他紧急事件或高优先级任务可能无法及时响应。因此,在使用 __disable_irq
或类似方法时,需要谨慎考虑系统的性能和实时性需求,并确保在适当的时机启用中断。

因此FLASH操作关闭总中断,正好防止了线程调度,所以成功。

总结来说:可能原因应该主要就在两处
①多线程执行NVIC_SystemReset
②FLASH操作时没有保证原子性,要关闭总中断,原因应该是FreeRTOS中发送了任务调度,因为在main函数中是可以的

欢迎大佬指教

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值