STM32、GD32固件升级IAP
系列文章目录
GD32F105RBT6 keil基础工程模板
目录
系列文章目录
前言
一、BootLoader与APP
1、BootLoader
2、用户应用程序(APP)
3、启动流程
4、BootLoader和APP的关系
二、BootLoader工程
三、APP工程
四、调试
总结
前言
大家应该知道智能设备都有一个叫“OTA”的应用场景,主要是更新智能设备的固件,主要目的有两修复BUG和增加新功能。
对于嵌入式电子产品基于STM32或者GD32等Cortex®-M3、Cortex®-M4内核的单片机来说实现OTA功能就是实现IAP功能,即用户程序运行中作自身的更新操作。
下面就介绍STM32、GD32进行IAP固件升级(更新)的流程,本次介绍是基于GD32F105RBT6单片机进行的。
一、BootLoader与APP
1、BootLoader
BootLoader是MCU上电(复位)后首先运行的固件程序,在不进行应用程序升级的嵌入式单片机设备中是不需要写这个的。
要理解单片机BootLoader则要对ARM Linux的BootLoader进行区分开来。
Linux系统的BootLoader被称作“加载引导程序”,主要是:初始化硬件设备、建立内存空间映射图,将系统的软硬件环境带到一个合适的状态,然后跳转到操作系统所在的空间,启动操作系统运行。无论设备是否需要升级,都必须要BootLoader。
单片机(GD32或者STM32)的BootLoader,主要是实现在固件升级时下载和校验固件、跳转到用户应用程序(APP)的功能,所以单片机(GD32或者STM32)的BootLoader相对简单。
2、用户应用程序(APP)
APP是用户应用程序,也就是我们编写的底层驱动和上层业务逻辑的那部分程序,由BootLoader运行完成后跳转过来的。
3、启动流程
在讲BootLoader和APP的关系前我们先了解一下STM32和GD32(Cortex – M3/M4)系列单片机的启动流程:
下图为官方手册的内容:
以内部Flash启动为例,可得到STM32的启动流程如下:
1.复位(上电)后,取0x0000 0000地址的值为MSP指针的值(也就是栈内存空间栈顶的地址);取0x0000 0004地址(第二个字)的值为PC(程序计数器)的值(复位后第一条执行的指令)
2.根据选择的启动模式进行地址映射:
keil编译出来的bin文件默认地址是0x0800 0000,所以MSP指针的值是bin文件的第一个字的内容(注意M3/M4内核是小端模式);PC(程序计数器)的值为bin文件第二个字的内容。
3.执行启动文件(.s)的程序:
中断向量表:各种中断的入口地址。
4.运行main函数,运行用户程序。
4、BootLoader和APP的关系
上面讲了MCU的整个启动的流程,无论是BootLoader还是APP都必须要按照上面的流程进行启动,只是APP的运行需要在BootLoader中进行跳转,即在BootLoader对MSP和PC进行重新赋值成APP.bin文件中的参数。
故两者的关系如下图:
一般的设计流程:
BootLoader:验证下载新固件完整性,从固件备份区拷贝新固件数据到APP区,跳转到APP中;
APP:业务应用程序设计,下载新固件到备份区(APP_back)并复位。
内部flash分区如下图:
二、BootLoader工程
以GD32F105RBT6为例,BootLoader基础工程创建,起始地址为0x0800 0000,在keil中不需要改动配置参数。
keil工程参数配置如下图:
main.c代码如下:
#include <stdio.h>
#include <string.h>
#include "systick.h"
#include "sys.h"
#include "led.h"
#include "timer.h"
#include "usart.h"
#include "32flash.h"
#define FLASH_APP1_ADDR 0x08003000 // 第一个应用程序起始地址(存放在FLASH)
#define EN_FIRMWARE_CRC32
#define EN_IAP_DEBUG
/*
* 128k: 10 + 2 + 56 + 56 + 2 + 2 = 128
*/
#define BOOTLOADER_SIZE (10 * 1024) // 0x2800
#define CORE_SIZE_128K (56 * 1024) // 0xE000
#define USER_PARA_SIZE (2 * 1024) // 0x800 注意:GD32F105RBT6的FLASH页大小为2K,建议每次拷贝512个字节
typedef void (*iapfun)(void); // 定义一个函数类型的参数.
typedef struct _tag_IAP_UPDATE
{
uint16_t file_size;
uint16_t needupdate;
uint8_t crc32_a, crc32_b, crc32_c, crc32_d;
}ipa_update_t;
ipa_update_t ipa_update; // 缓存升级信息
uint8_t Receive_dat_buffer[USER_PARA_SIZE] = {0}; // 数据接收缓存数组 2K字节缓存
iapfun jump2app;
void iap_load_app(uint32_t appxaddr); // 跳转到APP程序执行
void load_update_info(void); // 加载升级信息,如有新固件在APP中会进行相关标志位的置位
uint8_t update_application(void); // 将APP备份区固件数据拷贝到APP区
int main(void)
{
uint8_t tmp = 0;
/* configure systick */
systick_config();
/* configure priority group */
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);// 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
led_init(); // configure LED
timerx_init(TIMER2, 99, 1079); // configure TIMER3 108 000 000/1080 = 100,000K Hz 计数100 定时1ms
gd32_uart_init(); // configure UART
load_update_info(); // 加载升级信息
while(1)
{
if(ipa_update.needupdate == 1)
{
tmp = update_application(); // 进行固件升级
if(tmp)
{
if(((*(vu32*)(FLASH_APP1_ADDR + 4)) & 0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
printf("bootloader main() firmware upgrade succesuful, ready jump to APP!!\r\n");
// INTX_DISABLE(); // 关闭总中断__disable_irq()
__disable_irq(); // 关闭总中断
iap_load_app(FLASH_APP1_ADDR); // 执行FLASH APP代码
}
else
{
printf("bootloader main() firmware upgrade failed!!\r\n");
__disable_irq(); // 关闭总中断
iap_load_app(FLASH_APP1_ADDR); // 执行FLASH APP代码
}
}
else
{
printf("bootloader main() firmware upgrade failed!!\r\n");
__disable_irq(); // 关闭总中断
iap_load_app(FLASH_APP1_ADDR); // 执行FLASH APP代码
}
}
else
{
printf("bootloader main() not new firmware!!\r\n");
__disable_irq(); // 关闭总中断
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}
}
}
/**
* @brief 加载更新信息
* @param[in] NULL
* @return NULL
*/
void load_update_info(void)
{
uint16_t rec_len = 0;
rec_len = STM_GDFLASH_Read(ADDR_UPDATE_INFO, (uint16_t*)&ipa_update.file_size, sizeof(ipa_update_t)/2); //从片内Flash中读取IAP参数
if(rec_len != 0)
{
}
}
/**
* @brief 跳转到应用程序段
* @param[in] appxaddr:用户代码(user app)起始地址.
* @return
*/
void iap_load_app(uint32_t appxaddr)
{
if(((*(vu32*)appxaddr) & 0x2FFE0000) == 0x20000000) // 检查栈顶地址是否合法.
{
jump2app = (iapfun)*(vu32*)(appxaddr + 4); // 用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); // 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
// __set_CONTROL(0);//特权模式
jump2app(); // 跳转到APP.
}
}
update_application()函数的功能请自行实现,改函数主要是实现固件完整性校验和固件拷贝。
工程结构如下图:
三、APP工程
以GD32F105RBT6为例,APP keil工程,因为APP的起始地址和BootLoader的不同,所以当跳转到APP后需要对中断向量表的地址重新设置 :
#define FLASH_APP1_ADDR 0x08003000 // 第一个应用程序起始地址(存放在FLASH)
SCB->VTOR = FLASH_APP1_ADDR; //设置中断向量表偏移量
keil的配置需要改一下地址,如下图:
main.c的代码如下图,下载固件的业务请自行实现,下载完成后软件复位即可:
#include <stdio.h>
#include "systick.h"
#include "rtc.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
#include "32flash.h"
#define FLASH_APP1_ADDR 0x08003000 // 第一个应用程序起始地址(存放在FLASH)
int main(void)
{
SCB->VTOR = FLASH_APP1_ADDR; //设置中断向量表偏移量
INTX_ENABLE(); //开启总中断
/* configure systick */
systick_config();
/* configure priority group */
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
rtc_configuration(); // configure RTC
led_init(); // configure LED
key_init(); // configure KEY
relay_init(); // configure RELAY
gd32_uart_init(); // configure UART
timerx_init(TIMER2, 99, 1079); // configure TIMER2 108 000 000/1080 = 100,000K Hz 计数100 定时1ms
load_devi_info(); // 加载设备信息
while(1)
{
date_deal_4g_statask();
rtc_get();
}
}
我这里是4g模块远程下载固件包,然后软件复位.。
NVIC_SystemReset(); // 复位进入bootloader
四、调试
将两个工程编译的bin文件先烧录BootLoader.bin,后烧录APP.bin,APP添加串口打印版本号,上电运行,通过串口助手查看版本号,下发新版本的固件,升级完成在通过串口助手查看版本是否是新版。
本例程经调试可以正常从BootLoader跳转到APP,也可以正常更新固件。
缺点:
BootLoader.bin、APP.bin是两个bin文件,需要烧录两次不太友好,后面会分享如何将这两个bin文件合并成一个bin文件的操作。
总结
固件更新,甚至远程OTA的实现都是离不开BootLoader与APP的设计,MCU的IAP是非常重要的,掌握这个技术对提高对底层的理解很有帮助,追本溯源是每个技术人必不可少的路
作者:带风追风