文章目录
- 概述
- 什么是bootloader?为什么用?
- bootloader启动流程图
- 步骤
- 下载过程
- 代码
- 获取本地配置信息
- 获取主机传过来的配置信息
- bootloader发送2给上位机,上位机发送文件给bootloader
- 根据网站复制CRC
- 烧写flash
- erase
- 启动
- 编译问题
概述
用keil编写app,来用bootloader更新app,完成灯闪烁指令,升级app的新版本就是灯闪烁的更快了;
通过usb直接传输更新的配置给开发板,然他自己启动bootloder,擦除原来的app和配置信息,烧录新的app和配置信息;
什么是bootloader?为什么用?
运行时,bootloader会先启动,如果有程序升级,bootloader从RAM里面下数据烧录到flash的一块空间上面,就是app;
其中突然断电没烧录完也没事,bootloader等下次启动又会重新烧录,如果没有bootloader,app不是完整的也不能直接启动,就不能用了;
原本没有bootloader怎么运行的:
RAM如果很小,里面不会去在复制一个app进去,问题就是此时上点时是flash的app程序在从RAM下载烧录数据到flash的app,这直接把自己覆盖了呀,那会出错的;
RAM如果很大,那RAM存一个APP,运行是就用RAM的APP,然后把升级的app数据下载烧录到flash上把他覆盖,这样就不会出现上面的问题了,但是断电就会导致flash上的APP没烧录完整,下次通电先打开flash就打不开了;
通电先执行flash;
bootloader启动流程图
STM32H563RIV
cpu是ARM公司生产的,他们设定只能从0开始读,因此要设置映射关系,让cpu一开始在flash的地址0x0800 0000;
1、cpu从0开始读4个字节存入sp寄存器;
2、cpu从4这个地址读到4个字节存入pc寄存器
发生中断时:
硬件会做的事:在异常向量表里面找到函数,执行
我们需要操作的是:告诉cpu异常向量表的基地址
下图为有无bootloader的异常向量表的首地址
要把这个//,因为这个是异常向量表,是flash开头的异常向量表
把flash开始烧录的地址移到0804000,前面的给bootloader;
步骤
传进来的参数是存在寄存器R0里面的;
把R0的数据写入寄存器VTOR
把R0的数值存入R1所指向的位置;
VTOR保存的是异常向量表的基地址;
从R0这里读取R1;
把R1的数值写给栈SP寄存器;
跳转到R0+4的位置
下载过程
1、bootloader发送“1”字符给上位机,请求固件信息;
2、上位机给bootloader发送固件信息:先发同步字符;
上位机发送的固件信息
a、长度信息
b、加载地址
c、校验码
d、文件名
e、版本号
3、bootloader决定升级
发送0x02给上位机;
4、上位机发送bin文件给bootloader
收到后bootloader会(1)计算CRC校验码,(2)与之前收到的固件信息比较;
5、bootloader开始烧写,同时可以发送烧写进度给上位机
生成设备信息
用这个create_firemware_info来生成脚本
代码
获取本地配置信息
#define CFG_OFFSET 0x081FE000//设备地址
file_len是判断配置信息有没有写
获取主机传过来的配置信息
先是usb传输发送1;
接收上机回应信号5个0x5a;
接收上机设备配置信息32个寄存器;
bootloader发送2给上位机,上位机发送文件给bootloader
上位机发送文件时就会发送一系列的数据,超时时间*10,给上位机充分时间发送
根据网站复制CRC
bootloaderTask
这里任务要等待一会
因为usb插到电脑上,要等电脑识别出来usb,才能进行传输,
获取固件失败的原因?
数据丢失,上位机发送数据给bootloader,是不是数据没有接收全,数据覆盖了,数据丢失了;
先看是不是数据接收不全,怎么看呢,我们是创建队列来接收的,队列初始化时的大小看一下,能不能装得下;
找到usb传输的队列只有200bit,但是传输的数据有5kb ,5x1024bit,把队列改成10kb=10240bit
BootTask整体流程:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os2.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "draw.h"
#include "stdio.h"
#include "draw.h"
#include "ux_api.h"
#include "modbus.h"
#include "errno.h"
#include "uart_device.h"
#include "semphr.h"
#include "bootloader.h"
#define UPDATE_TIMEOUT 1000//超时时间
#define CFG_OFFSET 0x081FE000
static struct UART_Device *g_pUpdateUART;
static uint32_t BE32toLE32(uint8_t *buf)
{
return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3] << 0);
}
static int GetLocalFirmwareInfo(PFirmwareInfo ptFirmwareInfo)
{
PFirmwareInfo ptFlashInfo = (PFirmwareInfo)CFG_OFFSET;
if (ptFlashInfo->file_len == 0xFFFFFFFF)
return -1;
*ptFirmwareInfo = *ptFlashInfo;
return 0;
}
static int GetServerFirmwareInfo(PFirmwareInfo ptFirmwareInfo)
{
uint8_t data = '1';
uint8_t buf[sizeof(PFirmwareInfo)];
/*send 0x01 cmd to pc*/
if(0 != g_pUpdateUART->Send(g_pUpdateUART, &data, 1, UPDATE_TIMEOUT))
return -1;
/*wait for response*/
while(1)
{
if(0 != g_pUpdateUART->RecvByte(g_pUpdateUART, &data, UPDATE_TIMEOUT*10))
return -1;
if(data != 0x5a)
{
buf[0] = data;
break;
}
}
for(int i = 1; i < sizeof(PFirmwareInfo); i++)
{
if(0 != g_pUpdateUART->RecvByte(g_pUpdateUART, &buf[i], UPDATE_TIMEOUT))
return -1;
}
ptFirmwareInfo->version = BE32toLE32(&buf[0]);
ptFirmwareInfo->file_len = BE32toLE32(&buf[4]);
ptFirmwareInfo->load_addr = BE32toLE32(&buf[8]);
ptFirmwareInfo->crc32 = BE32toLE32(&buf[12]);
strncpy((char *)ptFirmwareInfo->file_name, (char *)&buf[16], 16);
return 0;
}
static int GetServerFirmware(uint8_t *buf, uint32_t len)
{
uint8_t data = '2';
/*send 0x01 cmd to pc*/
if(0 != g_pUpdateUART->Send(g_pUpdateUART, &data, 1, UPDATE_TIMEOUT))
return -1;
/*上位机发送文件给bootloader*/
for(int i = 0; i < len; i++)
{
if(0 != g_pUpdateUART->RecvByte(g_pUpdateUART, &buf[i], UPDATE_TIMEOUT*10))
return -1;
}
return 0;
}
/* https://lxp32.github.io/docs/a-simple-example-crc32-calculation/ */
static int GetCRC32(const char *s,size_t n)
{
uint32_t crc=0xFFFFFFFF;
for(size_t i=0;i<n;i++) {
char ch=s[i];
for(size_t j=0;j<8;j++) {
uint32_t b=(ch^crc)&1;
crc>>=1;
if(b) crc=crc^0xEDB88320;
ch>>=1;
}
}
return ~crc;
}
void BootLoaderTask( void *pvParameters )
{
struct UART_Device *pUSBUART = GetUARTDevice("usb");
FirmwareInfo tLocalInfo;
FirmwareInfo tServerInfo;
int err;
int need_update = 0;
uint8_t *firmware_buf;
vTaskDelay(10000); /* wait for pc ready */
pUSBUART->Init(pUSBUART, 115200, 'N', 8, 1);
g_pUpdateUART = pUSBUART;
while (1)
{
/* read cfg info, to detect app's version */
err = GetLocalFirmwareInfo(&tLocalInfo);
if (err)
{
/* update */
need_update = 1;
}
else
{
pUSBUART->Send(pUSBUART, (uint8_t *)"GetLocalFirmwareInfo Failed\r\n", strlen("GetLocalFirmwareInfo Failed\r\n"), UPDATE_TIMEOUT);
}
err = GetServerFirmwareInfo(&tServerInfo);
if (!err)
{
/* compate version */
if (tServerInfo.version > tLocalInfo.version)
{
/* update */
need_update = 1;
}
}
else
{
need_update = 0;
pUSBUART->Send(pUSBUART, (uint8_t *)"GetServerFirmwareInfo Failed\r\n", strlen("GetServerFirmwareInfo Failed\r\n"), UPDATE_TIMEOUT);
}
if (need_update)
{
firmware_buf = pvPortMalloc(tServerInfo.file_len);
if (!firmware_buf)
{
/* error */
pUSBUART->Send(pUSBUART, (uint8_t *)"Malloc Failed\r\n", strlen("Malloc Failed\r\n"), UPDATE_TIMEOUT);
}
err = GetServerFirmware(firmware_buf, tServerInfo.file_len);
if (!err)
{
/* calc CRC */
uint32_t crc = GetCRC32((const char *)firmware_buf, tServerInfo.file_len);
if (crc == tServerInfo.crc32)
{
/* OK */
/* burn */
pUSBUART->Send(pUSBUART, (uint8_t *)"Download OK\r\n", 13, UPDATE_TIMEOUT);
}
else
{
pUSBUART->Send(pUSBUART, (uint8_t *)"GetCRC32 Failed\r\n", strlen("GetCRC32 Failed\r\n"), UPDATE_TIMEOUT);
}
}
else
{
pUSBUART->Send(pUSBUART, (uint8_t *)"GetServerFirmware Failed\r\n", strlen("GetServerFirmware Failed\r\n"), UPDATE_TIMEOUT);
}
}
else
{
/* start app */
}
}
}
烧写flash
对比flash上面的版本比主机发过来的版本小,或者flash上面的配置信息坏了,就启动升级:
1、erase 擦除flash
2、烧写
flash有两个栈区,bank1和bank2 128个栈区x 每个栈区8k
把中间蓝色圈APP擦除再烧录,只是更新APP,以及把配置文件的信息改成对应的最新的;
erase
‘
1、擦除app并烧录
擦除bank1和bank2的APP的,APP的长度可能延伸到了bank2有if进行判断,传入的地址是bootloader后的,也就是APP开头地址
2、擦除配置文件并烧录
3、bootloadertask
下载完成主机发送过来的配置文件就用1、2这两个函数进行擦除烧录;
启动
触发软件复位
复位最终会调用main
如果是复位的话就会得到flash上面这个应用程序的基地址,然后调用这个汇编函数start_app来启动程序
注意:
编译问题
non-ASCLL表示引入了中文字符