一步一步实现STM32-FOTA系列教程之Bootloader编写
文章系列链接
《一步一步实现STM32-FOTA系列教程之bin文件生成》
《一步一步实现STM32-FOTA系列教程之STM32-FLASH分区说明》
《一步一步实现STM32-FOTA系列教程之FLASH静态区读写》
前言
上一篇文章《一步一步实现STM32-FOTA系列教程之FLASH静态区读写》实现了对FLASH静态区读写的操作,有了这部分功能之后,就可以实现一个非常简单的Bootloader代码了。
转载请注明出处
Bootloader 功能说明
这里提供的Bootloader功能就非常的简单了,就是在Bootloader启动之后,读取FLASH静态区的参数信息,然后判断启动分区标志位的值,然后进入相应的分区,运行该分区的程序。
注意这里的教程中没有在 Bootloader 中编写联网获取新版本的代码,这部分的实现会放到主分区和备份分区的代码中实现。
Bootloader 启动流程
这个启动流程之前已经说过了,这里贴出来,方便对比代码。
函数实现
FLASH 分区宏定义
// FLASH 分区 配置
#define FLASH_BASE_ADDR ((uint32_t)0x08000000)
#define NLEDBOOTLOADER_SIZE (64*1024) // Bootloader 大小为 64KB
#define FIRMWAR_ONE_SIZE (80*1024) // 固件1 大小为 80KB
#define FIRMWAR_TWO_SIZE (80*1024) // 固件2 大小为 80KB
#define NLED_CONFIG_PARAM_SIZE (224*1024)
#define BOOTLOADER_START_ADDR (FLASH_BASE_ADDR) //Bootloader 启动地址
#define FIRMWAR_ONE_START_ADDR (FLASH_BASE_ADDR + NLEDBOOTLOADER_SIZE ) // 固件 1 启动地址
#define FIRMWAR_TWO_START_ADDR (FLASH_BASE_ADDR + NLEDBOOTLOADER_SIZE +FIRMWAR_ONE_SIZE) // 固件 2 启动地址
#define CONFIG_PARAM_START_ADDR (FLASH_BASE_ADDR + NLED_CONFIG_PARAM_SIZE) // FLASH 静态区参数 起始地址
程序跳转代码
在之前的 FLASH静态区参数读写的基础上。增加了对启动分区的判断还有加载启动分区的代码。
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
for(int i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF; /* 关闭中断*/
NVIC->ICPR[i] = 0xFFFFFFFF; /* 清除中断标志位 */
}
jump2app(); //跳转到APP.
}
}
//判断进行固件升级还是跳转APP
void LoadingFirmware(void)
{
if(bootflag ==1)
{
printf("Jump To Firmware First 0x%08X\r\n",FIRMWAR_ONE_START_ADDR);
iap_load_app(FIRMWAR_ONE_START_ADDR);
}
else if(bootflag ==2)
{
printf("Jump To Firmware First 0x%08X\r\n",FIRMWAR_TWO_START_ADDR);
iap_load_app(FIRMWAR_TWO_START_ADDR);
}
else
{
printf("boot error\r\n");
}
LED0 = 0;
}
主函数实现
int main()
{
ledInit();
uart1_init(9600);
delay_init();
printf("-----------------------------------\r\n");
printf("------------Bootloader-------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Version: V1.0\r\n");
LED0_Blink(100);
GetDeviceInfo();
LoadingFirmware(); /*判断加载启动分区*/
}
至此,如此简单的一个Bootloader 就编写完成,下面开始测试验证一下吧。
注意事项
1、由于Bootloader所占用的FLASH空间大小为64KB,如果在MDK中进行调试仿真时,注意修改MDK工程的ROM大小,如下图所示。
2、在使用JLINK仿真器下载代码的时候,注意下载时擦除 FLASH 扇区的方式不要选择全片擦除,这里选择擦除部分块,选择方式如下所示。
测试验证
验证说明
本次测试验证主要是验证该 Bootloader 能否加载指定分区中的程序,由于还没有编写远程获取固件的代码,因此这里采用调试编译仿真的方式将主分区和备份分区的代码烧写到 FLASH 中。
为了测试方便,这里仅仅提供两个最简单的分区代码,其中主分区的代码会在串口1循环打印Firmware1 running…,循环间隔为1秒;备份分区的代码则会在串口1循环打印Firmware2 running…,循环间隔为2.5秒。
Firmware1固件main文件实现
#include <string.h>
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
#include "logdebug.h"
#include "fotaprotocol.h"
//运行指示灯
void LED0_Blink(int xms)
{
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
}
int main()
{
NVIC_SetVectorTable(FIRMWAR_ONE_START_ADDR,0);
delay_init();
ledInit();
uart1_init(9600);
printf("-----------------------------------\r\n");
printf("------------Firmware1--------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Firmware-1-Version: V1.0\r\n");
while(1)
{
printf("Firmware1 running...\r\n");
LED0_Blink(200);
}
}
注意,代码编写完之后,还要修改MDK工程中ROM区域的设置,Firmware1固件的设置如下。
Firmware2固件main文件实现
#include <string.h>
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
#include "logdebug.h"
#include "fotaprotocol.h"
//运行指示灯
void LED0_Blink(int xms)
{
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
}
int main()
{
NVIC_SetVectorTable(FIRMWAR_TWO_START_ADDR,0);
delay_init();
ledInit();
uart1_init(9600);
printf("-----------------------------------\r\n");
printf("------------Firmware2--------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Firmware-2-Version: V1.0\r\n");
while(1)
{
printf("Firmware2 running...\r\n");
LED0_Blink(500);
}
}
注意,代码编写完之后,还要修改MDK工程中ROM区域的设置,Firmware2固件的设置如下。
验证步骤
- 首先将 Bootloader 利用 Jlink 烧写到 FLASH 中。
- 然后将 Firmware1 和 Firmware2 固件分别按照同样的方法烧写到FLASH中。
- 烧写完成后,按复位按钮查看启动日志。
烧写完成后,正常的测试现象是,首先启动 Bootloader 打印 Bootloader 相关信息,然后根据启动分区标志位加载不同的固件。
注意,由于在 Bootloader中加入了交替修改启动分区标志位的代码,因此每次重启单片机,Bootloader就会从不同的分区执行代码。这样也达到了测试Bootloader加载启动分区的效果了。
测试日志记录
下面附上测试验证的日志,注意单片机重启是手动硬件复位的,日志如下。
-----------------------------------
------------Bootloader-------------
-----------------------------------
Compile Time: Nov 13 2018,19:24:50
Version: V1.0
Static Params Address :0x08038000
start to boot firmware one
Testing...
set updateflag 2
Jump To Firmware First 0x08010000
-----------------------------------
------------Firmware1--------------
-----------------------------------
Compile Time: Nov 13 2018,19:39:26
Firmware-1-Version: V1.0
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
-----------------------------------
------------Bootloader-------------
-----------------------------------
Compile Time: Nov 13 2018,19:24:50
Version: V1.0
Static Params Address :0x08038000
start to boot firmware two
Testing...
set updateflag 1
Jump To Firmware First 0x08024000
-----------------------------------
------------Firmware2--------------
-----------------------------------
Compile Time: Nov 13 2018,19:33:58
Firmware-2-Version: V1.0
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
测试源码
测试源码中包含有Bootloader源码,主分区源码和备份分区源码,已经打包到一个压缩包中了,下面附上下载链接。
测试验证代码下载链接
参考文档
正点原子 STM32 系列开发板IAP实验