这篇文章分享一个简单的串口IAP Demo,实现使用串口更新我们自己的App程序。
目录
- 一、IAP简介
- 二、Stm32CubeMx配置
- 三、Boot代码及配置
- 1、代码
- 2、配置
- 四、App代码及配置
- 1、代码
- 2、配置
- 五、效果展示
一、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字节,并写入Flashwhile(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字节数据接收完以后处理一次,下一次接收超时认为接收完成,跳转Apptemp = 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内的日志