简介
本设计是基于STM32F103ZET6的HAL库的串口OTA升级方案,用的是Xmodem+串口的升级方案,代码部分有部分模块参考了别人写的,但整个项目框架是完完全全自己搭建的。接下来就开始分模块介绍本项目。
项目思路
其实,实现OTA的方案很简单,就是把STM32的flash分成两个分区,一个分区叫BOOT分区(本项目由于是基于STM32实现,就叫SBOOT),另一个分区就是程序分区(APP),是你板子启动后运行的功能。有了这两个分区,想要实现OTA升级,就很好办了。用BOOT分区去擦写程序分区,便可实现对程序的更新。而程序分区则和我们正常写的单片机程序差别不大,主要是自己想实现的功能。所以实现整个OTA升级的关键是在BOOT分区的搭建上。
SBOOT分区的搭建
此分区是单片机上电后运行的第一个分区,在这里我们要实现的功能有以下两点
1、接收程序代码,烧录到APP分区。
2、跳转到程序分区执行。
Xmodem协议接收
先来介绍一下Xmodem协议
通讯流程
1、发送方等待接收方的C
2、收到接收方的C后,发送第一包数据
3、接收方收到数据后停止发C,在数据源校验无误后,发送ACK;校验有误,发送NACK;
4、发送方收到ACK后发送下一包数据,收到NACK重复发送当前一包数据(在接收到ACK/NACK前发送方处在静默状态)。
5、最后一包发送完,发送方发送EOT。
6、接收方收到EOT回ACK.
以上便是完整一次Xmodem的传输。
数据包
数据包构成如下(本次采用的是128byte形式)
帧头|第几包数据|255-第几包数据|[0-127]个数据|CRC校验高八位|CRC低八位
帧头固定为0x01,而接收方只需要拿到数据后做一次CRC校验,便可以确定数据接收有无错误。
ACK:发送十六进制06;
NACK:发送十六进制15;
接收代码实现
此处比较难的点就是对数据包的校验,和对数据包个数的处理。我在网上看很多人的代码接收了直接写入单片机的flash,我个人是非常不建议大家用这种写法的,首先对单片机的flash擦写本就有寿命,高频的擦写非常的不好。其次由于这是在中断回调函数中,涉及到Flash的擦除非常耗时,有可能会导致程序死锁(亲测),也不利于代码的编写。所以我建议大家还是在完整接收完一整个数据缓存好后,再统一的对flash进行擦写。
norflash的驱动代码。由于我是正点原子的板子,所以就直接移植来用了,大家有其他缓存芯片也可以,不一定非要norflash。
//这里是截取自我代码中的串口DMA空闲中断回调函数
else if(BootStructINFO.BootFlag==UPDATE) //如果是更新模式
{
if(rx_buf[0]==0x01&&data_leng==133) //如果包头是0x01,包长度为133,则一包数据初步认为正确
{
BootStructINFO.Event_bit&=~EVENT_C_BIT; //把发C事件标志位给清零,停止发C
BootStructINFO.XmodeCRC= Xmodem_crc16(&rx_buf[3],128); //对128字节数据校验
if(BootStructINFO.XmodeCRC==(rx_buf[131]*256+rx_buf[132])) //如果我们的CRC和数据包给的CRC一致,可以确认这是一包完全正确的数据
{
BootStructINFO.XmodeRecNum+=1; //接收数+1,这里的接收数是接收数据包的个数,方便后期统计接收数据量
memcpy(&Updatebuff[0+128*((BootStructINFO.XmodeRecNum-1)%16)],&rx_buf[3],128); //把中间的数据段给复制到UpdaeBuff中(这里我设置的数组大小为2048,也就是说16包数据才能填满一个缓冲区)
if((BootStructINFO.XmodeRecNum)%16==0) //如果接收到16包数据,即此刻缓冲区已经满了
{
BootStructINFO.XmodeRecPge+=1; //接收到的页数+1,也是方便后期进行数据搬运
norflash_write(Updatebuff,(BootStructINFO.XmodeRecPge-1)*2048,2048); //把接收到的数据写入Norflash里(外置Norflash)
}
printf("\x06"); //发送ACK
}
else
{
printf("\x15"); //如果不是数据正确的情况,就发送NACK
}
}
else if(data_leng==1&&rx_buf[0]==0x04) //如果接收到长度为1,且为0x04的数据,则表明已经完成一次协议通讯了
{
printf("\x06"); //发送一个ACK高职
if(BootStructINFO.XmodeRecNum%16!=0) //这里,由于我们接收到的数据量不可能是2048的整数倍,可能会多出几包数据这里进行一个判断
{
BootStructINFO.XmodeRceLstPN = BootStructINFO.XmodeRecNum%16; //如果这里把多出来的数据包数量存起来,方便后期搬运
norflash_write(Updatebuff,BootStructINFO.XmodeRecPge*2048,(BootStructINFO.XmodeRceLstPN)*128); //把多出来的数据写入Norflash
BootStructINFO.XmodeRecPge+=1; //因为我们是新开一页写的,这里不要忘记页数+1
}
BootStructINFO.Event_bit|=EVENT_FLASH_BIT; //使能FLASH更新标志事件
}
else
{
printf("\x15");
}
//CRC校验,没什么好讲的,大家感兴趣自己去学一下
uint16_t Xmodem_crc16(uint8_t *data,uint16_t datalen)
{
uint8_t i;
uint16_t Crcinit = 0x0000;
uint16_t Crcipoly = 0x1021;//这个是通讯协议规定了的
while(datalen--)
{
Crcinit = (*data<<8)^Crcinit;
for(i=0;i<8;i++)
{
if(Crcinit&0x8000)
{
Crcinit = (Crcinit<<1)^Crcipoly;
}
else
{
Crcinit = (Crcinit<<1);
}
}
data++;
}
return Crcinit;
}
Flash的写入
在接收完一整包数据后,就开始写入单片机的flash了,这里补充一点前置知识。
关于单片机的Flash
1、单片机的flash的数据只能从1变为0,不能从0变为1,所以在写入之前要先擦除
2、单片机的flash只能按页擦除。
3、stm32单片机的flash页大小分为两种情况,128k以下的一页1k,128k以上的2k一页。我的是zet6 512k以2k为一页有256页
4、单片机flash寻找是从0x08000000开始的。
有了以上知识,大家看我这里关于flash空间操作的宏定义应该就不难了,这里我们分了20页flash给SBOOT用,合计20*2048byte(实际代码用不了这么多),剩余的全用作程序分区。
else if(BootStructINFO.BootFlag==UPDATE)
{
if(BootStructINFO.Event_bit&EVENT_MSG_BIT)//这里是一个状态机的开始标志位,切换到UPdata模式后,先输出提示信息
{
printf("Warning:Cant quit before finished\r\n");
printf("Please Use the XModem Dowload\r\n");
BootStructINFO.Event_bit &= ~EVENT_MSG_BIT;
BootStructINFO.Event_bit |= EVENT_C_BIT;
}
else if(BootStructINFO.Event_bit&EVENT_C_BIT)//跳转到发C状态
{
BootStructINFO.XmodeTimer++;
if(BootStructINFO.XmodeTimer==2)
{
BootStructINFO.XmodeTimer=0;
printf("C");
}
}
else if(BootStructINFO.Event_bit&EVENT_FLASH_BIT)//如果接收完成使能了写入位开始写入数据
{
printf("ready write %d pack\r\n",BootStructINFO.XmodeRecNum);
HAL_Delay(3000);
eraseflash(SYS_START_PAG,SYS_PAGE); //先对主程序部分的256-20页全部擦除
printf("Erase Done\r\n");
BootStructINFO.Event_bit&=~EVENT_FLASH_BIT; //把写入先失能,避免重复触发
memset(Updatebuff,0,2048); //把updatebuf清零,后续要用这个做数据搬运桥梁
for(uint16_t i=0;i<BootStructINFO.XmodeRecPge-1;i++)//这里我们先用一个for循环写入除最后一页外的其他页(因为最后一页有可能并不满2048个数据,所以要单独判断)
{
norflash_read(Updatebuff,0x00000000+i*PAGE_SIZE,PAGE_SIZE);
writeflash(SYS_START_ADDR+i*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
}
if(BootStructINFO.XmodeRceLstPN==0)//如果接收的余出来的数据包为0即,恰好是放满整页的的(128byte的数据包)
{
norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,PAGE_SIZE);//直接写一页
writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
}
else//如果有余出来的
{
norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,BootStructINFO.XmodeRceLstPN*128);//把余出来的单独读写
writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,BootStructINFO.XmodeRceLstPN*128);
}
printf("Write Finished\r\n");//写完以后打印
HAL_Delay(2000);
HAL_NVIC_SystemReset();//重启系统
}
}
flash写入和擦除的代码是经过封装的,现在把这些函数贴出来。
uint8_t eraseflash(uint16_t addr,uint16_t nbPages)
{
unsigned int pageError = 0;
uint32_t pageAddress = 0x08000000+addr*2048;
FLASH_EraseInitTypeDef eraseInit;
eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
eraseInit.PageAddress = pageAddress;
eraseInit.Banks = FLASH_BASE;
eraseInit.NbPages = nbPages;
HAL_FLASH_Unlock();
if(HAL_FLASHEx_Erase(&eraseInit,&pageError) != HAL_OK)
{
HAL_FLASH_Lock();
return 1;
}
HAL_FLASH_Lock();
return 0;
}
void writeflash(uint32_t saddr,uint32_t *wdata,uint32_t wnum)
{
HAL_FLASH_Unlock();
while(wnum)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,saddr,*wdata);
wnum -=4;
saddr+=4;
wdata++;
}
HAL_FLASH_Lock();
}
系统完整性搭建
main函数(HAL库生成的)
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
#include "stdio.h"
#include "crc.h"
#include "flash_ctrl.h"
#include "boot.h"
#include "norflash.h"
#include "string.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t rx_buf[270];
uint8_t Updatebuff[PAGE_SIZE];
struct BOOT_Struct {
uint8_t BootFlag;
uint8_t Event_bit;
uint32_t XmodeTimer;
uint32_t XmodeRecNum;
uint32_t XmodeCRC;
uint32_t XmodeRecPge;
uint32_t XmodeRceLstPN;
}BootStructINFO;
struct Wait_struct{
uint8_t Wait_Cmd;
uint8_t Cmd_Count;
}WaitTimeCount;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buf,270);
BootStructINFO.BootFlag = WAIT_CMD;
norflash_init();
printf("Press a within 3 seconds to enter sboot\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(100);
if(BootStructINFO.BootFlag==WAIT_CMD)
{
WaitTimeCount.Wait_Cmd++;
if(WaitTimeCount.Wait_Cmd%10==0)
{
printf("Press a within %1d seconds to enter sboot\r\n",3-(WaitTimeCount.Wait_Cmd/10));
}
if(WaitTimeCount.Wait_Cmd==30)//倒计时结束跳转到主分区
{
HAL_DeInit();
__set_PRIMASK(1);
Load_SYS_User(SYS_START_ADDR);
}
}
else if(BootStructINFO.BootFlag==INTO_CMD)//输出命令行
{
WaitTimeCount.Cmd_Count++;
if(WaitTimeCount.Cmd_Count%50==0)
{
WaitTimeCount.Cmd_Count=0;
printf("/***********************************************************/\r\n");
printf("[A] Update the System\r\n");
printf("[B] Run into the MainSys\r\n");
printf("[C] Restart the Sysyem\r\n");
printf("\r\n\r\n\r\n\r\n");
}
}
else if(BootStructINFO.BootFlag==UPDATE)
{
if(BootStructINFO.Event_bit&EVENT_MSG_BIT)//这里是一个状态机的开始标志位,切换到UPdata模式后,先输出提示信息
{
printf("Warning:Cant quit before finished\r\n");
printf("Please Use the XModem Dowload\r\n");
BootStructINFO.Event_bit &= ~EVENT_MSG_BIT;
BootStructINFO.Event_bit |= EVENT_C_BIT;
}
else if(BootStructINFO.Event_bit&EVENT_C_BIT)//跳转到发C状态
{
BootStructINFO.XmodeTimer++;
if(BootStructINFO.XmodeTimer==2)
{
BootStructINFO.XmodeTimer=0;
printf("C");
}
}
else if(BootStructINFO.Event_bit&EVENT_FLASH_BIT)//如果接收完成使能了写入位开始写入数据
{
printf("ready write %d pack\r\n",BootStructINFO.XmodeRecNum);
HAL_Delay(3000);
eraseflash(SYS_START_PAG,SYS_PAGE); //先对主程序部分的256-20页全部擦除
printf("Erase Done\r\n");
BootStructINFO.Event_bit&=~EVENT_FLASH_BIT; //把写入先失能,避免重复触发
memset(Updatebuff,0,2048); //把updatebuf清零,后续要用这个做数据搬运桥梁
for(uint16_t i=0;i<BootStructINFO.XmodeRecPge-1;i++)//这里我们先用一个for循环写入除最后一页外的其他页(因为最后一页有可能并不满2048个数据,所以要单独判断)
{
norflash_read(Updatebuff,0x00000000+i*PAGE_SIZE,PAGE_SIZE);
writeflash(SYS_START_ADDR+i*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
}
if(BootStructINFO.XmodeRceLstPN==0)//如果接收的余出来的数据包为0即,恰好是放满整页的的(128byte的数据包)
{
norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,PAGE_SIZE);//直接写一页
writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
}
else//如果有余出来的
{
norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,BootStructINFO.XmodeRceLstPN*128);//把余出来的单独读写
writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,BootStructINFO.XmodeRceLstPN*128);
}
printf("Write Finished\r\n");//写完以后打印
HAL_Delay(2000);
HAL_NVIC_SystemReset();//重启系统
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance==USART1)
{
uint8_t data_leng = 270-__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//计算本次接收到的字符串个数
if(BootStructINFO.BootFlag==WAIT_CMD)//如果在等a模式下
{
if(data_leng==1&&rx_buf[0]=='a')//接收到a
{
BootStructINFO.BootFlag=INTO_CMD;//进入输出控制台模式
WaitTimeCount.Cmd_Count=49;
}
}
else if(BootStructINFO.BootFlag==INTO_CMD)//如果在控制台模式下
{
if(data_leng==1)
{
switch(rx_buf[0])//收到以下字符
{
case 'A':{BootStructINFO.BootFlag=UPDATE;BootStructINFO.Event_bit |= EVENT_MSG_BIT;}break;
case 'B':Load_SYS_User(SYS_START_ADDR);break;
case 'C':HAL_NVIC_SystemReset();break;
default:printf("cmd erro\r\n");break;
}
}
else
{
printf("cmd erro\r\n");
}
}
else if(BootStructINFO.BootFlag==UPDATE) //如果是更新模式
{
if(rx_buf[0]==0x01&&data_leng==133) //如果包头是0x01,包长度为133,则一包数据初步认为正确
{
BootStructINFO.Event_bit&=~EVENT_C_BIT; //把发C事件标志位给清零,停止发C
BootStructINFO.XmodeCRC= Xmodem_crc16(&rx_buf[3],128); //对128字节数据校验
if(BootStructINFO.XmodeCRC==(rx_buf[131]*256+rx_buf[132])) //如果我们的CRC和数据包给的CRC一致,可以确认这是一包完全正确的数据
{
BootStructINFO.XmodeRecNum+=1; //接收数+1,这里的接收数是接收数据包的个数,方便后期统计接收数据量
memcpy(&Updatebuff[0+128*((BootStructINFO.XmodeRecNum-1)%16)],&rx_buf[3],128); //把中间的数据段给复制到UpdaeBuff中(这里我设置的数组大小为2048,也就是说16包数据才能填满一个缓冲区)
if((BootStructINFO.XmodeRecNum)%16==0) //如果接收到16包数据,即此刻缓冲区已经满了
{
BootStructINFO.XmodeRecPge+=1; //接收到的页数+1,也是方便后期进行数据搬运
norflash_write(Updatebuff,(BootStructINFO.XmodeRecPge-1)*2048,2048); //把接收到的数据写入Norflash里(外置Norflash)
}
printf("\x06"); //发送ACK
}
else
{
printf("\x15"); //如果不是数据正确的情况,就发送NACK
}
}
else if(data_leng==1&&rx_buf[0]==0x04) //如果接收到长度为1,且为0x04的数据,则表明已经完成一次协议通讯了
{
printf("\x06"); //发送一个ACK高职
if(BootStructINFO.XmodeRecNum%16!=0) //这里,由于我们接收到的数据量不可能是2048的整数倍,可能会多出几包数据这里进行一个判断
{
BootStructINFO.XmodeRceLstPN = BootStructINFO.XmodeRecNum%16; //如果这里把多出来的数据包数量存起来,方便后期搬运
norflash_write(Updatebuff,BootStructINFO.XmodeRecPge*2048,(BootStructINFO.XmodeRceLstPN)*128); //把多出来的数据写入Norflash
BootStructINFO.XmodeRecPge+=1; //因为我们是新开一页写的,这里不要忘记页数+1
}
BootStructINFO.Event_bit|=EVENT_FLASH_BIT; //使能FLASH更新标志事件
}
else
{
printf("\x15");
}
}
else
{
printf("\x15");
}
memset(rx_buf,0,270);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,(uint8_t *)rx_buf,270);
}
}
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
printf("error now\r\n");
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
最后就是跳转代码,这里用了点汇编,也是借鉴了一个大佬的写法。
typedef void (*load_sys)(void);
load_sys Load_SYS;
__ASM void MSR_SP(uint32_t addr)//汇编传参,addr是第一个变量,值直接给到r0寄存器
{
MSR MSP,r0//设置sp指针
BX r14 //恢复现场
}
void Load_SYS_User(uint32_t addr)
{
if((*(uint32_t *)addr>0x20000000-1)&&((*(uint32_t *)addr<(0x20000000+0x10000))))//看看指针是否在合理区间(RAM中)
{
MSR_SP(*(uint32_t *)addr);//sp跳转
Load_SYS = (load_sys)*(uint32_t *)(addr+4);//地址强转为函数,这里转的是(程序区)复位Reset_Handler回调函数
BOOT_Clear();
Load_SYS();//执行复位函数
}
}
void BOOT_Clear(void)//初始化的清理
{
HAL_UART_DeInit(&huart1);
HAL_SPI_DeInit(&g_spi2_handler);
HAL_DeInit();
}
基本上分区搭建的代码差不多就到这里了,后续还有一个细节要修改。
这里改为整片擦除,然后先烧录SBOOT程序再烧录APP
APP分区的搭建
这部分没什么特别的,改一下程序的起始地址和中断向量表,设置一个脚本生成.bin文件就好了。
修改程序起始地址,改成SBOOT分区之后
设置中断向量表,hal库下设置中断向量表不在.s文件下,而是在system_stm32f1xx.c下,找到,把这里的注释去掉。
写上偏移量(注意,是偏移量,不是地址值)
添加生成bin文件脚本:fromelf --bin -o ".\bin_file\@L.bin" "#L"
自此整一个OTA的环境就搭建完成了,现在开始测试烧录。
测试
上电提示:
3s无操作跳转到APP区:
按下a呼出控制台
准备烧录(换了个串口软件):
重启后输出:
非常成功
项目踩坑点总结
1、在DMA空闲回调函数里直接烧写flash没有norflash缓冲,程序直接跑飞不可取。
2、在Load_SYS_User(SYS_START_ADDR);加载到APP分区之前,没对BOOT分区初始化的外设去初始化,导致进入到APP后出现各种异常。
3、接收的数据包的数量换算,很容易算错。
工程共享
Github:GitHub - Norginx/stm32f103_OTA
Xmodem工具:链接: https://pan.baidu.com/s/1o4WoXOs3o4Xp21EhCHx6rQ
提取码: u1mi