赞
踩
本设计是基于STM32F103ZET6的HAL库的串口OTA升级方案,用的是Xmodem+串口的升级方案,代码部分有部分模块参考了别人写的,但整个项目框架是完完全全自己搭建的。接下来就开始分模块介绍本项目。
其实,实现OTA的方案很简单,就是把STM32的flash分成两个分区,一个分区叫BOOT分区(本项目由于是基于STM32实现,就叫SBOOT),另一个分区就是程序分区(APP),是你板子启动后运行的功能。有了这两个分区,想要实现OTA升级,就很好办了。用BOOT分区去擦写程序分区,便可实现对程序的更新。而程序分区则和我们正常写的单片机程序差别不大,主要是自己想实现的功能。所以实现整个OTA升级的关键是在BOOT分区的搭建上。
此分区是单片机上电后运行的第一个分区,在这里我们要实现的功能有以下两点
1、接收程序代码,烧录到APP分区。
2、跳转到程序分区执行。
先来介绍一下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
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
这部分没什么特别的,改一下程序的起始地址和中断向量表,设置一个脚本生成.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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。