0x00 前言
STM32有三种烧录程序的方式:烧录器下载、拉高boot1进行串口下载(ISP)还有通过在 Bootloader中进行flash擦除和写入(IAP)。
在进行开发之前,你需要了解一下Bootloader是什么。
Bootloader是嵌入式系统在加电后执行的第一段代码,也称为引导加载程序。它不属于操作系统内核,而是在操作系统内核或用户程序运行之前运行。
举个简单的例子。例如我的拓竹3D打印机,在开机之后,它的Bootloader会进行硬件自检、联网检查固件等操作,而一旦发现了有新版本的固件可以用,那么它就会提示你需要更新后才能使用,更新后它便会开始执行新的固件。
假设这台3D打印机的用到的芯片是STM32,那么它是如何实现这个操作的呢?
首先你的设备flash中,需要存储下一个Bootloader引导分区,然后是一个app应用分区。
当你的决定升级时,那么你就需要将app应用分区的内容擦除,然后用你的新固件去覆盖。
或者你决定不升级,那么在Bootloader程序结束后,直接进入到app应用分区之中。
那么你现在需要做的,就是编写一个Bootloader程序,然后再编写一个APP程序,并确保在bootloader程序结束后,能够准确的进入到app应用之中。你需要确保你的 STM32 flash够大,可以同时存下这两个程序。如果你有特殊需求,你甚至可以编写多个应用分区,以满足你不同的需求。
STM32的flash起始地址是0x0800 0000,八百万,很好记。意味着你每一次烧录程序,都是将程序从这个地址开始烧录;也代表着系统再运行的时候,每一次都是从这个地址开始加载程序。
至于flash结束地址,与你实际所使用的单片机有关系。本文选用了STM32F103C8T6开发板,最常见的小蓝板。它的flash大小是64kb,所以结束地址是0x0801 0000。
0x01 flash分区
做一件事情,最为重要的就是前期规划,不能想到什么做什么。
你需要将stm32的flash区间分为两段甚至多段。
我决定将STM32的 0x800 0000 - 0x800 2000 作为我们的Bootloader 地址段,这个段的大小大概是8KB,我之前写的Bootloader大小大概是5.6KB,分配8KB的原因为要为后续程序升级留有余地,否则牵一发而动全身,到时候重新修改flash分区则会导致很多地方需要修改。
然后我将剩下的flash分为两个大小一致的分区,打算放下两个app分区在里面。
这个是APP2在flash分区中所占用的地址段
0x02 Bootloader
在程序启动的时候,程序会从0x0800 0000地址加载代码,然后将其搬运至RAM(0x0200 0000)中以供执行。
所以我们的Bootloader自然是从0x0800 0000 开始。
在Bootloader中,主要需要实现两个功能:
- 选择需要进入的分区
- 进入选中的分区
在这里我选择了根据按键的状态进入选中的分区,如果按键1 按下,就进入APP1,如果按键2按下,就进入APP2。举个比较简单的例子,我用STM32F103C8T6写了两个游戏程序,但是如果要来回烧录程序来游玩不同的游戏就很麻烦,所以我用我现在的方式,实现了在开机后,按下按键1就执行游戏1的程序,按下按键2就执行另一个游戏2的程序。
你问我能不能通过编写多级菜单的方式来实现游戏数据的切换?我的回答当然是可以,但是你需要将两个游戏的工程合并到一个工程中,然后还要考虑到单片机的内存因素,编写很多内存管理的代码,略微麻烦,所以通过Bootloader来进行这个选择是比较好的方式。
那么主要靠下面的代码进入到APP1分区或APP2分区。
需要关中断和设置栈顶指针
#include "stm32f10x.h"
#include "key.h"
#define appxaddr 0x08000000
#define FLASH_APP1_ADDR 0x08002000
#define FLASH_APP2_ADDR 0x08006000
void iap_load_app(u32 appxaddr)
{
SysTick->CTRL = 0X00;//禁止SysTick
SysTick->LOAD = 0;
SysTick->VAL = 0;
__disable_irq();
jump2app=(iapfun)*(vu32*)(appxaddr+4);
MSR_MSP(*(vu32*)appxaddr);
jump2app();
}
int main()
{
Key_Init();
while(1)
{
if (Key1 == 1)
iap_load_app(FLASH_APP1_ADDR);
if (Key2 == 1)
iap_load_app(FLASH_APP2_ADDR);
}
return 0;
}
0x02 APP设置
在APP中,需要修改的地方比较少,需要为你的工程设置flash起始地址和flash结束地址,这样如果你编译的固件超过了规定的大小,会弹出报错来提示flash不够用。
设置完工程后,需要将中断向量地址表偏移指定长度。例如APP1之前有一个0x2000大小的Bootloader,那么你的中断向量地址表就需要偏移0x2000个字节。例如APP2,在这个地址之前有Bootloader和APP1一共0x6000大小,那么就需要偏移0x6000个字节。
如下代码,只需要加一句代码就足够
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main()
{
/* 中断向量表偏移 */
SCB->VTOR = FLASH_BASE | 0x2000;
LED_Init();
delay_init();
while(1)
{
LED_ON;
delay_ms(1000);
LED_OFF;
delay_ms(1000);
}
return 0;
}
0x03 升级程序编写
升级程序可以直接使用正点原子的IAP例程中的iap.c和stmflash.c,修改你的触发逻辑就可以了。
比如说 单机按键1就进入app1,长按按键1 就开始接收新程序并且切入到app1分区
还有 单机按键2就进入app2,长按按键2 就开始接收新程序并且切入到app2分区
0x04 固件烧录
烧录固件时需要注意固件烧录的起始地址,不要覆盖了之前的程序。并且一定要烧录二进制文件,不能烧录16进制文件,16进制的文件包含了一些地址信息可能会导致APP无法运行。
在Keil中需要将你的Bootloader程序设置地址为 0x0800 0000,然后 size 设置为 0x2000
将你的APP1程序设置地址为 0x0800 2000,然后 size 设置为 0x4000
将你的APP2程序设置地址为 0x0800 6000,然后 size 设置为 0x4000
需要烧录三次。也可以通过将bin文件合并的方式只烧录一次
写好了例程,需要的评论区留下邮箱