一、因为要做模块的升级所以需要用到bootloader。先捋清楚一下各步骤:
1.划分flash区间。我的模块是stm32f103RB,flash大小是128k字节(0x2000)。我打算将flash分为三个部分:bootloader(0x8000000~0x8004000)、应用程序代码(0x8004000~0x8012000)、待升级的IAP程序(0x8012000~0x8020000)。
2.划分FRAM区间,总共0x2000字节,bootloader要记录是否需要IAP所以需要占用一些FRAM空间(0x0000~0x0100),应用程序占用(0x0100~0x2000)。
3.bootloader先实现跳转到flash应用程序app的目标位置。(IAP先不考虑)
4.跳转后成功执行app。
二、先用cubeMX生成bootloader:
1.选择芯片
2.选上需要的功能,首先是必须的RCC、SYS、IWDG。因为要用到FRAM所以需要用到I2C2。
3.对照原理图核对管脚是否正确,并且增加LED灯用来辅助调试boot
FRAM-WP、LED3、LED4、LED5都设置为output。其他默认
4.配置时钟为72M
5.详细配置,默认即可
6.工程设置
然后生成代码即可。在代码中cubeMX只是初始化了配置,但是并没有使用,比如开启了看门狗,但是并未喂狗,需要自行喂狗。因为bootloader的特殊性,并不需要喂狗,也并不需要有什么内容是放在while(1)中的,因为bootloader只运行一次,主要就是执行跳转指令。
7.生成代码后打开工程,修改工程配置
因为bootloader大小设置为0x4000大小,所以size设置一下
然后就是debug设置为J-LINK,SWD
创建代码后如何验证程序可用呢?我在while(1)里加了延时和翻转小灯的状态,这样每循环两次小灯就闪烁一次。在这里我是一次成功的。当然,需要喂狗。
那么如何跳转到flash目标地址呢?
app_iap.c
#include "app_iap.h"
__asm void MSR_MSP ( uint32_t ulAddr )
{
MSR MSP, r0 //set Main Stack value
BX r14
}
//跳转到应用程序段
//ulAddr_App:用户代码起始地址.
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
pIapFun_TypeDef pJump2App;
if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
{
pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
pJump2App (); //跳转到APP.
}
}
app_iap.h
/*****************************************************************************
* FileName: app_iap.h
* Processor: Stm32F103
* Company: TY
* Overview: IAP程序升级
* Version: 1.5
*****************************************************************************/
#ifndef _APP_IAP_H_
#define _APP_IAP_H_
#include "stm32f1xx_hal.h"
/* 类型定义 ------------------------------------------------------------------*/
/************************** IAP 数据类型定义********************************/
typedef void ( * pIapFun_TypeDef ) ( void ); //定义一个函数类型的参数.
#define USER_ADDR_APP 0x08004000 // 应用程序的起始地址
#define USER_SIZE_APP 0xE000 // 应用程序的大小
#define USER_ADDR_UPDATE 0x08012000 // 存放升级文件的起始地址
#define USER_SIZE_UPDATE 0xE000 // 升级程序的大小
extern void IAP_ExecuteApp ( uint32_t ulAddr_App );
#endif
加上这两个文件,然后在主函数中调用IAP_ExecuteApp(USER_ADDR_APP);
为了验证这个bootloader好用,我们在这个bootloader的小灯初始化时,让LED3常亮,LED4、LED5长灭。
让应用程序初始化小灯时LED3为灭,并且LED5常量,LED4闪烁。这样如果成功的话,那么理论上LED3亮一下,随后熄灭,LED4闪烁,LED5常亮。
当然,要设置一下应用程序的配置,修改起始地址和大小
之后重新编译,烧录,但是结果并不对,LED3几乎是常亮,间歇短暂熄灭的时候LED4、LED5也短暂闪烁一下,随后熄灭。
找了很久的原因,以为是触发了看门狗,或者是没有关闭中断。但其实都不是,是因为向量表没有更改
在应用程序的中的这个文件VECT_TAB_OFFSET默认是0x00000000U,但是我们应该更改为起始地址中0x08004000的0x00004000U。
编译后,就可以正常跳转了。但是根据同事的意见,还是要在跳转指令前关闭一下全局中断才好。
__disable_irq(); // 关闭总中断
__enable_irq(); // 开启总中断
于是我在跳转指令前加了关闭总中断,但又出现跳转失败的情况。通过debug发现可以跳转到应用程序成功,但是还是会在初始化的过程中死机,所以我在应用程序中的第一句加上打开总中断,这时跳转成功并且应用程序运行正常了。
搞不清楚这个原因,时间有限暂且不研究了,不过这是一个值得尝试的地方。我还是暂且不用这两句话吧。
到此,bootloader的跳转功能实现了,接下来要实现程序升级。