STM32 IAP升级(bootLoader)

STM32 IAP升级(bootLoader)

今天给大家做一个STM32F系列的IAP 升级, 网上有不少例子,我这里字数有点多如果看请耐心看完,如果嫌麻烦可以看其他的帖子
我这边以103为例子,用的人多,不过最近芯片涨价太狠也不知道还有人拿来玩不。当然我做这个不仅仅是可以用在F103上面 F系列/G系列/L系列/H也是可以用的

废话不多说 先来说明一下这个IAP是个什么,我就简单举个例子 ,大家请看下面:

通常单片机的程序:
在这里插入图片描述
加入IAP后单片机的程序:
在这里插入图片描述
这个IAP 呢 说白了就是在原来单片机只有一个程序的基础下增加一个程序,这样单片机里面就有两个程序。


重点来了:单片机里面为啥要加个IAP程序呢?写一个就够了还要整两个不是吃饱了没事干吗?
其实不然,如果大家有体验过连续一个星期甚至几个星期都在刷单片机代码就不会觉得了。现实中会有这种情况出现 就是咱做好的电路板写完了程序通常会被一些精美外壳封装好,有些为了防止漏水啊或者什么的 通常都封装的很严实的。要想软件出问题了去拆开得废多大劲。与其这样不如写个能通过通信对单片机进行 程序下载的,就算出现问题了也能通过什么usb口啊 网口啊 进行刷代码,甚至可以连接wifi模块在线远程刷新


以上为前言啊 ! 说这么多,IAP就是一个能通过其他通信方式对 已经封装好的或者不能通过硬件下载口下载程序的第三方烧录方式。

好接着往下面说,前面大概知道了怎么个东西了,现在我们要开始来实现功能了。怎么实现呢?(如果有什么疑问的请带着疑问往下面看! 很多问题我会写在下面!)

先给大家说一下整体的流程:

上位机发起更新请求 →下位机从用户区域跳转到IAP区域→ 中间传输通过传输协议 →发送到下位机 → 下位机接收文件通过IAP程序更新 →下位机更新完成跳转到用户区域

第一步是 要确定好我们要干啥,首先呢IAP下载到单片机,所以我们需要一个能通信并且内存稍大点的单片机,如果是简单程序呢一个STM32C8T6 就够了
在这里插入图片描述
这是我从官方扒下来的图片,这边我只找了103的,有其他型号的大家可以去官网上面看。

单片机选好好过后,IAP传输肯定需要有一个发送方,这里的话我写了C# 的Winform做为发送端,回头我在开一篇文档说一下,然后这个就放一个传送门在这里:👇👇👇

传送门:IAP上位机部分

然后下一步,前面两步有发送端了,有接收端了,剩下的就是要通过一个方式上位机把一个程序传输给单片机。这一部分就是通信协议

/********************** 以上部分就是我们需要的最基本的东西(上位机,下位机,通信协议) *************************************/

通信协议讲解:

这一部分有很多人都不愿开源出来,当然每一个人写的东西都不一样,一些就是简单的传输,一些就是加了算法校验的保证文件的 准确性和完整性 这边的话我是用的我自己的方式来实现的 ,我里面就不画的太麻烦,简单点大家都懂:
👇👇👇

在这里插入图片描述

基础描述

1.协议主要做的就是上面的功能,上位机和下位机要进行更新文件下载,首先呢需要双方都能正常确认通信,一般就是上位机发起请求(请求里面包含下载的信息数据,比如更新文件的版本号,对应的下载包数量之类的)然后让下位机接收并且反馈(下位机初始化),完成一次循环

2.文件传输,握手协议达成之后开始进行数据传输,考虑到一些文件比较大所以这里我们是吧一个bin文件进行拆分来通过多次收发数据来进行烧录文件的发送的。防止一些文件过大导致单片机接收不过来

3.总校验是文件在传输完成后在进行校验一次 防止文件被拆分后拼接出错。


具体数据

上面大概介绍了下,请看下面👇👇👇

1.一条一条分析 首先是握手:
上位机 单片机 {"type":"commond","value":"EFXX10AF"} {"response":"0xEF,0x10,0x00"} 上位机 单片机

说明:
1.type:“commond” (指令类型 为 命令 固定格式)
2.value:“EFXX10AF” (值 EF数据头 XX 包数量 10AF 数据尾)
3.“response”:“0xEF,0x10,0x00” (反馈为固定校验信息)

上位机发送请求命令:等待下位机进行握手反馈,下位机反馈对应格式反馈上位机,如果上位机多次发送握手申请仍然没有下位机反馈就判定为握手不成功


2.握手完成后:开始数据传输 下发文件请求
上位机 单片机 {"type":"commond","value":"EFDAEEEE"} {"response":"0xEF,0xFA,0xFA"} 上位机 单片机

说明:
1.type:“commond”,“value”:“EFDAEEEE” (固定格式)
2.response:“0xEF,0xFA,0xFA” (反馈为固定格式)

上位机发送文件下发请求,下位机接收到后开始进行数据下发等待,如果上位机多次发送握手申请仍然没有下位机反馈就判定为请求数据下发失败


3.数据发送:一个大数据包
上位机 单片机 {"type":"data","value":"xxxxxxxxxxxxxxxxxxxxxxxxx"} {"response":"0xCC,0xCC,1"} {"type":"data","value":"xxxxxxxxxxxxxxxxxxxxxxxxx"} {"response":"0xCC,0xCC,2"} 。。。 {"response":"0xCC,0xCC,7"} {"type":"data","value":"xxxxxxxxxxxxxxxxxxxxxxxxx"} {"response":"0xEF,XX,XX"} 上位机 单片机

说明:
1.“type”:“data” (发送类型为数据)
2.“value”:“xxxxxxxxxxxxxxxxxxxxxxxxx” (数据)
3.“response”:“0xCC,0xCC,1” (反馈数据 第三个值为反馈接收的小包数量 其余的为固定值)
4.{“response”:“0xEF,XX,XX”} (反馈数据 0xEF为请求头,第二第三个值为 八个包的CRC校验值)

上位机 发送一大包数据,数据再次分为8次发送,每发送一次数据包,下位机记录反馈一次,当8个数据包发送完成单片机开始进行CRC运算,将校验值反馈给上位机


4.数据校验:上位机验证当前大数据包是否有问题
上位机 单片机 {"type":"commond","value":"EFDA3FXX"} {"response":"0xEF,1,0xFA"} 上位机 单片机

说明:
1.“type”:“commond” (指令类型 为 命令)
2.“value”:“EFDA3FXX” (EFDA3F为固定格式, XX为发送的大分包数量)
3.“response”:“0xEF,1,0xFA” (0XEF 为数据头 0xFA为数据尾 第二个数据为 大包数)

上位机在上一步接收到下位机传输上来的CRC 校验值之后,开始进行数据分析和对比数据是否相等,如果相等就输出当前步骤语句,发送给单片机,单片机收到数据,反馈当前包接收的包数量


5.数据发送:循环发送所有大数据包
上位机 单片机 循环 执行上面步骤3 和步骤4 反馈接收包数量 。。。 。。。 发送校验 最后一个包的值 反馈最后一个包的数量 上位机 单片机

说明:
依次循环执行步骤3 步骤4 每执行一次 发送一个包,直到发送完成


6.总数据校验:所有数据接收完成后进行整个Bin文件校验
上位机 单片机 {"type":"commond","value":"EFDADACC"} {"response":"0xEF,XX,XX"} 上位机 单片机

说明:
1.type:“commond” (指令类型 为 命令)
2.“value”:“EFDADACC” (固定格式为总CRC校验)
3.“response”:“0xEF,XX,XX” 上传总CRC校验值 第二第三个数据为CRC参数

等待完下位机反馈最后一个数据包,上位机发起所有文件CRC 请求,下位机反馈接收到所有包的CRC 校验总值 然后反馈上来


7.验证单片机:验证单片机 做唯一识别查询时使用
上位机 单片机 {"type":"commond","value":"EF0B0BEC"} {"cid":"1C003D3439470C39303131"} 上位机 单片机

说明:
1.“value”:“EF0B0BEC” (固定格式为 请求CID)
2.cid:“1C003D3439470C39303131” (设备唯一表示符)

此步骤为区分单片机号


8.发送完毕:上位机发送完毕单片机开始执行文件更新
上位机 单片机 {"type":"commond","value":"EFE0E0EC"} {"response":"0xEF,0x0A,0xFA"} 上位机 单片机

说明:
1.“value”:“EFE0E0EC” (固定格式为 IAP更新完成)
2.response:“0xEF,0x0A,0xFA” (反馈固定格式)

上位机 发送 最终json语句,下位机 执行反馈


以上就是整个文件传输通信协议,主要做的工作就是吧要烧录的文件通过上位机发送到单片机里面。

下面是单片机软件上面的说明:

先看stm32Cubexm

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面就是单片机基础配置, 就用一个串口和TIMER1 ,串口波特率根据自己需求调整,time1 分频72-1 周期100。设置ok
下面是时钟配置:

在这里插入图片描述
我这里接的是外部晶振 主频72M,大家可以用HSI 处理

以下是mian.c 函数,主要功能就是在while循环里面进行标志位判定,然后跳转到对应函数, 详情请看下面注释:
int main(void)
{
  /* USER CODE BEGIN 1 */
//UseFlag.bits.COMState =0;
//UseFlag.bits.DataState =0;
//UseFlag.bits.WillbeSuccessfull =0;
//UseFlag.bits.TOSuccessfull= 0;
  /* USER CODE END 1 */
uint8_t count =0;
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();                  	//系统生成不管
  SystemClock_Config();			//系统生成不管
  MX_GPIO_Init();				//系统生成不管
  MX_TIM1_Init();				//系统生成不管
  MX_USART1_UART_Init();		//系统生成不管
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);    //接收中断使能,自己可以判断接收标志,第一种中断方式
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);		//定义接收空闲使能																						
	HAL_TIM_Base_Start_IT(&htim1);			//开始定时
	//HAL_UART_Receive_IT(&huart1,&Rxdata,1);
	
  while (1)
  {
    /* USER CODE END WHILE */
	if(system_flag.bits.system_1ms == ON)  //system loop 1ms
  {
		system_flag.bits.system_1ms = OFF;
  }

  if(system_flag.bits.system_10ms == ON)  //system loop 10ms
  {
		system_flag.bits.system_10ms = OFF;
		System_Json_Main();				//处理串口接收到的数据
    
		if(UseFlag.bits.TOSuccessfull==1)   //接收完成标志位置位
		{
		/*以下段都说释放定义了的外设 使能*/
		  UseFlag.bits.TOSuccessfull =0;
			HAL_GPIO_DeInit(GPIOA, GPIO_PIN_15);
			HAL_UART_DeInit(&huart1);
			HAL_TIM_Base_DeInit(&htim1);  
			__HAL_TIM_DISABLE_IT(&htim1, TIM_IT_TRIGGER);		
			HAL_TIM_Base_Stop_IT(&htim1);
			system_flag.interface =0;	//标志位清空
			UseFlag.IAPinterface =0;	//标志位清空
			IAP_ExecuteApp(APP_START_ADDR);   //这句话是区域跳转
			
		}
  }
  
  if(system_flag.bits.system_100ms == ON)  //system loop 10ms
  {
		system_flag.bits.system_100ms = OFF;
  
  }  

  if(system_flag.bits.system_500ms == ON)  //system loop 1s
  {
   system_flag.bits.system_500ms = OFF;
   HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15);  //这个脚接的LED 灯翻转高低电平 闪烁灯
   if((++count > 100)&&(UseFlag.bits.UpdateState==0))  //计数达到并且没有更新标志过来
	 {
	 /*以下段都说释放定义了的外设 使能*/
		 count=0;
		 HAL_GPIO_DeInit(GPIOA, GPIO_PIN_15);
		 HAL_UART_DeInit(&huart1);
		 HAL_TIM_Base_Stop_IT(&htim1);
		 HAL_TIM_Base_DeInit(&htim1);
		 HAL_NVIC_DisableIRQ(TIM1_UP_IRQn);
		 __HAL_RCC_TIM1_CLK_DISABLE();
		 IAP_ExecuteApp(APP_START_ADDR) ;		//这句话是区域跳转		 		
	 }
  }
 }
}

然后是串口处理接收函数和定时器处理函数 👇👇,主要就是一个计时器供while 里面使用 ,另外一个就是串口接收函数 处理上位机下发得数据

/*******************************************************************************
Name            :HAL_TIM_PeriodElapsedCallback
Syntax          :void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/	
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	static uint8_t system10ms_cnt =0;
	static uint8_t system100ms_cnt =0;
	static uint16_t system500ms_cnt =0;
	if(htim->Instance == TIM1)		//系统调度周期定时器		1ms		10ms		1s
	{
	   /*对应定时器到时间 计数对应的值*/
		system_flag.bits.system_1ms = ON;      
		
		if((++system10ms_cnt) >= 10)
		{
			system10ms_cnt =0;
			system_flag.bits.system_10ms = ON;
		}
		
		if((++system100ms_cnt) >= 100)
		{
			system100ms_cnt =0;
			system_flag.bits.system_100ms = ON;
		}		
		
		if((++system500ms_cnt) >= 500)
		{
			system500ms_cnt =0;
			system_flag.bits.system_500ms = ON;
		}
	}
}


/*******************************************************************************
Name            :HAL_UART_RxCpltCallback
Syntax          :void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|********************************************************************************/
uint8_t CMD_Data_Count =0;     
uint8_t receive_dat;
void user_uart1IT_ReceiveCallback(void)
{
	static uint8_t size_cnt=0;	
		if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)		 //uart rx flag
		{
		
			receive_dat = (uint16_t) READ_REG(huart1.Instance->DR); //从接收buff里面获取数据
			USART_RX_BUF[size_cnt] = receive_dat;					//数据放到 USART_RX_BUF  数组里面 解析JSON时使用
			size_cnt ++;											//计数+1
			__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);			//清除标志位
		}
		//串口空闲中断
		if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)		 //uart idle flag
		{
			USART1_RX_STA = size_cnt;								  	//获取接收数据
			UseFlag.bits.DataACCState = ON;                            	//数据接收完毕
			receive_dat = (uint16_t)READ_REG(huart1.Instance->DR);		//一定要在接收一次 不然会出问题
			size_cnt=0;													//清除计数											
			__HAL_UART_CLEAR_IDLEFLAG(&huart1);							//清除标志位
		}		
}

然后下面是一些定义,补充上面的标志位说明,需要做一下串口打印重定向,不知道怎么做的可以去看下我之前发的帖子:

传送门:STM32HAL库学习技巧1:基于STM32CubeMX printf重定向

//printf 重定向
#include "stdio.h"
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (unsigned char *)&ch, 1, 0xFFFF); 
	while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){};		
  return ch;
}

char USART_RX_BUF[2048]={0};

/************************ SYSTEM TIME TYPE STRUCT *****************************/
typedef union
{
    unsigned int interface;
  struct
   {
   unsigned char system_1ms     :1;
   unsigned char system_10ms     :1;
   unsigned char system_100ms    :1;
   unsigned char system_500ms      :1;
   
  }bits;
}syst;
/********************** SYSTEM TIME TYPE STRUCT END ***************************/

typedef union{
	unsigned int IAPinterface;
	struct{
		unsigned char COMState      :1;
		unsigned char DealData      :1;
		unsigned char DataState     :1;
		unsigned char DataCRCState  :1;
		unsigned char DataACCState  :1;
		unsigned char DataAllowWrite :1;
		unsigned char TOSuccessfull  :1;
		unsigned char UpdateState    :3;
		unsigned char WillbeSuccessfull  :1;
	}bits;
}userFlag;
/********************** userFlag TYPE STRUCT END ***************************/
userFlag UseFlag;
syst system_flag;   //system flag

上面是一些基础工作:搭建完成就可以开始整个逻辑代码梳理了

首先还是来看文件传输通信部分:就是当我设备已经在IAP 区域了等待和上位机进行通信获取烧录文件的部分 如下图:
在这里插入图片描述

在这里插入图片描述

下面是JSON 处理主函数,单片机处理JSON 需要外部导入两个库文件
没有的朋友 可以到GIthub上面看一下:CJSON 说明
只用CJSON.C 和CJSON.h 即可。

/*******************************************************************************
Name            :System_Json_Main
Syntax          :void System_Json_Main(void)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
void System_Json_Main(void)       
{
		System_Json_deal(USART_RX_BUF);		//单纯来解析JSON 数据  传入串口接收到的数据
		JSON_DATA_TODEAL();					//解析完数据处理
		Communication_Deal();				//处理完成与上位机交互
}

以下是对System_Json_Main 函数调用的 函数做一个解剖。



/*******************************************************************************
Name            :System_Json_deal
Syntax          :uint8_t System_Json_deal(char *getData)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
cJSON* cjson_Acc = NULL;
cJSON* cjson_Type = NULL;
cJSON* cjson_Value = NULL;
uint8_t System_Json_deal(char *getData)    
{	
		if(UseFlag.bits.DataACCState == ON)    //串口接收完毕状态
		{			
			 /* 解析整段数据 */
			cjson_Acc = cJSON_Parse(getData);
			if(cjson_Acc == 0)					//如果不符合json 语法规则 返回0
			{
				return 0;
			}
			cjson_Type =cJSON_GetObjectItem(cjson_Acc,"type");    //判断命令还是数据
			cjson_Value =cJSON_GetObjectItem(cjson_Acc,"value");  //获取值 									
			
			if(strcmp1(cjson_Type->valuestring,"commond"))  //通过比较函数对比 如果是命令
			{
				UseFlag.bits.COMState = ON;					//命令标志位置位
				UseFlag.bits.DataState =OFF;				//数据标志位清除
				UseFlag.bits.DealData =ON;					//数据处理标志位置位
			}
		 if(strcmp1(cjson_Type->valuestring,"data")) //如果通过比较函数对比 是数据
			{
				UseFlag.bits.COMState = OFF; 	//命令标志位清除
				UseFlag.bits.DataState =ON;		//数据标志位置位
				UseFlag.bits.DealData =ON;		//数据处理标志位置位
			}
		}
		else		
		{
				UseFlag.bits.COMState = OFF;	
				UseFlag.bits.DataState =OFF;
		}	
   return 1;		
}



/*******************************************************************************
Name            :JSON_DATA_TODEAL
Syntax          :void JSON_DATA_TODEAL(void)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
void JSON_DATA_TODEAL(void)
{	
	 if((UseFlag.bits.COMState == ON)&&(UseFlag.bits.DealData == ON))	 	//接受指令数组
	 {
		// strcpy(getCmdValue,cjson_Value->valuestring);   					//赋值
			Get_add_Array_Value(cjson_Value->valuestring,8,COMMOND_TYPE);	//串口获取的为ascii 需要做一个转换 变成hex形式的字符
			memset(USART_RX_BUF, 0, sizeof(USART_RX_BUF));   				//清除数据
			UseFlag.bits.DealData = OFF;		                 			//数据处理完成清除标志位
		 	cJSON_Delete(cjson_Acc);										//删除jsoN 释放空间
	 }
	 else if((UseFlag.bits.DataState == ON)&&(UseFlag.bits.DealData == ON))
	 {
		 Get_add_Array_Value(cjson_Value->valuestring,128,DATA_TYPE);		//串口获取的为ascii 需要做一个转换 变成hex形式的字符
		 memset(USART_RX_BUF, 0, sizeof(USART_RX_BUF));   					//清除数据
		 UseFlag.bits.DealData = OFF;		                 				//数据处理完成清除标志位
		 cJSON_Delete(cjson_Acc);											//删除jsoN 释放空间					
	 }
	 else
	 {		 
	 }

}


下面是具体通信协议处理函数,内容可以参照上面通信协议对比查看!


/*******************************************************************************
Name            :Communication_Deal
Syntax          :void Communication_Deal(void)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
uint8_t RealPackSum=1u;															//真实计数包
void Communication_Deal(void)
{
	static uint16_t CRCCount=0;
	if(UseFlag.bits.COMState==ON)												//如果是接收到命令
	{
		UseFlag.bits.COMState = OFF;											//处理命令清空标志位				
		//判断握手 
		if((getCmdValue[0]==0xEF)&&(getCmdValue[1]!=0x00)&&(getCmdValue[3]==0XAF)&&(UseFlag.bits.UpdateState==0))
		{
			GetPackSum = getCmdValue[1];       									//获取包数量
			GetVison = getCmdValue[2];      									//获取版本值
			memset(getCmdValue, 0, sizeof(getCmdValue));   						//清除数据
			Usart_respond(0xEF,0x10,0x00);  									//反馈上位机数据
			UseFlag.bits.UpdateState = 1 ;                                		//完成第一步 
		}
		//请求数据传输
		else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0xEE)&&(getCmdValue[3]==0xEE)&&(UseFlag.bits.UpdateState == 1))
		{
			Usart_respond(0xEF,0xFA,0xFA);										//反馈上位机数据			
			memset(getCmdValue, 0, sizeof(getCmdValue));   						//清除数据			
			memset(getAccDatavalue, 0, sizeof(getAccDatavalue)); 
			UseFlag.bits.UpdateState = 2 ;                                		//完成第二步 
		}
		//CRC 转存校验
		else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0x3F)&&(getCmdValue[3]!=0x00))      
		{						
			if(ON==Transfer_Data())                                       		//转存数据
			{
							
				if((RealPackSum%20==0)&&(RealPackSum!=0))  						//10k 数据写入flash  一个包512个数据  20*512 = 10240 10k
				{
					IAP_Write_App_Bin(OldUsLength,ucDataBuf,usLength); 			//APP_START_ADDR  存储数据 一次性存10k
					OldUsLength += usLength;									//地址偏移累加
					memset(ucDataBuf,0,sizeof(ucDataBuf));						//清除数据
					usLength =0;
				}					
				Usart_respond(0xEF,RealPackSum,0xFA); 							//反馈上位机数据
				RealPackSum++;	
			}
			memset(getCmdValue, 0, sizeof(getCmdValue));   						//清除数据	
		}
		//传送完成询问  如果完成就 返回校验值
		else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0xDA)&&(getCmdValue[3]==0xCC))
		{
			if(GetPackSum < RealPackSum)
			 {
				 CRC16(GetCrcdata,CRCCount);                                    //进行CRC校验
				 Usart_respond(0xEF,returnCRCValue[0],returnCRCValue[1]);   	//ALLCRC校验数据
				 UseFlag.bits.UpdateState = 3 ;                                 //完成第三步 
				 CRCCount=0;
			 }
			 else
			 {
				 //错误 数据没传输完成 
				 //Usart_respond(0xEF,0xE0,0xE0); 
			 }
			 memset(getCmdValue, 0, sizeof(getCmdValue));   					//清除数据	
		}
		else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0x0A)&&(getCmdValue[2]==0xEF)&&(getCmdValue[3]==0xEC))
		{
			if(GetPackSum%20!=0)
			{
				IAP_Write_App_Bin(OldUsLength,ucDataBuf,(GetPackSum%20)*512); //APP_START_ADDR  如果最后文件传输没有20个包将剩余的包写入flash
				OldUsLength =0;	   					   					   		//清空累计长度													
				memset(ucDataBuf,0,sizeof(ucDataBuf));//清除数据
				UseFlag.bits.TOSuccessfull = 1;									//数据传输完成
				 UseFlag.bits.UpdateState = 0 ;                               	//完成第三步 
				Usart_respond(0xEF,0xEF,0xFA); 									//向上位机反馈数据
				//printf("successfull");                                      	//成功
			}
			memset(getCmdValue, 0, sizeof(getCmdValue));   						//清除数据				
		}
		
	}
	else if(UseFlag.bits.DataState == ON)										//如果接收到的是数据
	{
		UseFlag.bits.DataState = OFF;											//清空标志										
		Data_synthesis(getAccDatavalue,64);                             		//保存512个数据 判定CRC 状态
		memset(getAccDatavalue, 0, sizeof(getAccDatavalue));        			//清除数据
		if(UseFlag.bits.DataCRCState == ON)										//有CRC 标志位置位									
		{
			CRC16(getSaveData,512);												//计算一个大包的CRC值
			GetCrcdata[CRCCount]=returnCRCValue[0];								//得到CRC【0】值													
			GetCrcdata[CRCCount+1]=returnCRCValue[1];							//得到CRC【1】值													
			CRCCount += 2;
			Usart_respond(0xEF,returnCRCValue[0],returnCRCValue[1]);      		//发聩CRC校验值
			UseFlag.bits.DataCRCState = OFF;									//关闭状态标志
		}
	}
}

对上面的一些没有 的函数做一下补充

/*******************************************************************************
Name            :strcmp1
Syntax          :int strcmp1(char *source, char *dest)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
int strcmp1(char *source, char *dest)
{   
  while ( (*source != '\0') && (*source == *dest))   
   {
			 source++;
			 dest++;   
  }   
  return ((*source) - (*dest) ) ? 0 : 1;
} 

/*******************************************************************************
Name            :Get_add_Array_Value
Syntax          :char Get_add_Array_Value(char array[],uint8_t length)
Sync/Async      :Synchronous
Reentrancy      :Non Reentrant
Parameters(in)  :None              :

Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :-                 :-
|******************************************************************************/
void Get_add_Array_Value(char array[],uint16_t length,uint8_t type){
	//char result;
	uint16_t count =0;
	uint16_t num =0;
  uint16_t cnt_num =0;
	for(count =0 ;count<length;count++){ 
		switch(array[count]){
		 case '0':array[count] = 0;break;
		 case '1':array[count] = 1;break;
		 case '2':array[count] = 2;break;
		 case '3':array[count] = 3;break;
		 case '4':array[count] = 4;break;
		 case '5':array[count] = 5;break;
		 case '6':array[count] = 6;break;
		 case '7':array[count] = 7;break;
		 case '8':array[count] = 8;break;
		 case '9':array[count] = 9;break;
		 case 'a':array[count] = 10;break;
		 case 'A':array[count] = 10;break;
		 case 'b':array[count] = 11;break;
		 case 'B':array[count] = 11;break;
		 case 'c':array[count] = 12;break;
		 case 'C':array[count] = 12;break;
		 case 'd':array[count] = 13;break;
		 case 'D':array[count] = 13;break;
		 case 'e':array[count] = 14;break;
		 case 'E':array[count] = 14;break;
		 case 'f':array[count] = 15;break;
		 case 'F':array[count] = 15;break;
		 }
		}
		if(type == COMMOND_TYPE)
		{
			cnt_num =0;
			for(num = 0; num < length; num++)
			{
				if((num+1)%2 ==0)
				{
					getCmdValue[cnt_num] = array[num-1]*16 + array[num];
					cnt_num++;
				}					
			}			
		//if()
		//result = array[0]*16 +array[1];
	  }
		else if(type == DATA_TYPE)
		{
			cnt_num =0;
			for(num = 0; num < length; num++)
			{
				if((num+1)%2 ==0)
				{
				  getAccDatavalue[cnt_num] = array[num-1]*16 + array[num];
				  cnt_num++;
				}
			}
		}
		
}

/*******************************************************************************
Name            :CRC16
Syntax          :void CRC16(uint8_t p[],uint16_t count)
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
//CRC校验函数 ---------------------------------------------------------
uint8_t returnCRCValue[2];
void CRC16(uint8_t p[],uint16_t count)
{
	 uint8_t CRC16Lo, CRC16Hi, CL, CH, SaveHi, SaveLo;
	 uint16_t i, Flag;
	 CRC16Lo = 0xFF;
   CRC16Hi = 0xFF;
	 CL = 0x86;
   CH = 0x68;
	//Allsum = sizeof(*p) / sizeof(int);
	 for(i = 0; i < count; i++)
	 {
			CRC16Lo = CRC16Lo ^ p[i];//每一个数据与CRC寄存器进行异或  167
			for (Flag = 0; Flag <= 7; Flag++)
			{
					SaveHi = CRC16Hi;
					SaveLo = CRC16Lo;
					CRC16Hi = CRC16Hi >> 1;//高位右移一位 127
					CRC16Lo = CRC16Lo >> 1;//低位右移一位  83
					if ((SaveHi & 0x01) == 0x01)//如果高位字节最后一位为
					{
							CRC16Lo =CRC16Lo | 0x80;//则低位字节右移后前面补 否则自动补0
					}
					if ((SaveLo & 0x01) == 0x01)//如果LSB为1,则与多项式码进行异或
					{
							CRC16Hi = CRC16Hi ^ CH;
							CRC16Lo = CRC16Lo ^ CL;
					}
			}
	 }
	   returnCRCValue[0] = CRC16Hi;//CRC高位 19
     returnCRCValue[1] = CRC16Lo;//CRC低位 46
}


/*******************************************************************************
Name            :Usart_respond
Syntax          :void Usart_respond(uint8_t A,uint8_t B,uint8_t C)   
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
//单片机应答函数 ---------------------------------------------------------
void Usart_respond(uint8_t A,uint8_t B,uint8_t C)   //JSON 返回 
{
	printf("{\"response\":\"%d,%d,%d\"}",A,B,C);   
}



/*******************************************************************************
Name            :Data_synthesis
Syntax          :void Data_synthesis(void)
Sync/Async      :-
Reentrancy      :Non Reentrant
Func						:SIP
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/07/17
|******************************************************************************/
uint16_t DATA_SAVE_COUNT =0;
void Data_synthesis(uint8_t data[],uint8_t num)
{
	static uint8_t Acc_fragment =0;
	uint8_t n =0;
	for(n=0;n<num;n++)
	{
		getSaveData[DATA_SAVE_COUNT] =  data[n];
		DATA_SAVE_COUNT++;
	}
	if(Acc_fragment<7)    //8个碎片  一个碎片64个字节
	{
		Usart_respond(0xCC,0xCC,(++Acc_fragment));
	}
	if(DATA_SAVE_COUNT>=512)
	{
		UseFlag.bits.DataCRCState = ON;       //CRC 状态打开
		Acc_fragment = 0;
		DATA_SAVE_COUNT =0;		
	}
}

下面是 FLASH 存储函数,上面有调用我这里把他们集中处理下

/************************** IAP 外部变量********************************/
#define APP_FLASH_LEN  			10240u      // 定义 APP  固件最大容量55kB=55*1024=56320
#define APP_START_ADDR       	0x8005000  	  //应用程序起始位置

#define STM32_FLASH_SIZE       	512  // 所选择的STM32的Flash容量
#define STM32_FLASH_WREN       	1    // stm32芯片内容FLASH 写入使能
#define STM_SECTOR_SIZE	       	2048 // 扇区大小


#define COMMOND_TYPE      1
#define DATA_TYPE         2
// * union --------------------------------------------------------------/																							

static uint16_t STMFLASH_BUF [ STM_SECTOR_SIZE / 2 ];//最多是2k字节
static FLASH_EraseInitTypeDef EraseInitStruct;


uint8_t ucDataBuf[APP_FLASH_LEN];
//Data 数组长度
uint16_t usLength =0u;
uint32_t OldUsLength = APP_START_ADDR;  //默认地址开始写

uint8_t getCmdValue[10];   		//接受指令数组
uint8_t GetPackSum=0u;     //包数量
uint8_t GetVison =0u;      //获取版本值
uint8_t getAccDatavalue[128];
uint8_t getSaveData[512];
uint8_t GetCrcdata[400];

static uint16_t ulBuf_Flash_App[1024];

int strcmp1(char *source, char *dest);
/*******************************************************************************
Name            :IAP_Write_App_Bin
Syntax          :void IAP_Write_App_Bin(uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
//向地址写入APP文件 ---------------------------------------------------------
void IAP_Write_App_Bin(uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
{
	uint16_t us;
	uint16_t usCtr =0;
	uint16_t usTemp ;
	//-----------------------------------------
	uint32_t ulAdd_Write = ulStartAddr;       //写入当前的地址
	uint8_t * pData = pBin_DataBuf;           //写入的数据
	
	for ( us = 0; us < ulBufLength; us += 2 ) //循环长度
	{
		usTemp =  ( uint16_t ) pData[1]<<8;     //高八位
		usTemp += ( uint16_t ) pData[0];				//加上低八位
		pData+=2; 															//偏移两个字节
		ulBuf_Flash_App [ usCtr ++ ] = usTemp;	//数组保存
		if(usCtr==1024)													//存满了
    {
			usCtr = 0;                            //复位usCtr
			STMFLASH_Write( ulAdd_Write, ulBuf_Flash_App, 1024 );  //写入flash 从ulAdd_Write 开始写	
			ulAdd_Write += 2048; 
		}
	}
	if ( usCtr ) 
     STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, usCtr );//将最后的一些剩下的写入	
}

/*******************************************************************************
Name            :STMFLASH_Write
Syntax          :void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
//向地址写入FLASH ---------------------------------------------------------
void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )	
{
	uint32_t SECTORError = 0;      //扇区错误
	uint16_t secoff;               //扇区内偏移地址(16位计算)
	uint16_t secremain; 					 //扇区剩余地址(16位计算)	   
 	uint16_t i;    
	uint32_t secpos;	   					 //扇区地址
	uint32_t offaddr;							 //去掉0X08000000后地址
	//小于0X08000000 或者大于本来的内存大小
	if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
	HAL_FLASH_Unlock();            					//解锁
	offaddr=WriteAddr-FLASH_BASE;    				//实际的偏移地址  WriteAddr - 0X08000000
	secpos=offaddr/STM_SECTOR_SIZE;  				//扇区地址 0~127 for STM32F103RBT6 
	secoff=(offaddr%STM_SECTOR_SIZE)/2;     //扇区内偏移(两个字节为单位)
	secremain=STM_SECTOR_SIZE/2-secoff;		  //扇区剩余空间大小
	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于剩余空间 
	while(1) 
	{	
		STMFLASH_Read(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//¶Á³öÕû¸öÉÈÇøµÄÄÚÈÝ
		for(i=0;i<secremain;i++)//УÑéÊý¾Ý
		{
			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//ÐèÒª²Á³ý  	  
		}
		if(i<secremain)//ÐèÒª²Á³ý
		{
			//²Á³ýÕâ¸öÉÈÇø
      /* Fill EraseInit structure*/
      EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
      EraseInitStruct.PageAddress   = secpos*STM_SECTOR_SIZE+FLASH_BASE;
      EraseInitStruct.NbPages       = 1;
      HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
			for(i=0;i<secremain;i++)//¸´ÖÆ
			{
				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
			}
			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//дÈëÕû¸öÉÈÇø  
		}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//дÒѾ­²Á³ýÁ˵Ä,Ö±½ÓдÈëÉÈÇøÊ£ÓàÇø¼ä. 				   
		if(NumToWrite==secremain)break;//дÈë½áÊøÁË
		else//дÈëδ½áÊø
		{
			secpos++;				//ÉÈÇøµØÖ·Ôö1
			secoff=0;				//Æ«ÒÆλÖÃΪ0 	 
		   	pBuffer+=secremain;  	//Ö¸ÕëÆ«ÒÆ
			WriteAddr+=secremain;	//дµØÖ·Æ«ÒÆ	   
		   	NumToWrite-=secremain;	//×Ö½Ú(16λ)ÊýµÝ¼õ
			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//ÏÂÒ»¸öÉÈÇø»¹ÊÇд²»Íê
			else secremain=NumToWrite;//ÏÂÒ»¸öÉÈÇø¿ÉÒÔдÍêÁË
		}	 
	};	
	HAL_FLASH_Lock();//ÉÏËø
}

/*******************************************************************************
Name            :STMFLASH_Write_NoCheck
Syntax          :void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )   
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )   
{ 			 		 
	uint16_t i;	
	
	for(i=0;i<NumToWrite;i++)
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
	  WriteAddr+=2;                                    //µØÖ·Ôö¼Ó2.
	}  
} 	
	
/*******************************************************************************
Name            :STMFLASH_ReadHalfWord
Syntax          :uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
{
	return *(__IO uint16_t*)faddr; 
}

/*******************************************************************************
Name            :STMFLASH_Read
Syntax          :void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )   	
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )   	
{
	uint16_t i;
	
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//¶ÁÈ¡2¸ö×Ö½Ú.
		ReadAddr+=2;//Æ«ÒÆ2¸ö×Ö½Ú.	
	}
}
下面是区域跳转
typedef  void ( * pIapFun_TypeDef ) ( void ); //定义一个函数类型的参数
/*******************************************************************************
Name            :MSR_MSP
Syntax          :__asm void MSR_MSP ( uint32_t ulAddr 
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
__asm void MSR_MSP ( uint32_t ulAddr ) 
{
    MSR MSP, r0 			                   //set Main Stack value
    BX r14
}


/*******************************************************************************
Name            :IAP_ExecuteApp
Syntax          :void IAP_ExecuteApp ( uint32_t ulAddr_App )
Sync/Async      :-
Reentrancy      :Non Reentrant
Parameters(in)  :None              :
Parameters(out) :None              :-
Return value    :-                 :-
Description     :-                 :-
Call By         :ZY                :-
Date						:2020/08/01
|********************************************************************************/
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
	pIapFun_TypeDef pJump2App; 
																								
	if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )	  //¼ì²éÕ»¶¥µØÖ·ÊÇ·ñºÏ·¨.
	{ 
		pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 );	//Óû§´úÂëÇøµÚ¶þ¸ö×ÖΪ³ÌÐò¿ªÊ¼µØÖ·(¸´Î»µØÖ·)		
		MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //³õʼ»¯APP¶ÑÕ»Ö¸Õë(Óû§´úÂëÇøµÄµÚÒ»¸ö×ÖÓÃÓÚ´æ·ÅÕ»¶¥µØÖ·)
		pJump2App ();								                                    	//Ìøתµ½APP.
	}
}		

以上就是IAP程序的重要逻辑部分,以下是一些特殊问题处理

1.IAP和用户区是两个区域了,在哪儿可以设置他们文件存储的位置?

IAP区域的地址设定
在这里插入图片描述
用户区域的地址设定,如果用户区域地址做了修改 需要在基础文件里面修改他的 中断向量地址
在这里插入图片描述
在这里插入图片描述

2.怎么设置用户区域应该取什么值?取大了或者取小了会有什么问题?

IAP函数里面有个宏定义 定义的用户区域:
#define APP_START_ADDR 0x8005000 //应用程序起始位置

下图是 stm32f103的flash说明

在这里插入图片描述

配置的区域可以选择当前存储页的头位置 如0x8001000、0x8001800等,配置的大小可以根据IAP的大小调整。可以用stvp和STM32CubeProgrammer 或者JFlash等软件测试

在这里插入图片描述
可以吧IAP程序下载进去反查 flash 根据实际大小来决定用户区域的的起始值
如图所示 ,IAP程序到0x8003280就结束了 我可以选择0x8004000 或者0x8005000
在这里插入图片描述

3.如果我要设置看门狗跳转了后 看门狗还会有效吗? 需不需要重新设置?

注意!!独立看门狗在IAP程序启动了过后,通过跳转语句跳转到用户区域,看门狗还是有效的,但是如果没有在用户区域做看门狗配置,喂狗会失败,而且如果用户区域使用的HAL库,没有配置看门狗好像没有对应生成的库文件。

一句话说明就是如果看门狗配置了 跳转了区域,任然是有效的(iwdg),除非是复位处理

4. 为啥存储的时候数据会丢失单片机会宕机?跳转的时候调用了函数为啥跳转后没有执行函数?

第一个问题: 注意查看对应芯片的数据手册,有的芯片flash 需要做4字节对齐(就是一次性存4字节),有的需要8字节对齐 比如STM32G0系列的

第二个问题: 注意!! 之前开了外设时钟的 或者配置了引脚的 一定要Deinit恢复默认,中断用到了的一定要关掉,系统的可以不管。

算了还是吧工程文件上传吧

链接:https://pan.baidu.com/s/1D-edLrLVEvFx3FSCgq6O6w
提取码:ana3

写作不易,如果对你有帮助麻烦打个赏!!

如果综上问题不是你要找的问题可以加Q群 :764284134 问一下

  • 23
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: STM32 IAP(In-Application Programming)串口 Bootloader是一种用于STM32微控制器的串口引导程序。它允许用户通过串口接口对STM32芯片进行固件(程序)的更新和烧录。 使用IAP串口Bootloader的好处在于,我们可以通过外部设备(如电脑)的串口与STM32微控制器通信,而无需使用专门的烧录工具。这样就方便了固件的更新,同时节省了成本。 在使用IAP串口Bootloader进行固件更新时,我们需要先将Bootloader本身烧录到STM32芯片的内部Flash中。然后,我们可以通过串口接口将新的固件程序发送到芯片,并在芯片上执行这个程序。 一个典型的IAP串口Bootloader应该具备以下功能:与上位机(电脑)通信的串口接口、对固件进行接收和存储的能力、对接收到的固件进行校验的功能以确保完整性、重启和跳转到新固件的能力等。 通过使用IAP串口Bootloader,我们可以实现对STM32芯片的远程更新。这对于大规模应用中的批量更新非常有用,因为我们可以通过网络将固件传输到每个设备,并远程执行固件更新。这大大提高了更新的效率和便捷性。 总之,STM32 IAP串口Bootloader允许通过串口对STM32芯片进行固件更新,具备了简单易用、低成本、远程更新等优势,用于批量更新和快速迭代的产品开发中非常有用。 ### 回答2: STM32 IAP (In-Application Programming) 是一种可以通过串口来实现固件升级的串口 bootloader。这意味着我们可以通过串口接口,不用外部编程器,直接在已经部署的STM32芯片上更新固件。 STM32的串口 bootloader 非常方便,因为大多数STM32芯片都集成了用于串口通信的USART模块。通过该串口模块,我们可以与主机计算机建立通信,并使用升级固件的工具来发送新的固件文件。 串口 bootloader 通常由两部分组成。一部分是在芯片出厂时预烧录的引导程序,也称为ROM bootloader。这个引导程序负责在芯片上电时的初始化操作,并负责在引导模式或应用模式之间进行切换。另一部分是我们可以根据需要编写的应用程序,通常被称为User Application。User Application负责处理应用层的功能,同时需要处理与串口 bootloader 之间的通信。 在运行时,当芯片启动时,ROM bootloader会检测特定引脚(例如 BOOT0 引脚)是否被拉低,如果是,则芯片会进入串口 bootloader 模式。然后,主机计算机可以通过串口与芯片通信,并发送新的固件文件。芯片会通过串口接收文件并存储在相应的存储位置上。 一旦新的固件文件接收完毕,芯片将验证文件的完整性,并在通过验证后将其存储在相应的固件区域。然后芯片将控制权转交给User Application,让其开始使用新的固件。通过这种方式,我们可以使用串口 bootloader 来实现非常方便的固件升级,而不需要物理访问芯片和外部编程器的支持。 总而言之,STM32 IAP 串口 bootloader 是一种用于通过串口接口进行固件升级的方便方式。它由ROM bootloader和User Application组成,通过串口与主机计算机通信,并处理新固件文件的接收和存储。这种方法避免了对外部编程器的依赖,使得固件升级变得更加方便快捷。 ### 回答3: STM32是一种嵌入式微控制器系列,可以使用UART串口来实现IAP(应用程序在内部存储器中更新)功能。而串口Bootloader是一种特殊的程序,允许通过串口接口进行外部的固件升级。 串口Bootloader主要有以下功能: 1. 通过串口接口与计算机或其他设备进行通信,从而实现固件的传输和更新。 2. 提供一个命令界面,通过接收计算机发送的指令来执行不同的操作,如擦除内存、写入新固件等。 3. 具备错误检测和恢复机制,确保固件的传输和写入的正确性。 4. 具备固件校验功能,保证新固件的完整性和安全性。 在stm32中,IAP功能通过串口Bootloader来实现。首先,将带有Bootloader的特殊固件烧录到stm32芯片中。然后,将计算机或其他设备通过串口与stm32连接,并发送相应的命令和固件数据。串口Bootloader将接收到的指令解析,并执行相应的操作。例如,当接收到固件数据时,Bootloader将把数据写入芯片内部的闪存。在整个过程中,Bootloader会负责检测错误并进行恢复,以保证固件的正确更新。 通过串口Bootloader,用户可以方便地对stm32芯片中的应用程序进行升级和更新,无需进行物理连接或者使用其他烧录工具。这为产品的开发和维护提供了灵活性和便利性,同时也增强了固件的稳定性和可靠性。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值