关于ARM Keil5 Optimization level 优化后读取flash异常程序进入HardFault_Handler()-分析

项目场景:

通过STM32 cubeMX创建程序后,把以前的flash驱动、UART等移植,实现产品相关功能。


问题描述

flash驱动移植后,程序在运行到FLASH_Read()函数时会进入到HardFault_Handler()硬件死循环中,而且单步运行发现该if(upgrade_finesh_flag == 0x5a5a) 条件可以进入。问题走向很明显编译环境出现了问题。

void InitDevice(void)
{
	__IO uint32_t upgrade_flag = 0;
	__IO uint32_t upgrade_finesh_flag = 0;

	HAL_FLASH_Unlock();	
	
	upgrade_flag = *(__IO uint32_t*)UPDATE_FLAG_ADDR;
	upgrade_finesh_flag = *(__IO uint32_t*)UPDATE_FINISH_FLAG_ADDR;
	if (upgrade_flag == 0xa5a5)    
	{
		FLASH_PageErase(UPDATE_FLAG_ADDR);
	}
	if(upgrade_finesh_flag == 0x5a5a)  //判断是否进行软件升级
	{
		FLASH_PageErase(UPDATE_FINISH_FLAG_ADDR);
		osDelay(500);
		__disable_fault_irq();
		NVIC_SystemReset();
	}
	osDelay(500);
	deviceInitSM = GET_RS485_BAUD;
	init_read_only_data();
	FLASH_Read(SERNUMBER_ADDR,&st_FactoryData.SerNumber,sizeof(st_FactoryData.SerNumber));
	FLASH_Read(FACTORYADDR_ADDR,&st_FactoryData.address,sizeof(st_FactoryData.address));
	FLASH_Read(MANUFACDAY_ADDR,&st_FactoryData.Manufacday,sizeof(st_FactoryData.Manufacday));
	FLASH_Read(BAUDRATE_ADDR,&st_FactoryData.baudRate,sizeof(st_FactoryData.baudRate));
	while(deviceInitSM != INIT_DONE)
	{
		switch(deviceInitSM)
		{
			case GET_RS485_BAUD: //获取波特率,默认4800
			{
				if(st_FactoryData.baudRate == 0xFFFF)
				{
					st_FactoryData.baudRate = 3;
					FLASH_Write(BAUDRATE_ADDR,(const uint16_t *)&st_FactoryData.baudRate,sizeof(st_FactoryData.baudRate));  //写波特率
				}
				if(1 == st_FactoryData.baudRate)
					huart2.Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), 1200);
				else if(2 == st_FactoryData.baudRate)
					huart2.Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), 2400);
				else if(3 == st_FactoryData.baudRate)
					huart2.Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), 4800);
				else if(4 == st_FactoryData.baudRate)
					huart2.Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), 9600);
				else if(5 == st_FactoryData.baudRate)
					huart2.Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), 19200);
				deviceInitSM = GET_ADDRESS;
				break;
			}
			case GET_ADDRESS: //获取地址
			{
				if(st_FactoryData.address == 0xFFFF)
				{
					st_FactoryData.address = 1;
					FLASH_Write(FACTORYADDR_ADDR,(const uint16_t *)&st_FactoryData.address,sizeof(st_FactoryData.address));  //写地址
				}
				deviceInitSM = GET_SERIAL_NUM;
				break;
			}
			case GET_SERIAL_NUM:  //获取序列号
			{
				if(st_FactoryData.SerNumber.serialNumber1 == 0xFFFF || st_FactoryData.SerNumber.serialNumber2 == 0xFFFF
					||st_FactoryData.SerNumber.serialNumber3 == 0xFFFF || st_FactoryData.SerNumber.serialNumber4 == 0xFFFF)
				{
					break;
				}
				else
				{
					deviceInitSM = GET_MANU_DATE;
				}
				break;
			}
			case GET_MANU_DATE: //获取年月日
			{
				if(st_FactoryData.Manufacday.manufactureYear == 0xFFFF || st_FactoryData.Manufacday.manufactureMonth == 0xFFFF
					||st_FactoryData.Manufacday.manufactureDay == 0xFFFF || st_FactoryData.Manufacday.manufactureHour == 0xFFFF)
				{
					break;
				}
				else
				{
					deviceInitSM = READY_TO_WORK;
				}
				break;
			}
			case READY_TO_WORK:
			{
				if(1 == verify_dev_serial_num())
				{
					deviceInitSM = INIT_DONE;
					//osThreadTerminate(NULL);
				}
				break;
			}
		}
		osDelay(100);
	}
	osDelay(100);
uint32_t FLASH_Read(uint32_t Address, void *Buffer, uint32_t Size)
{
    uint32_t nread = Size;
    uint8_t* d = (uint8_t *)Buffer;
    const uint8_t* s = (const uint8_t *)Address;

    if (!Buffer || Address < STM32FLASH_BASE || Address >= STM32FLASH_END)
        return 0;

    while (nread >= sizeof(uint32_t) && (((uint32_t)s) <= (STM32FLASH_END - 4)))
    {
        *(uint32_t *)d = *(uint32_t *)s;
        d += sizeof(uint32_t);
        s += sizeof(uint32_t);
        nread -= sizeof(uint32_t);
    }

    while (nread && (((uint32_t)s) < STM32FLASH_END))
    {
        *d++ = *s++;
        nread--;
    }

    return Size - nread;
}

原因分析:

对比没有移植前的代码发现Optimization选项是没有进行程序优化的选项,而且两者编译后生成的代码区大小也有明显的区别:
Program Size: Code=27972 RO-data=880 RW-data=356 ZI-data=32612
Program Size: Code=22168 RO-data=880 RW-data=356 ZI-data=32612
在这里插入图片描述

解决方案:

根据参考文献发现该选项会使编译器对程序进行优化修改:
官网文档:
https://www.keil.com/support/man/docs/uv4/uv4_dg_adscc.htm

Optimization
Control compiler code optimization for the generated code. Sets the compiler command-line option -Onum:
Default: Use the compiler default or the setting of a higher Target or Group level.
Level 0 (-O0): Turn off all optimization, except some simple source transformations.
Level 1 (-O1): Turn off optimizations that seriously degrade the debug view.
Level 2 (-O2): High optimization (default level). The debug view might be less satisfactory because the mapping of object code to source code is not always clear.
Level 3 (-O3): Maximum optimization. Note that Level 3 in combination with Optimize for Time may generate more code that Level 2 since it may unroll loops.

翻译如下:
在这里插入图片描述

Optimization level -O0
-O0禁用所有优化。使用-O0可以加快编译和构建时间,但是生成的代码比其他优化级别要满。与其他优化级别相比,-O0的代码大小和堆栈使用率明显要高。生成的代码与源代码密切相关,但生成的代码要多得多,包括死代码。

Optimization level -O1
-O1支持编译器中的核心优化。因为这个级别比-O0提供了更好的代码质量,因此它能提供了很好的调试体验。堆栈的使用也在-O0的基础上有所提高。为了获得良好的调试体验,Arm推荐使用此选项。
-O1与-O0的区别是:
1.使能了优化,这可能会降低调试信息的保真度。
2.内联和尾调是启用的,意味着回溯可能不会提供运行函数的堆栈,而这个堆栈可能是在读源代码过程中是被需要的。
3.如果不需要结果,则可能不会在预期的位置调用没有副作用的函数,或者可能会省略该函数。
4.变量的值在不再使用之后可能在其范围内不可用。例如,它们的堆栈位置可能已经被重用。

Optimization level -O2
与-O1相比,-O2具有更高的性能优化。与-O1相比,它增加了很少的新优化,并更改了优化的启发式。这是编译器可能生成向量指令的第一个优化级别。它还会降低调试体验,并可能导致比-O1更大的代码大小。
高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O2与-O1的区别是:
1.编译器认为对于内联调用站点有利的阈值将可能会增加;
2.执行的循环展开量可能会增加;
3.可以为简单的循环和独立标量操作的相关序列生成向量指令;

Optimization level -O3
最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。
另外,可以通过单独设置 --loop_optimization_level=option 来控制循环展开的优化等级。

总结:

优化等级越高,生成的执行文件越小,但是编译速度会越慢。目前单片机性能来说,不需要刻意压缩执行文件容量。
优化等级越高,越容易影响仿真调试信息的保真度,同时,可能会影响到代码逻辑(例如if和else逻辑),以及函数内部需要再次声明的结构体而不再声明(加static可以解决)。
目前测试,建议使用level 0优化等级。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序卡在`HardFault_Handler`时,通常是由于程序发生了硬件故障或软件错误,导致CPU无法正常执行指令而触发了硬件故障异常。这种情况下,CPU会自动跳转到`HardFault_Handler`断服务函数进行处理。 要解决这个问题,可以尝试以下几个步骤: 1.检查程序是否存在空指针、数组越界、栈溢出等常见的软件错误。 2.检查硬件电路是否正常,例如是否存在电源电压不稳定、信号线接触不良等问题。 3.在`HardFault_Handler`断服务函数添加调试代码,例如打印寄存器状态、堆栈信息等,以便更好地定位问题。 以下是一个`HardFault_Handler`断服务函数的示例代码: ```c void HardFault_Handler(void) { __asm volatile ( " tst lr, #4 \n" " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" " ldr r1, [r0, #24] \n" " ldr r2, handler2_address_const \n" " bx r2 \n" " handler2_address_const: .word prvGetRegistersFromStack \n" ); } void prvGetRegistersFromStack( uint32_t *pulFaultStackAddress ) { /* These are volatile to try and prevent the compiler/linker optimising them away as the variables never actually get used. If the debugger won't show the values of the variables, make them global my moving their declaration outside of this function. */ volatile uint32_t r0; volatile uint32_t r1; volatile uint32_t r2; volatile uint32_t r3; volatile uint32_t r12; volatile uint32_t lr; /* Link register. */ volatile uint32_t pc; /* Program counter. */ volatile uint32_t psr;/* Program status register. */ r0 = pulFaultStackAddress[ 0 ]; r1 = pulFaultStackAddress[ 1 ]; r2 = pulFaultStackAddress[ 2 ]; r3 = pulFaultStackAddress[ 3 ]; r12 = pulFaultStackAddress[ 4 ]; lr = pulFaultStackAddress[ 5 ]; pc = pulFaultStackAddress[ 6 ]; psr = pulFaultStackAddress[ 7 ]; /* When the following line is hit, the variables contain the register values. */ for( ;; ); } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值