这篇文章分享一个简单的串口IAP Demo,实现使用串口更新我们自己的App程序。
一、IAP简介
IAP介绍可以在网上找找,相关资料很多,如:
https://blog.csdn.net/ba_wang_mao/article/details/110401656
二、Stm32CubeMx配置
1、RCC开启外部高速时钟(略)
2、配置STLink调试口(略)
3、配置串口方便调试输出(略)
4、配置工程名、生成路径,之后生成工程(略)
(1-4步的基础配置可以参考前面的文章《STM32基础工程模板创建》)
Boot和App的工程配置是一样的,配置好时钟、usart1即可
三、Boot代码及配置
1、代码
#include <stdio.h>
#include "string.h"
#include "stdio.h"
#define NVIC_VectTab_RAM_Start ((uint32_t)0x20000000) //RAM起始地址
#define NVIC_VectTab_RAM_End ((uint32_t)0x20020000) //RAM结束地址,大小为128K,根据自己的实际芯片大小修改
#define NVIC_VectTab_FLASH ((uint32_t)0x08000000) //Flash起始地址
#define BOOT_SIZE 0x3000 //Boot大小,12KB,0--12页,共128页
#define ApplicationAddress (NVIC_VectTab_FLASH + BOOT_SIZE) //App的起始地址
#define USER_FLASH_PAGES 52 //App所占大小,52KB
//#define FLASH_PAGE_SIZE 1024 //每页所占的字节大小,和系统库定义重复了,注释掉
#define UPDATE_CMD "update" //升级擦除指令
#define FLASH_USER_END_ADDR \
((FLASH_PAGE_SIZE*USER_FLASH_PAGES)+ApplicationAddress) //App结束地址
typedef void (*iapfun)(void);
void SystemClock_Config(void);
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
iapfun jump2app;
unsigned int count2 = 0;
unsigned char datatemp[256] = {0};
unsigned char boot_flag = 0;
unsigned char time_out_flag = 0;
int main(void)
{
unsigned char i;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("boot start\r\n");
printf("input \"update\" to erasure user flash, or wait 10s to start user app\r\n");
for(i = 0; i<10; i++)
{
//上电后每秒阻塞接收升级指令,连续10秒未收到则跳转App,10秒内收到则接收App数据并写入
HAL_UART_Receive(&huart1, datatemp, 256, 1000);
if(strstr((const char *)datatemp, UPDATE_CMD) != NULL)
{
// 擦除App区域
FLASH_EraseInitTypeDef EraseInitStruct;
unsigned int PageError;
HAL_FLASH_Unlock();
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = ApplicationAddress;
EraseInitStruct.NbPages = USER_FLASH_PAGES;
if(HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)
{
HAL_FLASH_Lock();
printf("Erase fail at:0x%x\n\r",PageError);
return 0;
}
HAL_FLASH_Lock();
boot_flag = 1;
printf("Erase OK\n\r");
break;
}
}
if(boot_flag == 1)
{
HAL_StatusTypeDef temp;
unsigned int Address;
unsigned int data_32;
unsigned char j = 0;
printf("ready to receive bin, please send in 30s\n\r");
Address = ApplicationAddress;
temp = HAL_UART_Receive(&huart1, datatemp, 256, 30*1000);
if(temp == HAL_TIMEOUT)
{
//阻塞30S,未收到App数据则退出
printf("time out, end wait to receive bin\n\r");
return 0;
}
else if(temp == HAL_OK)
{
//收到则循环接收,每秒阻塞接收256字节,并写入Flash
while(1)
{
unsigned char i;
HAL_FLASH_Unlock();
for(i=0; i<64; i++)
{
data_32 = *(unsigned int *)(&datatemp[i<<2]);
if(Address < FLASH_USER_END_ADDR)
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, data_32)==HAL_OK)
{
Address = Address + 4;
}
else
{
HAL_FLASH_Lock();
printf("write fail at: 0x%x\n\r", Address);
return 0;
}
}
}
HAL_FLASH_Lock();
printf("write 256 btye OK: 0x%x\t%d\n\r",Address,j++);
//最后一包数据不足256字节时会接收超时,防止app数据不完整,不足256字节数据接收完以后处理一次,下一次接收超时认为接收完成,跳转App
temp = HAL_UART_Receive(&huart1, datatemp, 256, 2*1000);
if(temp == HAL_TIMEOUT)
{
time_out_flag++;
if(time_out_flag == 2)
{
printf("End write OK\n\r");
goto START_APP;
}
}
}
}
}
else if(boot_flag == 0) //没有收到升级命令
{
START_APP:
printf("start user app\n\r");
HAL_Delay(10);
/*
判断App的栈顶指针是否合法(即是否有App)
ApplicationAddress为App在flash中的地址,APP把中断向量表ApplicationAddress开始的位置,而中断向量表前4字节存储的是栈顶地址
这里的目的是判断App的栈顶指针是否在0x20000000到0x2001FFFF之间,在的话就认为有App,不在就没有
*/
printf("ApplicationAddress:%0x\r\n", (*(unsigned int *)ApplicationAddress));
if(((*(unsigned int *)ApplicationAddress)>= NVIC_VectTab_RAM_Start) &&
((*(unsigned int *)ApplicationAddress)<= NVIC_VectTab_RAM_End))
{
// disable irq, if use this, must enable irq at app
//__disable_irq();
//(ApplicationAddress+4)放的是中断向量表的第二项“复位地址”
__set_MSP(*(unsigned int *)ApplicationAddress);
jump2app=(iapfun)*(unsigned int *)(ApplicationAddress+4);
jump2app();
}
else
{
printf("no user app\n\r");
return 0;
}
}
while (1)
{
}
}
2、配置
四、App代码及配置
1、代码
#include <stdio.h>
#define NVIC_VectTab_FLASH ((uint32_t)0x08000000) //Flash起始地址
#define BOOT_SIZE 0x3000 //Boot大小
void SystemClock_Config(void);
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int main(void)
{
uint32_t Count = 0;
/* 设置中断向量偏移地址 */
SCB->VTOR = NVIC_VectTab_FLASH | BOOT_SIZE;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("\n\rapp start\n\r");
while (1)
{
HAL_Delay(10);
if(Count%100 == 0)
{
printf("this is app!\t%d\n\r",Count/100);
}
Count++;
}
}
2、配置
//添加这条命令生成bin文件
$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L
五、效果展示
1、先使用Keil将Boot程序烧录进单片机,串口提示请在10s内输入升级命令
2、串口发送“update”命令,提示成功,在30s内发送bin文件
3、配置串口发送延时为100ms.因为stm32是接收一包写一包的,比较耗时。
4、选择并发送文件,开始打印接收升级日志
5、全部接收完成后会跳转到App,并打印App内的日志