Bootstrap

STM32 + CubeMX + 串口 + IAP升级

这篇文章分享一个简单的串口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内的日志
在这里插入图片描述

;