目录
好记性不如烂笔头:
本文主要用来记录 bootloader+IAP+APP+生成烧写文件.hex+升级文件.bin 的流程,用于以后类似项目的参考。
由于公司产品需要量产,虑到后续程序升级的问题就添加了bootloader。本想着前几年搞过了以为很简单,没得想到,忘记了好多,搞得花了一周时间才搞定。看来以后还是需要养成记笔记的习惯。
本文主要记录bootloader的编写,APP的程序的设计,上位机升级程序的设计,如何生成hex文件和bin文件,以及编写脚本自动生成带bootloader的烧写.hex文件
设计要点:
本文采用的升级协议是Ymodem协议,上位机是使用qt来编写,界面如下,浏览选择升级文件.bin,然后发送即可升级。
bootloader和APP程序的设计如下图:
bootloader程序参考ST给的官方IAP资料en.x-cube-iap-usart。核心代码如下:
COM_StatusTypeDef Ymodem_Receive ( uint32_t *p_size )
{
uint32_t i, packet_length, session_done = 0, file_done, errors = 0, session_begin = 0;
uint32_t flashdestination, ramsource, filesize;
uint8_t *file_ptr;
uint8_t file_size[FILE_SIZE_LENGTH], tmp, packets_received;
COM_StatusTypeDef result = COM_OK;
/* Initialize flashdestination variable */
flashdestination = APPLICATION_ADDRESS;
while ((session_done == 0) && (result == COM_OK))
{
packets_received = 0;
file_done = 0;
while ((file_done == 0) && (result == COM_OK))
{
switch (ReceivePacket(aPacketData, &packet_length, DOWNLOAD_TIMEOUT))
{
case HAL_OK:
errors = 0;
switch (packet_length)
{
case 2:
/* Abort by sender */
Serial_PutByte(ACK);
result = COM_ABORT;
break;
case 0:
/* End of transmission */
Serial_PutByte(ACK);
file_done = 1;
break;
default:
/* Normal packet */
if (aPacketData[PACKET_NUMBER_INDEX] != packets_received)
{
Serial_PutByte(NAK);
}
else
{
if (packets_received == 0)
{
/* File name packet */
if (aPacketData[PACKET_DATA_INDEX] != 0)
{
/* File name extraction */
i = 0;
file_ptr = aPacketData + PACKET_DATA_INDEX;
while ( (*file_ptr != 0) && (i < FILE_NAME_LENGTH))
{
aFileName[i++] = *file_ptr++;
}
/* File size extraction */
aFileName[i++] = '\0';
i = 0;
file_ptr ++;
while ( (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH))
{
file_size[i++] = *file_ptr++;
}
file_size[i++] = '\0';
Str2Int(file_size, &filesize);
/* Test the size of the image to be sent */
/* Image size is greater than Flash size */
if (*p_size > (USER_FLASH_SIZE + 1))
{
/* End session */
tmp = CA;
HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);
HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);
result = COM_LIMIT;
}
/* erase user application area */
FLASH_If_Erase(APPLICATION_ADDRESS);
*p_size = filesize;
Serial_PutByte(ACK);
Serial_PutByte(CRC16);
}
/* File header packet is empty, end session */
else
{
Serial_PutByte(ACK);
file_done = 1;
session_done = 1;
break;
}
}
else /* Data packet */
{
ramsource = (uint32_t) & aPacketData[PACKET_DATA_INDEX];
/* Write received data in Flash */
if (FLASH_If_Write(flashdestination, (uint32_t*) ramsource, packet_length/4) == FLASHIF_OK)
{
flashdestination += packet_length;
Serial_PutByte(ACK);
}
else /* An error occurred while writing to Flash memory */
{
/* End session */
Serial_PutByte(CA);
Serial_PutByte(CA);
result = COM_DATA;
}
}
packets_received ++;
session_begin = 1;
}
break;
}
break;
case HAL_BUSY: /* Abort actually */
Serial_PutByte(CA);
Serial_PutByte(CA);
result = COM_ABORT;
break;
default:
if (session_begin > 0)
{
errors ++;
}
if (errors > MAX_ERRORS)
{
/* Abort communication */
Serial_PutByte(CA);
Serial_PutByte(CA);
}
else
{
Serial_PutByte(CRC16); /* Ask for a packet */
}
break;
}
}
}
return result;
}
本项目使用的不是串口,所以需要移植一下,适合自己的项目,主要修改了数据的获取以及数据的发送函数,以及接收处理函数,由于是项目上在使用的,不方便贴核心代码。中心思想是围绕Ymodem进行数据解析。 主函数代码如下,其中Ymodem_Receive2为核心代码,参考上述代码:
int main(void)
{
int32_t Size = 0;
// uint16_t offset = 0;
// uint16_t Value = 0;
// uint8_t sensorBuffer[20];
unsigned int Start_Mode_Flag = 0;
systick_config();
tongxin_init();
//Set_Update_Flag();
//Set_Update_Over_Flag();
Start_Mode_Flag = Read_Start_Mode();
if(Start_Mode_Flag != 0xADACABAA)
{
delay_1ms(500);
//跳转到APP程序中
APP_Start();
while (1)
{
}
}
while (1)
{
Size = Ymodem_Receive2(&otabuf[0]);
}
}
踩坑点1:
本项目使用的不是串口,是其他的通信口,需要建立缓存机制,接收到的所有数据都需要进入缓存,在解析数据时都要出缓存。导致了有的数据获取和发送会出现失败的情况,导致通信异常。需要对缓存的数据长度以及进出指针做出处理。
踩坑点2:
本项目使用的是GD32F207的芯片,在APP的中需要跳转向量表,在main函数开头已经设置了跳转向量表,没得想到调用的对外设进行初始化的库函数中还有跳转向量表的设置。这个点浪费了我一天的时间。所有以后在编写带bootloader的APP时,一定要全局搜索一下nvic_vector_table_set这个函数的调用情况。
升级文件的生成:
使用说明:DebugInFlash文件夹需要和keil的工程文件.uvprojx在同一个目录下,在此文件夹中需要包含如下文件:
bootloader.hex
merge.cmd
boot_app.sh
.bin文件的生成需要在KEIL中做对应的设置,如下图所示:
#K\ARM\ARMCC\bin\fromelf.exe --bincombined --output=.\DebugInFlash\@L.bin !L
生产烧写文件的生成:
生成烧写文件为hex格式文件,主要包含了bootloader.hex和app.hex,在上图生成.bin中的设置下面调用了一个merge.cmd文件,调用了此文件可以自动生成烧写文件bootloader_app.hex。脚本代码如下:
set "cmdDir=%~dp0"
copy %cmdDir%bootloader.hex %cmdDir%bootloader_app.hex
@echo off
set "file_name=%cmdDir%bootloader_app.hex"
set back_line=1
for /f %%a in ('type "%file_name%"^|find /c /v ""') do (echo %%a&set num_line=%%a)
set/a last_line=num_line-back_line
cd.>"%temp%\tmp.hex"
for /f "tokens=1* delims=:" %%a in ('findstr/n .* "%file_name%"') do (
if %%a leq %last_line% (echo.^:%%b) else (goto:end)
)>>"%temp%\tmp.hex"
:end ==============================================================
type "%temp%\tmp.hex">%file_name%
type %cmdDir%..\.\GD32F20X_OBJ\app.hex >> %cmdDir%bootloader_app.hex
另外一种方式是需要安装git bash,具体方式如下:
使用如下命令替换上图中的Run#2命令:
#"C:\Program Files\Git\bin\sh.exe" --login -i -c "./DebugInFlash/boot_app.sh"
具体的命令需要根据自己安装的git路径进行修改。
此方式主要参考了鱼鹰Osprey大佬的博客文章
如下为脚本的源代码:
#!/bin/bash
#"C:\Program Files\Git\bin\sh.exe" --login -i -c "./DebugInFlash/boot_app.sh" # MDK 中执行的命令
boot_name_file_hex=./DebugInFlash/bootloader.hex # BOOT 文件所在位置和名称
app_name_file_hex=./GD32F20X_OBJ/app.hex #dir_file_name_hex=`find ${app_dir_file_hex} -name "*.hex"`
boot_app_name_file_hex=./DebugInFlash/bootloader_app.hex
cp ${boot_name_file_hex} ${boot_app_name_file_hex} # 复制新文件 boot 文件
sed -i '$d' ${boot_app_name_file_hex} # 删除最后一行
cat ${app_name_file_hex} >> ${boot_app_name_file_hex} # 追加文件
设计好之后,以后在build应用程序后会直接生成烧写文件。不需要在对文件进行二次加工。
结束语:
本文参考了其他博主的文章,由于时间太久了不记得具体哪几位了,就不一一链接了