项目场景:
通过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优化等级。