项目介绍
本项目包含IAP程序与APP程序。APP程序有部分共用代码(包括main\stm32标准库\RTOS\BSP)都编译进库中。另外部分特殊代码由其它程序生成后,连接共用代码库生成目标bin文件,通过IAP下载至FLASH中。
问题描述
1 使用keil编译生成的IAP程序
相同的APP代码,在keil工程中编译下载后可以正常执行(没有生成库,直接编译链接所有代码)。但是使用arm_none_eabi_gcc交叉编译工具链编译链接(先编译一部分代码生成库,再编译其它代码并与库链接生成目标bin文件。)则不能正常执行。
2 不使用IAP模式,直接编译APP程序
keil下仍一切正常,使用arm_none_eabi_gcc仍不正常。
开发环境
arm_none_eabi_gcc交叉编译工具链
自已开发的IAP下载工具
自己开发的图形化编译工具
自己开发的库文件生成工具
目标硬件
国霖电子生产的GPL-14MT工控板,板载stm32f103rbt6。
使用到的软件
STM32 ST-LINK Utility
keil rvmdk
使用到的调试工具
ST-Link V2
查阅的文档
《Cortex-M3权威指南》
《stm32用户手册》
《stm32f103RBT6芯片手册》
解决过程
1 确认一遍IAP工作要点(后方会提到,虽然做了这一步骤,但终究漏掉了一个致命问题点。)
修改中断向量表地址
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x6000); // NVIC_VectTab_FLASH=0x08000000
ld文件中设置FLASH基地址及长度
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20005000; /* end of 20K RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x800; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08006000, LENGTH = 104K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
2 排除IAP程序的错误
使用KEIL编译相同的代码,同样的IAP程序,跳转后却能正确执行,故可以认为IAP程序工作正常。(这个结果对我产生了误导,后文会提及)
3 怀疑是编译选项不正确
反复排查尝试了很多次,但是得到的是各种诡异的结果。在初始化IO口后立刻控制LED灯闪烁,程序工作正常。如果通过串口输出调试信息编码,有时候不能输出,有时候能正确输出,但输出几个就没有了。什么鬼?同样的APP程序使用keil编译出来明明工作正常的,但是使用gcc编出来,看上去程序执行了,证明IAP程序是正确的,为何功能代码却执行不成功?明明是非常简单的几乎不可能出错的代码呀,如果这都能写错,简直是对我这个资深老鸟的侮辱!是可忍孰不可忍!
4 不能忍还是要忍
做了无数个深呼吸,终于能够压抑住暴躁的内心,重新来审视代码。看上去编译选项应该没错,因为毕竟部分代码已经执行成功了,证明编译连接都没问题。我不得不怀疑是不是很久没写STM32的代码,能力退化,导致犯了什么低级错误。于是退出IAP状态,将APP改成正常的工作模式,连接文件也做了修改。使用ST-Link烧录下去,自然,仍然是不能正常工作。
这两步费时最多,整整折腾了一个白天两个晚上(周六晚上、周日全天、周一晚上),死盯着电脑,熬到很晚,人处于懵逼沮丧崩溃状态。
5 大受打击,暂时收手
(周二晚上没干活,早早睡觉)
6 养精蓄锐,卷土重来
周三项目正好交接,事情比较少,心情比较好,加上前一天休息比较好,晚上下班状态可以,继续奋斗!折腾了一会,想到STM32 ST-Link Utility可以查看寄存器状态,于是调出来看了一眼,马上看出点问题来了:
IPSR = 0x2c?
IPSR保存的是中断表项的地址偏移量,0x2c对应SVC中断。系统服务调用中断?我明明没有启动SVC中断,为什么会进这个中断?一定有问题!进了这个中断一定会执行SVC_Handler函数,既然我没有定义SVC_Handler函数,那么APP程序就是死在默认的中断处理函数的死循环里了!终于能够解释为何程序死机了!为了验证这点,我在stm32f10x_it.c中增加了一个SVC_Handler(),什么也不做,只让LED灯不断闪烁(之前调试LED灯是正常的)。执行后发现,居然什么都没发生,现象没变!这就更诡异了。为何?我查看了一下map文件(其实查看一下STM32 ST-Link Utility中的target memory也能发现,中断向量表中对应的几个地址值都是一样的),发现SVC、HardFault、 BusFault等异常的处理函数的入口地址都是一样的,但是我明明在stm32f10x_it.c中有定义相应的异常处理函数,为何没有链接到APP程序中?
到了这一步,问题已经显而易见了,我写的中断处理函数没有链接到程序中,导致中断处理不正常。暂时将startup_stm32f10x_md.s中几个weak标记的中断处理函数全部注释掉,重新编译,果然中断向量表看上去正常了,执行无IAP的程序,正常运行!
8 最终胜利
重新下载IAP程序,进入IAP模式,相应修改APP,通过IAP下载到FLASH,执行,仍然不正常。但是排除了上面几个问题后,信心大增,斗志昂扬,已经预感胜利近在眼前了。这种状态下思路特别清晰,仔细回想梳理一下整个过程,猛然想到,虽然明明知道IAP跳转前应该关闭所有中断,但是我偏偏忘记了关闭SysTick中断!这个中断1ms触发一次,必然会出问题。马上关闭它,在跳转前添加一句:
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; /* Disable SysTick Timer */
编译下载,然后再通过IAP下载一次APP程序,开关切换,跳转!果然,期待已久的灯亮起来了,程序运行成功!
最终定位问题点汇总
1 编译生成库时没有把stm32f10x_it.c中的函数包含进去,因为该文件中只有异常和中断处理函数,没有任何地方显式地调用了该文件中的任何函数,而startup_stm32f10x_md.s中又提供了相应的异常与中断处理函数,虽然被声明为weak属性,但没有被正确覆盖,即使用.global声明存在这些外部函数都不行。
2 IAP程序跳转至APP程序入口前,没有将SysTick中断关闭,导致进入APP后未重新配置SysTick前又进入了老的中断处理函数,流程全乱了。
总结及心得体会
1 如果用到了IAP程序,则在跳转至APP程序前,一定要把IAP所有开启的中断全部关闭,任何一个没关闭都是隐患,看起来现象非常诡异,各种异常情况都可能出现,导致调试时各种被误导,浪费大量时间。
2 gcc编译器对于weak属性的函数似乎支持得不好。
3 坚信不会出问题的地方也是有可能出问题的~~~~不要偷懒,一个个验证吧。我一直在想生成库的环节不会有问题,所以下意识地偷懒,一直没有去验证它。如果早一点直接把所有代码一起编译,可能早就定位到问题了。(有时间要验证一下)
3 要了解工具,善用工具。工具结合文档手册,再加上大脑分析综合,才是正确的debug之道。
4 不要相信眼睛看到的表象(Keil编译生成的IAP与APP程序都能工作正常,所以IAP程序应该没问题,APP应该也没问题) ,要深入灵魂,也就是寄存器和存储器,才可以精确定位问题,而不是应该如何如何。
5 张驰有度、劳逸结合,工作效率更高!休息好了脑子更活跃,想法更多,更容易迸发灵感,找出问题所在。
6 不要轻易否定自己,也不要急于求成,保持沉着冷静很重要。