STM32 + CubeMX + 串口 + IAP升级

这篇文章分享一个简单的串口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字节,并写入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内的日志

    作者:Lin201230

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 + CubeMX + 串口 + IAP升级

    发表回复