STM32的Bootloader主要负责在设备上电或复位后初始化硬件,然后加载并启动主应用程序。它还可以提供一些额外的功能,如通过串口、USB或其他接口进行程序烧录,支持固件升级,以及在开发过程中的调试。
本文章的bootloader实现以下功能:
v1.0.0 正常bootloader启动,升级
v1.0.1 增加备份功能,每次升级之前,把旧的代码备份,以防升级失败无法启动系统,只需在等待bootloader启动期间通过串口发送use backup system即可启用备份的系统
v1.0.2 可在等待bootloader启动期间通过串口发送downloadfile即可发送升级.bin文件升级
stm32有3种启动方式,当boot0置0时,将从单片机内部flash中启动,也就是0x08000000处。本文章用的就是这种方式。
所以我们可以把bootloader程序下载到0x08000000处,把应用程序放到flash的其他位置。再通过bootloader程序跳转到应用程序,或者升级程序。
这里用flash的前20KB存储bootloader程序,扇区5存储应用程序,扇区6存储待升级程序,扇区7存储备份的应用程序。
完整代码在这,设置的0积分,不允许动态调整积分,应该是免费的吧【免费】STM32F4bootloader程序资源-CSDN文库
以下是bootloader程序主要代码
bootloader.c
#include "qingtianluoluo_ota.h"
#include "main.h"
#include "stdio.h"
#define VERSION "v 1.0.2"
uint8_t g_use_backup_system = 0;
/**
* @bieaf 写若干个数据
*
* @param addr 写入的地址
* @param buff 写入数据的起始地址
* @param word_size 长度
* @return
*/
static void WriteFlash(uint32_t addr, uint32_t * buff, int word_size)
{
/* 解锁FLASH*/
FLASH_Unlock();
for(int i = 0; i < word_size; i++)
{
/* 对FLASH烧写*/
FLASH_ProgramWord(addr + 4 * i, buff);
}
/* 锁住FLASH*/
FLASH_Lock();
}
/**
* @bieaf 读若干个数据
*
* @param addr 读数据的地址
* @param buff 读出数据的数组指针
* @param word_size 长度
* @return
*/
static void ReadFlash(uint32_t addr, uint32_t * buff, uint16_t word_size)
{
for(int i =0; i < word_size; i++)
{
buff = *(__IO uint32_t*)(addr + 4 * i);
}
return;
}
/* 读取启动模式 */
unsigned int Read_Start_Mode(void)
{
unsigned int mode = 0;
ReadFlash((Application_2_Addr + Application_Size - 4), &mode, 1);
return mode;
}
/**
* @bieaf 进行程序的覆盖
* @detail 1.擦除备份区域代码
* 2.备份旧代码
* 3.擦除目的地址
* 4.源地址的代码拷贝到目的地址
* 5.擦除源地址
*
* @param 搬运的源地址
* @param 搬运的目的地址
* @param APP区域的总大小,不是代码大小
* @return
*/
void MoveCode(unsigned int src_addr, unsigned int des_addr, unsigned int byte_size)
{
/*1.擦除备份地址*/
printf("> Start erase backup flash......\r\n> ...\r\n");
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_7, VoltageRange_3);
FLASH_Lock();
printf("> Erase backup flash down......\r\n\r\n");
unsigned int temp[256];
/*2.开始备份*/
printf("> start backup......\r\n> ...\r\n");
for(int i = 0; i < byte_size/1024; i++)
{
ReadFlash((des_addr + i*1024), temp, 256);
WriteFlash((Backup_Addr + i*1024), temp, 256);
}
printf("> backup down......\r\n\r\n");
/*3.擦除目的地址*/
printf("> Start erase des flash......\r\n> ...\r\n");
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3);
FLASH_Lock();
printf("> Erase des flash down......\r\n");
/*4.开始拷贝*/
printf("> Start copy......\r\n> ...\r\n");
for(int i = 0; i < byte_size/1024; i++)
{
ReadFlash((src_addr + i*1024), temp, 256);
WriteFlash((des_addr + i*1024), temp, 256);
}
printf("> Copy down......\r\n\r\n");
/*5.擦除APP2区地址*/
printf("> Start erase src flash......\r\n> ...\r\n");
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_6, VoltageRange_3);
FLASH_Lock();
printf("> Erase src flash down......\r\n");
}
/**
* @brief 使用之前的系统启动
* @param void
* @return void
* @NOTE
*/
void use_backup_system(void)
{
printf("> use backup system\r\n");
/*擦除目的地址*/
printf("> Start erase des flash......\r\n> ...\r\n");
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3);
FLASH_Lock();
printf("> Erase des flash down......\r\n");
unsigned int temp[256];
/*开始拷贝*/
printf("> Start copy......\r\n> ...\r\n");
for(int i = 0; i < Application_Size/1024; i++)
{
ReadFlash((Backup_Addr + i*1024), temp, 256);
WriteFlash((Application_1_Addr + i*1024), temp, 256);
}
printf("> Copy down......\r\n\r\n");
}
/* 采用汇编设置栈的值 */
__asm void MSR_MSP (uint32_t ulAddr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
/* 程序跳转函数 */
typedef void (*Jump_Fun)(void);
void IAP_ExecuteApp (uint32_t App_Addr)
{
Jump_Fun JumpToApp;
/* 检查栈顶地址是否合法 */
if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )
{
printf("\r\njump to the app...\r\n\r\n\r\n");
/* 禁用所有中断,记得在APP程序打开中断 */
INTX_DISABLE();
/* 用户代码区第二个字为程序开始地址(复位地址) */
JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);
/* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
MSR_MSP( * ( __IO uint32_t * ) App_Addr );
/* 跳转到APP */
JumpToApp();
}
printf("jump to app failed\r\n");
}
extern void ota_update(void);
extern uint8_t g_updata_flag;
/**
* @bieaf 进行BootLoader的启动
*
* @param none
* @return none
*/
void Start_BootLoader(void)
{
/*==========打印消息==========*/
printf("\r\n\r\nbootloader by qingtian-luoluo\r\n");
printf("***********************************\r\n");
printf("* *\r\n");
printf("* BootLoader *\r\n");
printf("* *\r\n");
printf("***********************************\r\n");
printf("bootloader version: %s\r\n\r\n", VERSION);
extern uint16_t g_wait_start_ms;
g_wait_start_ms = 0;
printf("waitting for start bootloader\r\n");
while(g_wait_start_ms < 5000)
{
if(g_wait_start_ms % 500 == 0)
{
g_wait_start_ms++;
printf("- ");
}
/* 等待期间处理调试串口的消息 */
User_uart_handle();
if(g_updata_flag || g_use_backup_system)
{
break;
}
}
TIM_DeInit(TIM4);
/* 通过串口下载升级程序,并检验无误后进行升级 */
if(g_updata_flag)
{
ota_update();
}
/* 使用备份的程序,每次升级都会备份之前的程序 */
if(g_use_backup_system)
{
use_backup_system();
}
else
{
switch(Read_Start_Mode())//读取是否启动应用程序
{
case Startup_Normol://正常启动
{
printf("> Normal start......\r\n");
break;
}
case Startup_Update://升级再启动
{
printf("> Start update......\r\n");
MoveCode(Application_2_Addr, Application_1_Addr, Application_Size);
printf("> Update down......\r\n");
break;
}
case Startup_Reset://恢复出厂设置 目前没使用
{
printf("> Restore to factory program......\r\n");
break;
}
default://启动失败
{
printf("> Error:%X!!!......\r\n", Read_Start_Mode());
return;
}
}
}
/* 跳转到应用程序 */
printf("> Start up......\r\n\r\n");
IAP_ExecuteApp(Application_1_Addr);
}
bootloader.h
#ifndef __LEAF_OTA_H_
#define __LEAF_OTA_H_
/*=====用户配置(根据自己的分区进行配置)=====*/
#define BootLoader_Size 0x5000U ///< BootLoader的大小 20K
#define Application_Size 0x20000U ///< 应用程序的大小 128K
#define Application_1_Addr 0x08020000U ///< 应用程序1的首地址
#define Application_2_Addr 0x08040000U ///< 应用程序2的首地址
#define Backup_Addr 0x08060000U ///< 备份程序的首地址
/*==========================================*/
/* 启动的步骤 */
#define Startup_Normol 0xFFFFFFFF ///< 正常启动
#define Startup_Update 0xAAAAAAAA ///< 升级再启动
#define Startup_Reset 0x5555AAAA ///< ***恢复出厂 目前没使用***
void Start_BootLoader(void);
#endif
在bootloader程序里,跳转应用程序之前关闭所有中断,以防跑飞
然后就是下载bootloader程序,烧录的时候注意起始位置和大小
OK,到这里,一个bootloader程序就几乎是完成了。接下来就是应用程序。
由于在bootloader程序跳转的时候关闭了所有中断,所以第一时间肯定是要开启中断的。然后就是设置中断向量表偏移,可以选择直接修改VECT_TAB_OFFSET宏,也可以直接修改寄存器。
再然后就是修改程序运行地址,由于这里的应用程序是扇区5(0x08020000),最大限制为一个扇区128KB,所以要设置下载位置跟大小
再再然后就是把应用程序下载到芯片上,记得修改下载位置跟大小
OK,到这里就已经实现了通过bootloader去启动一个应用程序。接下来就是升级程序,升级程序要用到的是.bin文件,但是keil不能直接生成.bin文件,所以要进行一些小小的设置。
D:\keil5\ARM\ARMCC\bin\fromelf.exe --bin --output ..\APP_Code.bin ..\Output\STM32F4_Temp.axf
其实就是利用了keil自带的一个功能来生成
其中D:\keil5是keil5的安装目录
\ARM\ARMCC\bin\fromelf.exe是安装路径下的一个可执行文件
--bin --output输出 .bin文件
..\APP_Code.bin 在.uvprojx项目文件所在目录的上一级目录生成APP_Code.bin文件
..\Output\STM32F4_Temp.axf 从.uvprojx文件所在目录的上一级目录的\Output目录里的\STM32F4_Temp.axf文件生成。这里是STM32F4_Temp.axf,具体名称取决于keil5设置的输出文件名称。
以上路径,可以根据实际需要进行修改。
再编译一下,就得到了.bin文件,然后再自己各显神通,把.bin文件写到单片机内部flash的扇区6。并且把扇区7-4的地址,也就是(0x08060000 - 0x04),在这写入0xAAAAAAAA,再重新上电,bootloader程序检查到这里被置0xAAAAAAAA之后,就会备份,升级,然后启动新应用程序。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_51077191/article/details/139093817
|