相关资料:https://blog.csdn.net/elikang/article/details/86082960
IAP升级的原理这里就不介绍了。
1. 修改代码实现IAP+Ymodem
基于stm32官方提供的demo工程 STM32F10x_AN2557_FW_V3.3.0
提取出IAP和Ymodem文件,并修改Ymodem代码,从标准库到HAL库。
1.1 首先,从demo中找到执行框架,并将其封装在新建的文件IAP.c中。
#include "main.h"
#include "Define.h"
/* Flash user program offset */
uint32_t BlockNbr = 0;
uint32_t UserMemoryMask = 0; //掩码,用于检测FLASH是否有写保护
__IO uint32_t FlashProtection = 0; //读写+不被编译器优化
pFunction Jump_To_Application;
uint32_t JumpAddress;
extern uint8_t file_name[FILE_NAME_LENGTH];
extern uint32_t FlashDestination;
extern int32_t Ymodem_Receive (uint8_t *);
extern uint8_t Ymodem_Transmit (uint8_t *,const uint8_t* , uint32_t );
extern void Int2Str(uint8_t* str, int32_t intnum);
uint8_t tab_1024[1024] =
{
0
};
/**
* @brief Download a file via serial port
* @param None
* @retval None
*/
void SerialDownload(void)
{
uint8_t Number[10] = " ";
int32_t Size = 0;
printf("Waiting for the file to be sent ... (press 'a' to abort)\n\r");
Size = Ymodem_Receive(&tab_1024[0]);
if (Size > 0)
{
printf("\n\n\r Programming Completed Successfully!\n\r--------------------------------\r\n Name: %s",file_name);
Int2Str(Number, Size);
printf("\n\r Size: %s Bytes\r\n",Number);
printf("-------------------\n");
}
else if (Size == -1)
{
printf("\n\n\rThe image size is higher than the allowed space memory!\n\r");
}
else if (Size == -2)
{
printf("\n\n\rVerification failed!\n\r");
}
else if (Size == -3) //收到取消
{
printf("\r\n\nAborted by user.\n\r");
}
else
{
printf("\n\rFailed to receive the file!\n\r");
}
}
/**
* @brief Display the Main Menu on to HyperTerminal
在超级终端上显示主菜单
* @param None
* @retval None
*/
void Main_Menu(void)
{
uint8_t key = 0;
/* Get the number of block (4 or 2 pages) from where the user program will be loaded
获取块(4或2页)的数目 ,用户程序将从中加载的
(0x8003000-0x8000000)>>12 = 3
*/
BlockNbr = (FlashDestination - 0x08000000) >> 12;//8
/* Compute the mask to test if the Flash memory, where the user program will be
loaded, is write protected
计算掩码以测试加载用户程序的闪存是否有写保护
*/
#if defined (STM32F10X_MD) || defined (STM32F10X_MD_VL)
UserMemoryMask = ((uint32_t)~((1 << BlockNbr) - 1));
#else /* USE_STM3210E_EVAL */
if (BlockNbr < 62)
{
UserMemoryMask = ((uint32_t)~((1 << BlockNbr) - 1));
}
else
{
UserMemoryMask = ((uint32_t)0x80000000);
}
#endif /* (STM32F10X_MD) || (STM32F10X_MD_VL) */
/* Test if any page of Flash memory where program user will be loaded is write protected
测试将加载程序用户的闪存页是否有写保护
*/
if (((uint32_t)(READ_REG(FLASH->WRPR)) & UserMemoryMask) != UserMemoryMask)
{
FlashProtection = 1;
}
else
{
FlashProtection = 0;
}
printf("\r\n================== Main Menu ============================\r\n\n");
printf(" Download Image To the STM32F10x Internal Flash ------- 1\r\n\n");
printf(" Upload Image From the STM32F10x Internal Flash ------- 2\r\n\n");
printf(" Execute The New Program ------------------------------ 3\r\n\n");
if(FlashProtection != 0)
{
printf(" Disable the write protection ------------------------- 4\r\n\n");
}
printf("==========================================================\r\n\n");
while (1)
{
key = getchar();
//功能选项
if (key == 0x31)
{
/* Download user application in the Flash */
printf("--> Download user application in the Flash\n\r");
SerialDownload();
}
else if (key == 0x32)
{
/* Upload user application from the Flash */
//SerialUpload();
printf("上传\n\r");
}
else if (key == 0x33)
{
printf("跳转\n\r");
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
/* Jump to user application */
Jump_To_Application = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) ApplicationAddress);
Jump_To_Application();
}
else if ((key == 0x34) && (FlashProtection == 1))
{
/* Disable the write protection of desired pages */
HAL_FLASH_Unlock();
printf("解锁FLASH\n\r");
}
else
{
if (FlashProtection == 0)
{
printf("Invalid Number ! ==> The number should be either 1, 2 or 3\r");
}
else
{
printf("Invalid Number ! ==> The number should be either 1, 2, 3 or 4\r");
}
}
}
}
1.2 其次,提取出Ymodem.c文件,并对其中的一些函数进行修改。
下面是Ymodem.c文件修改前后的对比。最开始是要修改包含的头文件。main.h文件中包括HAL的Flash函数。Define.h文件中则是定义了Flash操作相关的宏定义常量。
下面一段是自己加的注释,实则并没有改变代码语句。在使用串口终端和设备进行交互时,用户键盘输入的命令字等会在这段代码处被识别。
下面是传输过程中,将数据包写入到Flash的操作。这里将标准库的函数用HAL库函数来替代。修改前最好理解一下这里的前后文。
下面是注释信息,Ymodem协议,等待过程中,一直打印‘C’。
1.3 最后,添加一个宏定义头文件,用于放置地址信息等定义。
应用程序的起始地址和boot代码的设定大小有关。这里给boot代码划分32K的代码空间。
#define ApplicationAddress 0x8008000 //boot 32K
#define FLASH_IMAGE_SIZE (uint32_t) (FLASH_SIZE - (ApplicationAddress - 0x08000000))
//这里实际上是RCT6 256KB的flash
#define PAGE_SIZE (0x800) /* 2 Kbytes */
#define FLASH_SIZE (0x40000) /* 256K Byte */
/* Exported macro ------------------------------------------------------------*/
/* Common routines */
#define IS_AF(c) ((c >= 'A') && (c <= 'F'))
#define IS_af(c) ((c >= 'a') && (c <= 'f'))
#define IS_09(c) ((c >= '0') && (c <= '9'))
#define ISVALIDHEX(c) IS_AF(c) || IS_af(c) || IS_09(c)
#define ISVALIDDEC(c) IS_09(c)
#define CONVERTDEC(c) (c - '0')
#define CONVERTHEX_alpha(c) (IS_AF(c) ? (c - 'A'+10) : (c - 'a'+10))
#define CONVERTHEX(c) (IS_09(c) ? (c - '0') : CONVERTHEX_alpha(c))
#define SerialPutString(x) Serial_PutString((uint8_t*)(x))
#define FILE_NAME_LENGTH (256)
#define FILE_SIZE_LENGTH (16)
typedef void (*pFunction)(void);
1.4 在将上述三个文件准备好之后,就是如何使用。下面是boot工程的main函数片段。
(1)初始化外设。
(2)打印boot信息
(3)判断是否需要进入boot模式,(按键被按下,则进入boot,未被按下,则直接跳转到app)
(4)如果按键被按下,则进入IAP升级框架。(Main_Menu函数)
MX_GPIO_Init();
MX_I2C1_Init();
MX_IWDG_Init();
MX_USART1_UART_Init();
printf("\n\r=== 南京某某呵呵啥啥有限公司 ===\n\r");
printf("--> BOOT\n\r");
printf("--> 编译时间:%s %s\n\r",__DATE__,__TIME__);
//解锁FLASH
HAL_FLASH_Unlock();
//检测按键(后期可以考虑检测标志位)
if(HAL_GPIO_ReadPin(USER_KEY_GPIO_Port,USER_KEY_Pin)==0)
{
printf("\r\n======================================================================");
printf("\r\n= (C) COPYRIGHT 2010 STMicroelectronics =");
printf("\r\n= =");
printf("\r\n= In-Application Programming Application (Version 3.3.0) =");
printf("\r\n= =");
printf("\r\n= By MCD Application Team =");
printf("\r\n======================================================================");
printf("\r\n\r\n");
extern void Main_Menu(void);
Main_Menu();
}
else
{
uint32_t JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
pFunction Jump_To_Application = (pFunction) JumpAddress;
printf("\n\rJump to APP!\n\r");
__set_MSP(*(__IO uint32_t*) ApplicationAddress);
Jump_To_Application();
}
while (1)
{
clearIWDG();
2. 代码框架分析
2.1 IAP框架
略(稍后补充)。
2.2 Ymodem协议介绍
略(稍后补充)。
3. 关于使用。
3.1 Bootloader
boot工程配置如下图所示。
另外,keil编译生成的是hex文件,需要添加指令才可以生成bin文件。如下图所示。编译完成后会生成Boot.bin文件。
上面用到的Hexbin.bat是一个脚本文件,内容如下。因为项目需求,有两个版本的代码同时存在。该脚本会判断两个版本代码的生成目录是否存在。如果不存在,则创建HexBinE1和HexBinE2文件夹。
通过fromelf.exe工具将hex文件转成bin文件,再移动到创建的文件夹中。
用户可根据自己的需求修改该脚本。
@echo off
if not exist ..\..\HexBinE1 (
mkdir ..\..\HexBinE1
)
set exePath=%1ARM\ARMCC\bin
set outName=%2
set binName=%3
%exePath%\fromelf.exe --bin %outName% --output %binName%
move /y .\%binName% ..\..\HexBinE1 >nul
if not exist ..\..\HexBinE2 (
mkdir ..\..\HexBinE2
)
%exePath%\fromelf.exe --bin %outName% --output %binName%
move /y .\%binName% ..\..\HexBinE2 >nul
3.2 Application
app工程配置如下图所示。重要注意点是,工程配置的地址以及中断向量表的地址。这里的配置相当于让出前面32K的空间,给boot使用。
同样关于如何生成bin文件,也需要进行如下图所示的操作。以E2版本为例。
这里的HexBin.bat脚本内容如下。判断文件夹是否存在,生成bin文件,移动到文件夹中。
@echo off
if not exist ..\..\HexBinE2 (
mkdir ..\..\HexBinE2
)
set exePath=%1ARM\ARMCC\bin
set outName=%2
set binName=%3
%exePath%\fromelf.exe --bin %outName% --output %binName%
move /y .\%binName% ..\..\HexBinE2 >nul
这里的BuildAction.exe是使用Qt编写的合成工具,用于将Boot.bin和APP_E2.bin合成为一个bin文件。
其中的配置文件ini内容如下。用户可根据自己的需求更改。
*以/或者*开头的行,认为整行都是注释,不解析
*配置文件只可以修改=之后的内容。
*boot文件的相对位置
boot_path=..\..\..\HexBinE2\Boot
*app文件的相对位置
app_path=..\..\..\HexBinE2\APP_E2
*syscfg文件相对位置
syscfg_path=..\..\Inc
*产品标识码
identification=GDCJB2020
*boot_size大小默认是以KBytes为单位,例如,实际boot大小是16K,下面只要写16就好
boot_size=32
*app区大小
app_size=96
*整个flash大小
flash_size=256
*本工具调试开关
DEBUG=0
还有一个注意点是BuildAction.exe工具需要一个头文件sys_cfg.h,用以定义版本号。内容如下。
#define DEBUG_ENABLE 0 // 调试使能(停用:0 /启用:1)
// 应用层软件版本号,4个ASCII字符,临时版本0.XX,正式版本E.XX 格式:45 2E 30 31
#define APPVIRSION_BYTE0 0x32
#define APPVIRSION_BYTE1 0x2E
#define APPVIRSION_BYTE2 0x30
#define APPVIRSION_BYTE3 0x37