STM32CUBE+IAR+IAP升级
案例应用:利用IAR+串口调试助手对STM32F411VET6进行简单的IAP实现,(代码分为两部分:BootLoader+APP翻转LED)
1.工具:
- IAR
- STM32CUBEMX
- 串口调试助手
- IAP预备知识参考:STM32CubeIDE IAP原理讲解,及UART双APP迭代升级IAP实现
- 声明下:本文章参考[sudaroot]的文章STM32CubeIDE IAP原理讲解,及UART双APP迭代升级IAP实现
2.工程配置
2.1.1简要说下,本案例IAP,使用uart进行轮询方式接收数据(bin文件),没有任何校验,因此在后续接受过程中可能会出现丢包(丢失bin文件数据)的问题,建议串口波特率设置小些(本案例为9600)
FLASH划分:STM32F411vet6片内FLASH512K,RAM128K,本案例将分配32KByte给BootLoader,480KByte给应用程序(本案例为LED_APP,功能0.5s翻转电平一次),如下图所示
2.1.2 LED_APP
——>STM32CUBEMX中 RCC,SYS,GPIO(PD12),UART,时钟树配置如下,LED应用程序挺简单的
对printf()函数的重定义具体见:【STM32】STM32CUBEMX+UART串口调试,循环接受发送数据
——>main.c
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
void SystemClock_Config(void);
#define APPLICATION_ADDRESS (uint32_t)0x08008000//LED_APP起始位置
int main(void)
{
__enable_irq();//开中断
SCB->VTOR = APPLICATION_ADDRESS;//设置程序烧写起始位置
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
printf("LED_APP TEST, This is IAP Application\r\n");
while (1)
{
printf("This is IAP Application running\r\n");
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
HAL_Delay(500);
}
}
——>IAR设置程序起始位置,(对应用程序调试时,不用修改起始位置,调试无误后再修改向量表起始地址和其ROM,RAM,并生成bin文件)
1.在第2步中.cif文件要选选stm32f411xe_flash.icf,因为我们这里是将程序烧写进flash,如果是烧写到sarm,那就选sarm.icf
2.第3步中设置向量表起始地址这里是0x08008000(可以相应修改自定义的,根据你分配区域的大小设置)
3.第4步中Memory Regions中ROM: 0x08008000~0x0807FFFF(总共480KB),RAM:栈是在RAM上分配 的,大小0x2000 0000 ~ 0x2001 FFFF(对应F411ve 128K),一般起始地址为0x2000 0000 ,这里设置为0x2000 00c0,留给参数变量c0大小的区域。
——>IAR生成bin文件设置,设置完后保存,编译之后会在你的工程exe文件中生成BIN文件,注意bin文件的大小要在设置的范围内
2.1.3 BootLoader
——>STM32CUBEMX配置和LED_APP配置一样,UART串口波特率设置成9600,
——>同样需要对printf()函数进行重定义,具体见:【STM32】STM32CUBEMX+UART串口调试,循环接受发送数据
main.c
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include <string.h>
#include "flash_if.h"
typedef void (*pFunction)(void);
#define UART_BUF_SIZE 256
uint32_t JumpAddress;
pFunction JumpToApplication;
uint8_t uart_buf[UART_BUF_SIZE] = {0};
void SystemClock_Config(void);
int main(void)
{
uint8_t key = 0;
uint32_t temp = 0;
uint32_t timeout = 0;
uint32_t userapplen = 0;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
printf("\r\n This is Bootloader\r\n");
while (1)
{
printf("\r\n=================== Main Menu ============================\r\n\n");
printf(" Download user application to the internal Flash ------ 1\r\n\n");
printf(" Execute the loaded application ----------------------- 2\r\n\n");
/* Clean the input path */
__HAL_UART_FLUSH_DRREGISTER(&huart2);
/* Receive key */
HAL_UART_Receive(&huart2, &key, 1, HAL_MAX_DELAY);
switch(key)
{
case '1':
/* Download user application in the Flash */
/* 1. erase user application area */
printf("Wait for the internal Flash erase to complete\r\n");
if(FLASH_If_Erase(APPLICATION_ADDRESS, 6) == 1)
{
printf("Erase the internal Flash is fail\r\n");
Error_Handler();
}
printf("Erase the internal Flash is complete\r\n");
/* 2. download a file via serial port */
printf("Waiting for the file to be sent ... \r\n");
userapplen = 0;
timeout = HAL_MAX_DELAY;
/* Clean the input path */
__HAL_UART_FLUSH_DRREGISTER(&huart2);
while(1)
{
if(HAL_UART_Receive(&huart2, uart_buf, UART_BUF_SIZE, timeout) != HAL_OK)
{
temp = UART_BUF_SIZE - (huart2.RxXferCount + 1);
if(FLASH_If_Write(APPLICATION_ADDRESS + userapplen, (uint32_t*)uart_buf, temp / 4) != FLASHIF_OK)
{
printf("Write the internal Flash is fail\r\n");
Error_Handler();
}
userapplen = userapplen + temp;
break;
}
timeout = 1000;//等待接收完成
if(FLASH_If_Write(APPLICATION_ADDRESS + userapplen, (uint32_t*)uart_buf, UART_BUF_SIZE / 4))//4字节对齐
{
printf("Write the internal Flash is fail\r\n");
Error_Handler();
}
userapplen = userapplen + UART_BUF_SIZE;
}
printf("Programming Completed Successfully! %ldBtye\r\n", userapplen);
break;
case '2':
printf("Start program execution......\r\n\n");
/*
* 关闭或反初始化前面用到的外设和中断
* 1、反初始化UART
* 2、关闭系统滴答定时器中断
* */
HAL_UART_DeInit(&huart2);
// HAL_SuspendTick();
// __disable_irq();
/* execute the new program */
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
/* Jump to user application */
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
JumpToApplication();
break;
default:
printf("Invalid Number ! ==> The number should be either 1 or 2\r");
break;
}
}
}
编译调试无误后就可以开始了,生成bin文件注意大小,在32K之内,不需要修改起始地址。
1、上电后打印功能菜单。
2、发送‘1’就会进入IAP模式;首先会擦除APP空间,然后等待用串口助手发送LED_APP的bin文件写入flash,直至完成。
3、发送‘2’就会直接跳转到LED_APP运行,结果如下。
——>BootLoader程序简单说下(个人看法,如有不同见解,欢迎留言讨论)
-
FLASH_If_Erase(APPLICATION_ADDRESS, 6)函数,第一个参数擦除的sector的起始地址,第二个参数根据自己的flash中sector数进行相应设置(判断下有没有擦除成功)
-
FLASH_If_Write(APPLICATION_ADDRESS + userapplen, (uint32_t * )uart_buf, temp / 4)写flash函数,第一个参数起始地址,第二个要写的数据的缓存地址,第三个要写的数据长度。在该函数之前uart_buf类型为uint8_t,(1个字节)用于接收BIN文件数据,所以轮询方式的HAL_UART_Receive()接收了几十次,FLASH_If_Write()也写了几十次,不过FLASH_If_Write()是以4字节对齐的方式写入,对应于(uint32_t * )uart_buf,
-
case ‘1’:中while()循环:当通过串口助手发送bin文件之后,执行HAL_UART_Receive()后(有可能满buffer有可能没有)—>执行1次FLASH_If_Write(),写完之后更新下次要写的flash起始位置—>HAL_UART_Receive()—>执行1次FLASH_If_Write()…,最后一次进入if(HAL_UART_Receive()!= HAL_OK),写完剩余数据,跳出循环,那么问题来了HAL_UART_Receive()接收数据和FLASH_If_Write()会存在快慢的问题,如果本次接收到的数据还没有写完flash,下一次又来数据又去写那么会存在丢包的问题,为避免该问题,这里将波特率设置的很小9600,可以尝试看看115200时的现象。
-
case ‘2’:在跳转到新的LED_APP之前关掉前面用到的外设和中断,然后在新的应用程序重新开启中断,避免干扰