参考网上的资料先学习了IAP实现的基本原理,结合自己板子的实际硬件资源,把芯片自带的512K内部Flash分为两部分,一部分为BootLoader实现IAP和应用程序的加载(跳转),另一部分是应用程序,升级的固件文件存放在外置的NandFlash系统中。为了把BootLoader做的更通用和漂亮一些,启用了RTX操作系统和Emwin,代码量大概在130K左右,因此,BootLoader放在了0~0x30000,应用放在了0x30000~0x80000。
芯片启动时首先运行BootLoader,BootLoader启动NandFlash文件系统,检查是否需要更新固件,NandFlash中放置了一个needup.bin文件,内容为空,只要存在needup.bin文件就说明需要升级,固件放在文件名为firmware.bin中。如果需要升级固件,启动固件升级进程,利用LPC1788的IAP功能,擦除扇区,将firmware.bin文件内容编程到0x30000开始的地方,然后跳转到0x8 0000运行APP。
如果不需要升级固件(没有needup.bin),则直接跳转到0x8 0000运行APP。
在跳转到0x8 0000开始运行APP前,首先判断一下0x80000处的数据(堆栈指针)是否在0x1000 0000~0x1000 FFFF,如果在范围里则为正确的APP,否则没有有效的APP,则不跳转到APP,直接进入到BootLoader程序。
为了防止在将固件文件写到内部Flash过程中出现问题,或固件本身就有问题,导致下载完成后不能正常运行,需要有一种直接进入BootLoader程序的方法,本系统有个按键,就设计为如果开机时按键被按下则直接进入到BootLoader而不加载APP应用,在BootLoader程序下启动USB、串口等进行固件的重新下载。
在实际编程的过程中出现了两个特殊的问题,经过努力查找问题最终得到了解决,下面将两个问题做个总结。
第一个问题是BootLoader程序调用LPC1788的IAP程序代码出现HardFault,在百度、Google各种查找,有一个人也出现类似的问题,但是并没有解决。最后不得已从main函数第一行开始防止IAP的测试代码,发现刚进main函数时调用IAP代码是不出错的,这就给我们一个启示,肯定是程序的某个地方触发了此问题。把IAP的测试代码往后放,放到while(1)前发现出现了HardFault,对这两个地方用仿真器进行跟踪,发现当代码停到两个调用代码前时,IAP所在(0x1FFF1FF0)的程序代码竟然不同。在后面停下时的代码明显的不对,好多mov r0, r0。为了找出来到底是哪句代码出了问题,从上到下一行一行的测试,最终找到了问题,原来在初始化EMC时修改了LPC_SC->MARB,是为了提高LCD占用总线的权限,设置LCD刷新拥有做高权限,数据访问次之,程序再次,其余最低。把这句话屏蔽后,采用缺省的总线仲裁设置后,能够正常的调用IAP。也许出现这种情况的可能性不高,但自己趟过的坑能告诉别人,让别人免走弯路也是一件好事。
第二个问题是从BootLoader跳转到APP时,APP并不能正常的运行。代码也是按照正常的跳转处理的,读取0x30000处的4个字节设置MSP(堆栈指针),将把0x30004的4个字节装入PC进行跳转,APP程序也正确设置了终端向量到0x30000。由于在BootLoader程序中看不到跳转后的源代码,但结合APP的map文件大致能知道程序运行到了哪个函数,用仿真器一直跟踪跳转后的汇编代码,发现程序也是最终停在了HardFault。用仿真器直接调试APP程序,要设置仿真器的初始化文件,如下图所示。其中runflash.ini文件内容如下:
SP = _RDWORD(0x00030000); // Setup Stack Pointer
PC = _RDWORD(0x00030004); // Setup Program Counter
其实就是设置仿真器进入调试状态是把MCU的SP和PC从哪里装载值,这个从BootLoader跳转的代码作用是一样的。进入调试状态,整个APP能够正常运行。这就说明我们在跳转到APP时,由于BootLoader本身已经设置了很多寄存器,导致APP的初始化过程和现有的寄存器设置有冲突。网上资料显示一定要关闭中断,在跳转前加了关全局中断的代码,不管用。又说要把RCC和NVIC复位,做了也不太管用。后来经过思考,最好的解决方法是BootLoader跳转到APP时把MCU做软复位,然后在执行尽量少的代码的情况下进行跳转,这样BootLoader的影响就很小,几乎等同于直接运行APP程序,能够尽早的把控制权交给APP程序。
软复位的代码还是比较好做的,如下:
__set_FAULTMASK(1);
NVIC_SystemReset();
while(1);
经测试也好用,现在的问题是等MCU重新启动后,如何判断是为了进APP的软重启还是用户断电后的硬启动,这个可以通过读取复位源标识寄存器(RSID—0x400F C180) 确定,如果此寄存器的第4位SYSRESET置位,说明处理器由于系统请求复位而被复位。但是在BootLoader的界面中也有一个复位按钮,也是通过这个方式重启的,这样就不能完全区分出来了。
为了解决这个问题,就在思考有哪些信息能够重启而不会丢失呢?写文件肯定是可以的,但为了让BootLoader尽快进入APP不能加载文件系统。写EEPROM肯定也是可以的,但是EEPROM被APP用了。还有个能用的就是RTC的通用寄存器,LPC1788有4个RTC通用寄存器(GPREG0~GPREG4–地址 0x4002 4044~0x4002 4054),这类寄存器可在主电源断开时保存重要的信息。芯片复位时,不会影响到这些寄存器中的值。而我们的APP正好也没有用到这四个寄存器,就选用其中的GPREG0保存重启的原因。
剩下的工作就简单了,需要跳转到APP时将GPREG0的值置为1,然后软重启。BootLoader程序在SystemInit函数(MCU启动后执行的第一段代码)中加入判断GPREG0的值的代码,如果GPREG0的值为0正常继续启动BootLoader,如果值为1直接跳转到APP。
经过测试发现此方法可行,能够很顺利的从BootLoader跳转到APP。
最终实现的效果可以参考下面的视频:
https://v.youku.com/v_show/id_XMzgxMzczNjk2NA==.html?spm=a2h3j.8428770.3416059.1
在实现过程中参考的资料如下:
NXP LPC1788(ARM Cortex m3) IAP 带UcosII 完结:
https://blog.csdn.net/kennann/article/details/45541511
关于ARM CM3的启动文件分析:
https://www.cnblogs.com/strongwong/p/8657639.html
STM32IAP升级-----编写IAP升级遇到的问题总结:
https://blog.csdn.net/super_demo/article/details/32086541
IAP与APP互相跳转的实现
https://blog.csdn.net/grublyboy/article/details/54909957
LPC1768更改IROM地址,用KEIL生成不了正常的.bin文件,而是生成奇怪的.bin文件夹
https://blog.csdn.net/robert_wzf/article/details/72968532
Keil5生成BIN文件及HEX文件介绍:
https://blog.csdn.net/langeldep/article/details/78202486