实际上,BootLoader不仅仅在操作系统上使用,在一些内存小,功能应用较为简单的单片机设备上面也可以通过BootLoader来完成固件升级。
我之前也有发过一些关于STM32远程升级的文章,但用的是第三方BootLoader,而且是基于操作系统实现的。BootLoader占用的内存也比较大,而且不开源。
所以这一讲我就来介绍一下如何自己制作一个简单的BootLoader程序。
1 环境搭建
关于STM32以及Keil的环境这里就不具体介绍了,网上教程也很多,不懂的同学自行查阅资料。
2 BootLoader工作原理以及常见分区介绍
不管用的是什么MCU,要实现固件升级都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现固件升级。
常见分区方式介绍:
1.Application
没有加入Bootloader之前,我们单片机内部的flash就是一整块的,所有的应用代码都放在这。
2.Bootloader + Application
在原有的flash区域里面划分出两个区域,Bootloader和Application,这种分区方式的好处在于既可以实现升级功能,App区又可以分到较大的空间,缺点是没有存放新固件的区域,需要从外部导入进来,而且一旦传输的过程被异常打断,那么原有的App代码也无法正常运行了,也就是传说中的“变砖”。
3.Bootloader + Application + Download
这种分区方式是比较万能的一种,优点是新固件是先存放到Download区的,哪怕搬运的过程中出现异常中断的情况,也不会“变砖”,缺点是需要单独划分一块内存跟APP区差不多的区域用来存放新固件,变相的减少了APP区的空间,对于内存较小的单片机来说压力就比较大了。
4.Bootloader + Application1 + Application2
这种方式可以同时存在两套App,优点在于升级了新固件以后,还保留了原来的旧版固件,必要的时候还可以进行版本的回退。
5.Bootloader + Setting + Application + Download
这种方式跟第3种基本一样,只是增加了一个区域用来存放升级相关的一些参数以及用户的一些配置。
3 BootLoader的制作
BootLoader的制作需要根据实际的需求来做,不同的运行方式或者升级方式在做法上都是有区别的,包括BootLoader所需要的内存空间也不尽相同。
不过不管是用什么方式,Bootloader都应该尽可能做的更小更简洁,这样的话内存的开销就更小,对于内存较小的MCU来说压力就没那么大了。
我下面要做的这个bootloader是上面讲的常见分区方式里面的第5种。
分区介绍:
我用的是STM32F103,内存是128K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。
分区表如下:
name | offset | size |
---|---|---|
boot | 0x08000000 | 0x00003000 |
setting | 0x08003000 | 0x00001000 |
app | 0x08004000 | 0x0000E000 |
download | 0x08012000 | 0x0000E000 |
功能描述:
运行bootloader的时候先从setting里面读一些参数,确定是否需要升级,如果需要,则把download
分区的固件搬运到app
分区,如果不需要升级则直接跳转到app
分区.
至于新固件的下载传输过程,我放到App里面去处理了,这跟我的项目实际需求有关系,App部分这里就先不往下拓展了,后面我会专门写一篇博客来介绍。
各个功能模块的具体讲解:
1、分区定义
先把各个分区的内存地址以及大小定义好,方便后面使用。
#define FLASH_SECTOR_SIZE 1024
#define FLASH_SECTOR_NUM 128 // 128K
#define FLASH_START_ADDR ((uint32_t)0x8000000)
#define FLASH_END_ADDR ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))
#define BOOT_SECTOR_ADDR 0x08000000 // BOOT sector start address
#define BOOT_SECTOR_SIZE 0x3000 // BOOT sector size
#define SETTING_SECTOR_ADDR 0x08003000 // SETTING sector start address
#define SETTING_SECTOR_SIZE 0x1000 // SETTING sector size
#define APP_SECTOR_ADDR 0x08004000 // APP sector start address
#define APP_SECTOR_SIZE 0xE000 // APP sector size
#define DOWNLOAD_SECTOR_ADDR 0x08012000 // Download sector start address
#define DOWNLOAD_SECTOR_SIZE 0xE000 // Download sector size
2、程序跳转
Bootloader作为引导程序,最重要的工作之一就是通过内存跳转进入用户程序,下面这段代码可以跳转到任何一个内存地址。
uint8_t jump_app(uint32_t app_addr)
{
uint32_t jump_addr;
jump_callback cb;
if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000)
{
jump_addr = *(__IO uint32_t*) (app_addr + 4);
cb = (jump_callback)jump_addr;
__set_MSP(*(__IO uint32_t*)app_addr);
cb();
return 1;
}
return 0;
}
3、处理函数
从setting区里面读取process状态值,然后进行对应的处理,如果需要升级则把download区的固件搬运到app区,然后再运行新APP,如果不需要升级则直接跳转到APP。
process = get_boot_state();
switch (process)
{
case START_PROGRAM:
printf("start app...rn");
delay_ms(50);
if (!jump_app(APP_SECTOR_ADDR))
{
printf("no programrn");
delay_ms(1000);
}
printf("start app failedrn");
break;
case UPDATE_PROGRAM:
printf("update app program...rn");
app_addr = APP_SECTOR_ADDR;
down_addr = DOWNLOAD_SECTOR_ADDR;
printf("app addr: 0x%08X rn", app_addr);
printf("down addr: 0x%08X rn", down_addr);
printf("erase mcu flash...rn");
mcu_flash_erase(app_addr, APP_ERASE_SECTORS);
printf("mcu flash erase successrn");
printf("write mcu flash...rn");
// memset(down_buf, 0, sizeof(down_buf));
for (i = 0; i < APP_ERASE_SECTORS * 8; i++)
{
mcu_flash_read(down_addr, &down_buf[0], 128);
delay_ms(5);
mcu_flash_write(app_addr, &down_buf[0], 128);
delay_ms(5);
down_addr += 128;
app_addr += 128;
}
printf("mcu flash write successrn");
set_boot_state(UPDATE_SUCCESS);
break;
case UPDATE_SUCCESS:
printf("update successrn");
boot_state = UPDATE_SUCCESS_STATE;
write_setting_boot_state(boot_state);
set_boot_state(START_PROGRAM);
break;
default:
break;
}
完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87462312
4 烧录下载配置
我们的Bootloader做好以后需要烧录到MCU里面,可以直接用Keil uVison来下载,也可以用J-Flash或者其他,这个都没关系,但是要注意内存的分配,要把固件烧到对应的内存地址上。
我这里做出来的bootloader bin只有8K,不过为了方便后续在这部分增加新功能,我实际分配了12K的空间,地址区间是0x08000000-0x08003000。
如果是用keil下载的话,需要注意flash的配置,具体如下:
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08000000就好了。
5 运行测试
注:这里我没讲解App部分代码,你们只看Bootloader部分的log就好了,不影响的,想看APP部分可以看我另外一篇文章,或者下载完整的代码实际跑一下也行。APP部分讲解:STM32 IAP应用开发——通过USB实现固件升级
运行结果:
不需要升级时直接跳转到App区,如下图:
需要升级时先从download区搬运新固件到app区,然后再跳转到App区,如下图:
结束语
好了,关于自制BootLoader的介绍就讲到这里,本文只是提供一个思路,不是唯一的方法,关键还是看你自己实际的需求。
还有App那部分这里没详细讲,我单独写了一篇文章,链接在下方,合到一起看就比较清晰了。或者你也可以下载完整的源码自己去跑一下,下面的源码我把BootLoader和APP都上传了。
APP部分讲解:STM32 IAP应用开发——通过USB实现固件升级
完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87462312
如果你有什么问题或者有更好的方法,欢迎在评论区留言。