RT-Thread STM32F4 自制 BootLoader 的制作和使用(串口YModem协议升级方式)
前言介绍
上一篇文章介绍了RT-thread通用BootLoader的制作和使用,这篇开始介绍自制BootLoader的方法。
本篇只讲下位机相关,上位机相关放到另外一篇中介绍。
我们先开始讨论下为什么要自制BootLoader?我个人认为有以下原因:
1.通用BootLoader的占用空间问题,如果你的APP程序对于FLASH空间占用比较大,通用的BootLoader恐怕不能满足需求。通用BootLoader使用起来是要有备份区的,如果没有备份区,则自制BootLoader会更能处理升级失败的问题。
2.通讯接口问题,如果你不采用串口或者网络升级,则目前不能使用通用BootLoader,例如你要采用CAN接口进行升级。
大致流程和开发工具
框架: 我是采用串口升级的方式来进行升级的。当然升级自然是有上位机的,上位机的开发平台是 Android ,升级的平台为STM32F4系列。通讯协议采用串口YModem协议。大致流程为:
1.上位机界面读取升级文件(.bin文件)
2.将文件内容采用串口YModem协议的方式发送给下位机
3.下位机将收到内容直接写入APP区
4.接收完成并确认文件完整
5.重启程序并跳转到APP程序。
开发工具: 上位机:Android Studio 下位机:RT-thread Studio
分区设计
分区名称 | 起始地址 | 分区大小 |
---|---|---|
BootLoader分区 | 0x08000000 | 128KB |
APP分区 | 0x08020000 | 896KB-4字节 |
升级标志位地址 | 0x08100000-4 | 4字节 |
根据F4系列芯片的FLASH块设计,前128KB 的FLASH是比较下的块组成,后面的896KB均为128KB的FLASH块组成,由于F4系列的FLASH读取和写入操作是以块为单位的,因此前面的128K适合BootLoader程序使用,后面的则适合APP程序使用。
从分区设计大小也可以看出,自制的BootLoader可以操作APP分区会更大。
中断向量地址偏移
在APP程序中的中断向量偏移地址为0x00020000,是起始地址0x08000000到APP分区地址0x08020000的差值。
整体思路
1.关键的flash写入和分区设置采用fal组件完成
2.通讯协议采用YModem组件和ymodem_ota组件完成
3.采用4个字节的升级成功标志位来控制是否升级成功,由于去掉了备份区,因此接收到的文件会直接写入到APP分区,如果中途升级失败,则只能再次进行升级直到升级成功为止。
4.首次烧录BootLoader后,首次启动,必须采用在线升级方式进行程序升级,因为此时的升级标志位是升级失败的状态,必须进行一次在线升级成功后,后续的才可以进行JLink烧录程序进行APP程序更新。
当前如果你有别的需求需要改的,也可以根据具体的需求进行修改。
程序编写
1.新建RT-thread项目
命名为BootLoader如下图,其中的串口接收和发送端口和芯片型号需要根据自己的实际情况进行配置。
2.进行RT-thread setting 配置
1.由于我使用的串口升级端口与控制台使用的端口冲突,因此关闭了finsh功能
2.开启libc组件和YModem组件和CRC校验
3.添加ota_downloader组件和fal组件
4.添加完成后编译一次。
3.文件建立
1.在applications 文件夹下建立IAP.c文件、IAP.h文件、sys.c文件 和sys.h文件。
4.文件的编写和分析
1. IAP.c 文件内容
#include "IAP.h"
#include "ymodem.h"
#include "ymodem_ota.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define IAPFlag_Value 0x11111111 //IAP 升级成功 标志位 值
bool YModem_IAPFunction() //IAP 升级功能
{
return ymodem_ota_Function();
}
void Start_App(void) //跳转到 下载的程序地址
{
jump_to_app(FLASH_APP_ADDR);
}
//读取指定地址的字(32位数据)
//faddr:读地址
//返回值:对应数据.
uint32_t STMFLASH_ReadWord(uint32_t faddr)
{
return *(__IO uint32_t*)faddr;
}
bool ReadIAPFlag(void) //读取 升级标志 判断是否设置了升级
{
uint32_t Value=0;
Value=STMFLASH_ReadWord(STM32_UpdataFlag);
// rt_kprintf("STM32_UpdataFlag value = 0x%08X",Value);
if(Value==IAPFlag_Value) return true;
else return false;
}
uint32_t GetSector(uint32_t Address)
{
uint32_t sector = 0;
if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
{
sector = FLASH_SECTOR_0;
}
else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
{
sector = FLASH_SECTOR_1;
}
else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
{
sector = FLASH_SECTOR_2;
}
else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
{
sector = FLASH_SECTOR_3;
}
else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
{
sector = FLASH_SECTOR_4;
}
else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
{
sector = FLASH_SECTOR_5;
}
else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
{
sector = FLASH_SECTOR_6;
}
else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))
{
sector = FLASH_SECTOR_7;
}
else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8))
{
sector = FLASH_SECTOR_8;
}
else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9))
{
sector = FLASH_SECTOR_9;
}
else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10))
{
sector = FLASH_SECTOR_10;
}
else/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11))*/
{
sector = FLASH_SECTOR_11;
}
return sector;
}
HAL_StatusTypeDef FLASH_EraseSector(uint32_t Sector,uint32_t VoltageRange)
{
uint32_t PageError = 0; //设置PageError,如果出现错误这个变量会被设置为出错的FLASH地址
FLASH_EraseInitTypeDef My_Flash; //声明 FLASH_EraseInitTypeDef 结构体为 My_Flash
//擦除当前扇区
My_Flash.TypeErase = FLASH_TYPEERASE_SECTORS; //标明Flash执行页面只做擦除操作
My_Flash.Banks=FLASH_BANK_1;
My_Flash.NbSectors=1 ;//说明要擦除的页数,此参数必须是Min_Data = 1和Max_Data =(最大页数-初始页的值)之间的值
My_Flash.Sector=Sector;
My_Flash.VoltageRange=VoltageRange;
return HAL_FLASHEx_Erase(&My_Flash, &PageError); //调用擦除函数擦除
}
void STMFlash_WriteWord(uint32_t Flash_Add_Start,uint32_t *buf, rt_size_t len)
{
uint32_t Flash_Add_Now,Flash_Add_End,i;
uint32_t StartSector,EndSector;
Flash_Add_End = Flash_Add_Start+len*4; //计算出 结束地址
HAL_FLASH_Unlock(); //解锁Flash
/* Get the number of the start and end sectors */
StartSector = GetSector(Flash_Add_Start); //获取FLASH的Sector编号
EndSector = GetSector(Flash_Add_End);
//擦除FLASH
for (i = StartSector; i <= EndSector; i += 8) //每次FLASH编号增加8,可参考上边FLASH Sector的定义。
{
/* Device voltage range supposed to be [2.7V to 3.6V], the operation will be done by word */
if (FLASH_EraseSector(i, FLASH_VOLTAGE_RANGE_3) != HAL_OK)
{
while (1)
{
}
}
}
/*擦除完毕 开始写入*/
Flash_Add_Now = Flash_Add_Start;
while (Flash_Add_Now < Flash_Add_End)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Flash_Add_Now,*buf)==HAL_OK) //将DATA_32写入相应的地址。
{
Flash_Add_Now+=4;
buf++;
}
}
HAL_FLASH_Lock(); //锁住Flash
}
bool WriteIAPFlag(void) //写入升级标志位值 表示 flash 数据写入成功 允许跳转
{
uint32_t buf[1] ={IAPFlag_Value};
STMFlash_WriteWord(STM32_UpdataFlag,buf,1);
if(STMFLASH_ReadWord(STM32_UpdataFlag)==IAPFlag_Value) return true;
else return false;
}
void jump_to_app(uint32_t app_address) //APP跳转程序
{
typedef void (*_func)(void);
__disable_irq();
/* MCU peripherals re-initial. */
{
/* reset systick */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
/* disable all peripherals clock. */
RCC->AHB1ENR = (1<<20); /* 20: F4 CCMDAT ARAMEN. */
RCC->AHB2ENR = 0;
RCC->AHB3ENR = 0;
RCC->APB1ENR = 0;
RCC->APB2ENR = 0;
/* Switch to default cpu clock. */
RCC->CFGR = 0;
} /* MCU peripherals re-initial. */
/* Disable MPU */
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
/* disable and clean up all interrupts. */
{
int i;
for(i = 0; i < 8; i++)
{
/* disable interrupts. */
NVIC->ICER[i] = 0xFFFFFFFF;
/* clean up interrupts flags. */
NVIC->ICPR[i] = 0xFFFFFFFF;
}
}
/* Set new vector table pointer */
SCB->VTOR = app_address;
/* reset register values */
__set_BASEPRI(0);
__set_FAULTMASK(0);
/* set up MSP and switch to it */
__set_MSP(*(uint32_t*)app_address);
__set_PSP(*(uint32_t*)app_address);
__set_CONTROL(0);
/* ensure what we have done could take effect */
__ISB();
__disable_irq();
/* never return */
((_func)(*(uint32_t*)(app_address + 4)))();
}
2.IAP.h 头文件内容
#ifndef APPLICATIONS_IAP_H_
#define APPLICATIONS_IAP_H_
#include <stdbool.h>
#include "sys.h"
#include "stdlib.h"
typedef void (*iapfun)(void); //定义一个函数类型的参数.
#define STM32_UpdataFlag (0x08100000-4) //程序 升级标志 存储地址 1 个字节
#define FLASH_APP_ADDR 0x08020000 //应用程序起始地址 (存放在FLASH)
void Start_App(void); //跳转到 下载的程序地址
bool ReadIAPFlag(void);//读取 升级标志 判断是否设置了升级
bool WriteIAPFlag(void); //写入升级标志位值 表示 flash 数据写入成功 允许跳转
void jump_to_app(uint32_t app_address);
uint32_t STMFLASH_ReadWord(uint32_t faddr);
uint32_t GetSector(uint32_t Address);
HAL_StatusTypeDef FLASH_EraseSector(uint32_t Sector,uint32_t VoltageRange);
void STMFlash_WriteWord(uint32_t Flash_Add_Start,uint32_t *buf, rt_size_t len);
bool WriteIAPFlag(void);//写入升级标志位值 表示 flash 数据写入成功 允许跳转
bool YModem_IAPFunction(); //IAP 升级功能
/* Base address of the Flash sectors Bank 1 */
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */
#endif /* APPLICATIONS_IAP_H_ */
3.main.c文件内容
#include <rtthread.h>
#include <board.h>
#include <rtdevice.h>
#include "fal.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "IAP.h"
int main(void)
{
fal_init();//fal组件初始化
if (ReadIAPFlag()) {
Start_App(); //跳转到 下载的程序地址
}
while (1)
{
YModem_IAPFunction(); //IAP 升级功能
rt_thread_mdelay(1000);
}
return RT_EOK;
}
4.sys.c文件内容
#include "sys.h"
void Reset_MCU(void)
{
__disable_fault_irq();
NVIC_SystemReset();
}
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__asm volatile("WFI");
}
//关闭所有中断(但是不包括fault和NMI中断)
void INTX_DISABLE(void)
{
__asm volatile("CPSID I");
__asm volatile("BX LR");
}
//开启所有中断
void INTX_ENABLE(void)
{
__asm volatile("CPSIE I");
__asm volatile("BX LR");
}
//设置栈顶地址
//addr:栈顶地址
void MSR_MSP(uint32_t addr)
{
__asm volatile("MSR MSP, r0"); //set Main Stack value
__asm volatile("BX r14");
}
4.sys.h文件内容
#ifndef __SYS_H
#define __SYS_H
#include "stm32f4xx.h"
void WFI_SET(void);
void INTX_DISABLE(void);
void INTX_ENABLE(void);
void MSR_MSP(uint32_t addr);
#endif
5.ymodem_ota.c文件增加的内容
介绍:ymodem_ota.c文件中,需要增加一个函数,用于进行串口升级相关的功能,其余的内容无需修改。
bool ymodem_ota_Function(void)
{
struct rym_ctx rctx;
rt_kprintf("Warning: Ymodem has started! This operator will not recovery.\n");
rt_kprintf("Please select the ota firmware file and use Ymodem to send.\n");
if (!rym_recv_on_device(&rctx, rt_YModem_get_device(), RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
ymodem_on_begin, ymodem_on_data, NULL,60))
{
rt_kprintf("Download firmware to flash success.\n");
rt_kprintf("System now will restart...\r\n");
/* wait some time for terminal response finish */
rt_thread_delay(rt_tick_from_millisecond(200));
//写入程序成功标志位
WriteIAPFlag(); //写入升级标志位值 表示 flash 数据写入成功 允许跳转
Start_App(); //跳转到 下载的程序地址
}
else
{
/* wait some time for terminal response finish */
rt_thread_delay(RT_TICK_PER_SECOND);
rt_kprintf("Update firmware fail.\n");
return false;
}
return false;
}
6.fal_cfg.h文件的内容
分析:当前的分区设置,与前面的分区设计保持一致
#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_
#include <rtconfig.h>
#include <board.h>
#define FLASH_SIZE_GRANULARITY_16K (4*16*1024)
#define FLASH_SIZE_GRANULARITY_64K (64*1024)
#define FLASH_SIZE_GRANULARITY_128K (7*128*1024)
#define STM32_FLASH_START_ADRESS_16K STM32_FLASH_START_ADRESS
#define STM32_FLASH_START_ADRESS_64K (STM32_FLASH_START_ADRESS+FLASH_SIZE_GRANULARITY_16K)
#define STM32_FLASH_START_ADRESS_128K (STM32_FLASH_START_ADRESS_64K+FLASH_SIZE_GRANULARITY_64K)
extern const struct fal_flash_dev stm32_onchip_flash_16k ;
extern const struct fal_flash_dev stm32_onchip_flash_64k ;
extern const struct fal_flash_dev stm32_onchip_flash_128k ;
#define NOR_FLASH_DEV_NAME "norflash0"
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&stm32_onchip_flash_16k, \
&stm32_onchip_flash_64k, \
&stm32_onchip_flash_128k, \
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "app", "onchip_flash_128k", 0*1024, 768*1024, 0}, \
{FAL_PART_MAGIC_WORD, "flag", "onchip_flash_128k", 768*1024, 128*1024, 0}, \
}
// {FAL_PART_MAGIC_WORD, "download", "onchip_flash_128k", 512*1024, 384*1024, 0},
#endif /* FAL_PART_HAS_TABLE_CFG */
#endif /* _FAL_CFG_H_ */
程序逻辑分析和总结
1.在程序启动后,先检查在程序标志位地址处的内容是否,如果是程序升级成功标志则自动跳转到APP地址
2.如果没有跳转,则启动串口YModem升级功能
3.升级成功后启动跳转功能,跳转到APP程序运行。
4.如果升级失败,则只能重启后进行再次升级(如果有别的需求可以进行修改,也可以改成升级失败,继续从头开始启动进行串口升级)
5.在IAP.c内的jump_to_app函数是RT-thread系统跳转函数,建议不要修改。
总结:这种在线升级的方式有优点也有缺点,希望能给大家提供一个思路,通过各种组件功能的使用,来简化大家在编写在线升级程序的工作量,程序设计不能墨守成规,要多发散思维。