当前位置:   article > 正文

STM32的OTA升级方案_stm32 双bank flash配置ota升级

stm32 双bank flash配置ota升级

简介

        本设计是基于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。

  1. //这里是截取自我代码中的串口DMA空闲中断回调函数
  2. else if(BootStructINFO.BootFlag==UPDATE) //如果是更新模式
  3. {
  4. if(rx_buf[0]==0x01&&data_leng==133) //如果包头是0x01,包长度为133,则一包数据初步认为正确
  5. {
  6. BootStructINFO.Event_bit&=~EVENT_C_BIT; //把发C事件标志位给清零,停止发C
  7. BootStructINFO.XmodeCRC= Xmodem_crc16(&rx_buf[3],128); //对128字节数据校验
  8. if(BootStructINFO.XmodeCRC==(rx_buf[131]*256+rx_buf[132])) //如果我们的CRC和数据包给的CRC一致,可以确认这是一包完全正确的数据
  9. {
  10. BootStructINFO.XmodeRecNum+=1; //接收数+1,这里的接收数是接收数据包的个数,方便后期统计接收数据量
  11. memcpy(&Updatebuff[0+128*((BootStructINFO.XmodeRecNum-1)%16)],&rx_buf[3],128); //把中间的数据段给复制到UpdaeBuff中(这里我设置的数组大小为2048,也就是说16包数据才能填满一个缓冲区)
  12. if((BootStructINFO.XmodeRecNum)%16==0) //如果接收到16包数据,即此刻缓冲区已经满了
  13. {
  14. BootStructINFO.XmodeRecPge+=1; //接收到的页数+1,也是方便后期进行数据搬运
  15. norflash_write(Updatebuff,(BootStructINFO.XmodeRecPge-1)*2048,2048); //把接收到的数据写入Norflash里(外置Norflash)
  16. }
  17. printf("\x06"); //发送ACK
  18. }
  19. else
  20. {
  21. printf("\x15"); //如果不是数据正确的情况,就发送NACK
  22. }
  23. }
  24. else if(data_leng==1&&rx_buf[0]==0x04) //如果接收到长度为1,且为0x04的数据,则表明已经完成一次协议通讯了
  25. {
  26. printf("\x06"); //发送一个ACK高职
  27. if(BootStructINFO.XmodeRecNum%16!=0) //这里,由于我们接收到的数据量不可能是2048的整数倍,可能会多出几包数据这里进行一个判断
  28. {
  29. BootStructINFO.XmodeRceLstPN = BootStructINFO.XmodeRecNum%16; //如果这里把多出来的数据包数量存起来,方便后期搬运
  30. norflash_write(Updatebuff,BootStructINFO.XmodeRecPge*2048,(BootStructINFO.XmodeRceLstPN)*128); //把多出来的数据写入Norflash
  31. BootStructINFO.XmodeRecPge+=1; //因为我们是新开一页写的,这里不要忘记页数+1
  32. }
  33. BootStructINFO.Event_bit|=EVENT_FLASH_BIT; //使能FLASH更新标志事件
  34. }
  35. else
  36. {
  37. printf("\x15");
  38. }
  1. //CRC校验,没什么好讲的,大家感兴趣自己去学一下
  2. uint16_t Xmodem_crc16(uint8_t *data,uint16_t datalen)
  3. {
  4. uint8_t i;
  5. uint16_t Crcinit = 0x0000;
  6. uint16_t Crcipoly = 0x1021;//这个是通讯协议规定了的
  7. while(datalen--)
  8. {
  9. Crcinit = (*data<<8)^Crcinit;
  10. for(i=0;i<8;i++)
  11. {
  12. if(Crcinit&0x8000)
  13. {
  14. Crcinit = (Crcinit<<1)^Crcipoly;
  15. }
  16. else
  17. {
  18. Crcinit = (Crcinit<<1);
  19. }
  20. }
  21. data++;
  22. }
  23. return Crcinit;
  24. }

 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(实际代码用不了这么多),剩余的全用作程序分区。

  1. else if(BootStructINFO.BootFlag==UPDATE)
  2. {
  3. if(BootStructINFO.Event_bit&EVENT_MSG_BIT)//这里是一个状态机的开始标志位,切换到UPdata模式后,先输出提示信息
  4. {
  5. printf("Warning:Cant quit before finished\r\n");
  6. printf("Please Use the XModem Dowload\r\n");
  7. BootStructINFO.Event_bit &= ~EVENT_MSG_BIT;
  8. BootStructINFO.Event_bit |= EVENT_C_BIT;
  9. }
  10. else if(BootStructINFO.Event_bit&EVENT_C_BIT)//跳转到发C状态
  11. {
  12. BootStructINFO.XmodeTimer++;
  13. if(BootStructINFO.XmodeTimer==2)
  14. {
  15. BootStructINFO.XmodeTimer=0;
  16. printf("C");
  17. }
  18. }
  19. else if(BootStructINFO.Event_bit&EVENT_FLASH_BIT)//如果接收完成使能了写入位开始写入数据
  20. {
  21. printf("ready write %d pack\r\n",BootStructINFO.XmodeRecNum);
  22. HAL_Delay(3000);
  23. eraseflash(SYS_START_PAG,SYS_PAGE); //先对主程序部分的256-20页全部擦除
  24. printf("Erase Done\r\n");
  25. BootStructINFO.Event_bit&=~EVENT_FLASH_BIT; //把写入先失能,避免重复触发
  26. memset(Updatebuff,0,2048); //把updatebuf清零,后续要用这个做数据搬运桥梁
  27. for(uint16_t i=0;i<BootStructINFO.XmodeRecPge-1;i++)//这里我们先用一个for循环写入除最后一页外的其他页(因为最后一页有可能并不满2048个数据,所以要单独判断)
  28. {
  29. norflash_read(Updatebuff,0x00000000+i*PAGE_SIZE,PAGE_SIZE);
  30. writeflash(SYS_START_ADDR+i*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
  31. }
  32. if(BootStructINFO.XmodeRceLstPN==0)//如果接收的余出来的数据包为0即,恰好是放满整页的的(128byte的数据包)
  33. {
  34. norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,PAGE_SIZE);//直接写一页
  35. writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
  36. }
  37. else//如果有余出来的
  38. {
  39. norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,BootStructINFO.XmodeRceLstPN*128);//把余出来的单独读写
  40. writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,BootStructINFO.XmodeRceLstPN*128);
  41. }
  42. printf("Write Finished\r\n");//写完以后打印
  43. HAL_Delay(2000);
  44. HAL_NVIC_SystemReset();//重启系统
  45. }
  46. }

flash写入和擦除的代码是经过封装的,现在把这些函数贴出来。

  1. uint8_t eraseflash(uint16_t addr,uint16_t nbPages)
  2. {
  3. unsigned int pageError = 0;
  4. uint32_t pageAddress = 0x08000000+addr*2048;
  5. FLASH_EraseInitTypeDef eraseInit;
  6. eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
  7. eraseInit.PageAddress = pageAddress;
  8. eraseInit.Banks = FLASH_BASE;
  9. eraseInit.NbPages = nbPages;
  10. HAL_FLASH_Unlock();
  11. if(HAL_FLASHEx_Erase(&eraseInit,&pageError) != HAL_OK)
  12. {
  13. HAL_FLASH_Lock();
  14. return 1;
  15. }
  16. HAL_FLASH_Lock();
  17. return 0;
  18. }
  19. void writeflash(uint32_t saddr,uint32_t *wdata,uint32_t wnum)
  20. {
  21. HAL_FLASH_Unlock();
  22. while(wnum)
  23. {
  24. HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,saddr,*wdata);
  25. wnum -=4;
  26. saddr+=4;
  27. wdata++;
  28. }
  29. HAL_FLASH_Lock();
  30. }

系统完整性搭建

main函数(HAL库生成的)

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file : main.c
  5. * @brief : Main program body
  6. ******************************************************************************
  7. * @attention
  8. *
  9. * Copyright (c) 2024 STMicroelectronics.
  10. * All rights reserved.
  11. *
  12. * This software is licensed under terms that can be found in the LICENSE file
  13. * in the root directory of this software component.
  14. * If no LICENSE file comes with this software, it is provided AS-IS.
  15. *
  16. ******************************************************************************
  17. */
  18. /* USER CODE END Header */
  19. /* Includes ------------------------------------------------------------------*/
  20. #include "main.h"
  21. #include "dma.h"
  22. #include "usart.h"
  23. #include "gpio.h"
  24. /* Private includes ----------------------------------------------------------*/
  25. /* USER CODE BEGIN Includes */
  26. #include "oled.h"
  27. #include "stdio.h"
  28. #include "crc.h"
  29. #include "flash_ctrl.h"
  30. #include "boot.h"
  31. #include "norflash.h"
  32. #include "string.h"
  33. /* USER CODE END Includes */
  34. /* Private typedef -----------------------------------------------------------*/
  35. /* USER CODE BEGIN PTD */
  36. uint8_t rx_buf[270];
  37. uint8_t Updatebuff[PAGE_SIZE];
  38. struct BOOT_Struct {
  39. uint8_t BootFlag;
  40. uint8_t Event_bit;
  41. uint32_t XmodeTimer;
  42. uint32_t XmodeRecNum;
  43. uint32_t XmodeCRC;
  44. uint32_t XmodeRecPge;
  45. uint32_t XmodeRceLstPN;
  46. }BootStructINFO;
  47. struct Wait_struct{
  48. uint8_t Wait_Cmd;
  49. uint8_t Cmd_Count;
  50. }WaitTimeCount;
  51. /* USER CODE END PTD */
  52. /* Private define ------------------------------------------------------------*/
  53. /* USER CODE BEGIN PD */
  54. /* USER CODE END PD */
  55. /* Private macro -------------------------------------------------------------*/
  56. /* USER CODE BEGIN PM */
  57. /* USER CODE END PM */
  58. /* Private variables ---------------------------------------------------------*/
  59. /* USER CODE BEGIN PV */
  60. /* USER CODE END PV */
  61. /* Private function prototypes -----------------------------------------------*/
  62. void SystemClock_Config(void);
  63. /* USER CODE BEGIN PFP */
  64. /* USER CODE END PFP */
  65. /* Private user code ---------------------------------------------------------*/
  66. /* USER CODE BEGIN 0 */
  67. /* USER CODE END 0 */
  68. /**
  69. * @brief The application entry point.
  70. * @retval int
  71. */
  72. int main(void)
  73. {
  74. /* USER CODE BEGIN 1 */
  75. /* USER CODE END 1 */
  76. /* MCU Configuration--------------------------------------------------------*/
  77. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  78. HAL_Init();
  79. /* USER CODE BEGIN Init */
  80. /* USER CODE END Init */
  81. /* Configure the system clock */
  82. SystemClock_Config();
  83. /* USER CODE BEGIN SysInit */
  84. /* USER CODE END SysInit */
  85. /* Initialize all configured peripherals */
  86. MX_GPIO_Init();
  87. MX_DMA_Init();
  88. MX_USART1_UART_Init();
  89. /* USER CODE BEGIN 2 */
  90. HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buf,270);
  91. BootStructINFO.BootFlag = WAIT_CMD;
  92. norflash_init();
  93. printf("Press a within 3 seconds to enter sboot\r\n");
  94. /* USER CODE END 2 */
  95. /* Infinite loop */
  96. /* USER CODE BEGIN WHILE */
  97. while (1)
  98. {
  99. HAL_Delay(100);
  100. if(BootStructINFO.BootFlag==WAIT_CMD)
  101. {
  102. WaitTimeCount.Wait_Cmd++;
  103. if(WaitTimeCount.Wait_Cmd%10==0)
  104. {
  105. printf("Press a within %1d seconds to enter sboot\r\n",3-(WaitTimeCount.Wait_Cmd/10));
  106. }
  107. if(WaitTimeCount.Wait_Cmd==30)//倒计时结束跳转到主分区
  108. {
  109. HAL_DeInit();
  110. __set_PRIMASK(1);
  111. Load_SYS_User(SYS_START_ADDR);
  112. }
  113. }
  114. else if(BootStructINFO.BootFlag==INTO_CMD)//输出命令行
  115. {
  116. WaitTimeCount.Cmd_Count++;
  117. if(WaitTimeCount.Cmd_Count%50==0)
  118. {
  119. WaitTimeCount.Cmd_Count=0;
  120. printf("/***********************************************************/\r\n");
  121. printf("[A] Update the System\r\n");
  122. printf("[B] Run into the MainSys\r\n");
  123. printf("[C] Restart the Sysyem\r\n");
  124. printf("\r\n\r\n\r\n\r\n");
  125. }
  126. }
  127. else if(BootStructINFO.BootFlag==UPDATE)
  128. {
  129. if(BootStructINFO.Event_bit&EVENT_MSG_BIT)//这里是一个状态机的开始标志位,切换到UPdata模式后,先输出提示信息
  130. {
  131. printf("Warning:Cant quit before finished\r\n");
  132. printf("Please Use the XModem Dowload\r\n");
  133. BootStructINFO.Event_bit &= ~EVENT_MSG_BIT;
  134. BootStructINFO.Event_bit |= EVENT_C_BIT;
  135. }
  136. else if(BootStructINFO.Event_bit&EVENT_C_BIT)//跳转到发C状态
  137. {
  138. BootStructINFO.XmodeTimer++;
  139. if(BootStructINFO.XmodeTimer==2)
  140. {
  141. BootStructINFO.XmodeTimer=0;
  142. printf("C");
  143. }
  144. }
  145. else if(BootStructINFO.Event_bit&EVENT_FLASH_BIT)//如果接收完成使能了写入位开始写入数据
  146. {
  147. printf("ready write %d pack\r\n",BootStructINFO.XmodeRecNum);
  148. HAL_Delay(3000);
  149. eraseflash(SYS_START_PAG,SYS_PAGE); //先对主程序部分的256-20页全部擦除
  150. printf("Erase Done\r\n");
  151. BootStructINFO.Event_bit&=~EVENT_FLASH_BIT; //把写入先失能,避免重复触发
  152. memset(Updatebuff,0,2048); //把updatebuf清零,后续要用这个做数据搬运桥梁
  153. for(uint16_t i=0;i<BootStructINFO.XmodeRecPge-1;i++)//这里我们先用一个for循环写入除最后一页外的其他页(因为最后一页有可能并不满2048个数据,所以要单独判断)
  154. {
  155. norflash_read(Updatebuff,0x00000000+i*PAGE_SIZE,PAGE_SIZE);
  156. writeflash(SYS_START_ADDR+i*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
  157. }
  158. if(BootStructINFO.XmodeRceLstPN==0)//如果接收的余出来的数据包为0即,恰好是放满整页的的(128byte的数据包)
  159. {
  160. norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,PAGE_SIZE);//直接写一页
  161. writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,PAGE_SIZE);
  162. }
  163. else//如果有余出来的
  164. {
  165. norflash_read(Updatebuff,0x00000000+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,BootStructINFO.XmodeRceLstPN*128);//把余出来的单独读写
  166. writeflash(SYS_START_ADDR+(BootStructINFO.XmodeRecPge-1)*PAGE_SIZE,(uint32_t *)Updatebuff,BootStructINFO.XmodeRceLstPN*128);
  167. }
  168. printf("Write Finished\r\n");//写完以后打印
  169. HAL_Delay(2000);
  170. HAL_NVIC_SystemReset();//重启系统
  171. }
  172. }
  173. /* USER CODE END WHILE */
  174. /* USER CODE BEGIN 3 */
  175. }
  176. /* USER CODE END 3 */
  177. }
  178. /**
  179. * @brief System Clock Configuration
  180. * @retval None
  181. */
  182. void SystemClock_Config(void)
  183. {
  184. RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  185. RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  186. /** Initializes the RCC Oscillators according to the specified parameters
  187. * in the RCC_OscInitTypeDef structure.
  188. */
  189. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  190. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  191. RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  192. RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  193. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  194. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  195. RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  196. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  197. {
  198. Error_Handler();
  199. }
  200. /** Initializes the CPU, AHB and APB buses clocks
  201. */
  202. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  203. |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  204. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  205. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  206. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  207. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  208. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  209. {
  210. Error_Handler();
  211. }
  212. }
  213. /* USER CODE BEGIN 4 */
  214. void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
  215. {
  216. if(huart->Instance==USART1)
  217. {
  218. uint8_t data_leng = 270-__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//计算本次接收到的字符串个数
  219. if(BootStructINFO.BootFlag==WAIT_CMD)//如果在等a模式下
  220. {
  221. if(data_leng==1&&rx_buf[0]=='a')//接收到a
  222. {
  223. BootStructINFO.BootFlag=INTO_CMD;//进入输出控制台模式
  224. WaitTimeCount.Cmd_Count=49;
  225. }
  226. }
  227. else if(BootStructINFO.BootFlag==INTO_CMD)//如果在控制台模式下
  228. {
  229. if(data_leng==1)
  230. {
  231. switch(rx_buf[0])//收到以下字符
  232. {
  233. case 'A':{BootStructINFO.BootFlag=UPDATE;BootStructINFO.Event_bit |= EVENT_MSG_BIT;}break;
  234. case 'B':Load_SYS_User(SYS_START_ADDR);break;
  235. case 'C':HAL_NVIC_SystemReset();break;
  236. default:printf("cmd erro\r\n");break;
  237. }
  238. }
  239. else
  240. {
  241. printf("cmd erro\r\n");
  242. }
  243. }
  244. else if(BootStructINFO.BootFlag==UPDATE) //如果是更新模式
  245. {
  246. if(rx_buf[0]==0x01&&data_leng==133) //如果包头是0x01,包长度为133,则一包数据初步认为正确
  247. {
  248. BootStructINFO.Event_bit&=~EVENT_C_BIT; //把发C事件标志位给清零,停止发C
  249. BootStructINFO.XmodeCRC= Xmodem_crc16(&rx_buf[3],128); //对128字节数据校验
  250. if(BootStructINFO.XmodeCRC==(rx_buf[131]*256+rx_buf[132])) //如果我们的CRC和数据包给的CRC一致,可以确认这是一包完全正确的数据
  251. {
  252. BootStructINFO.XmodeRecNum+=1; //接收数+1,这里的接收数是接收数据包的个数,方便后期统计接收数据量
  253. memcpy(&Updatebuff[0+128*((BootStructINFO.XmodeRecNum-1)%16)],&rx_buf[3],128); //把中间的数据段给复制到UpdaeBuff中(这里我设置的数组大小为2048,也就是说16包数据才能填满一个缓冲区)
  254. if((BootStructINFO.XmodeRecNum)%16==0) //如果接收到16包数据,即此刻缓冲区已经满了
  255. {
  256. BootStructINFO.XmodeRecPge+=1; //接收到的页数+1,也是方便后期进行数据搬运
  257. norflash_write(Updatebuff,(BootStructINFO.XmodeRecPge-1)*2048,2048); //把接收到的数据写入Norflash里(外置Norflash)
  258. }
  259. printf("\x06"); //发送ACK
  260. }
  261. else
  262. {
  263. printf("\x15"); //如果不是数据正确的情况,就发送NACK
  264. }
  265. }
  266. else if(data_leng==1&&rx_buf[0]==0x04) //如果接收到长度为1,且为0x04的数据,则表明已经完成一次协议通讯了
  267. {
  268. printf("\x06"); //发送一个ACK高职
  269. if(BootStructINFO.XmodeRecNum%16!=0) //这里,由于我们接收到的数据量不可能是2048的整数倍,可能会多出几包数据这里进行一个判断
  270. {
  271. BootStructINFO.XmodeRceLstPN = BootStructINFO.XmodeRecNum%16; //如果这里把多出来的数据包数量存起来,方便后期搬运
  272. norflash_write(Updatebuff,BootStructINFO.XmodeRecPge*2048,(BootStructINFO.XmodeRceLstPN)*128); //把多出来的数据写入Norflash
  273. BootStructINFO.XmodeRecPge+=1; //因为我们是新开一页写的,这里不要忘记页数+1
  274. }
  275. BootStructINFO.Event_bit|=EVENT_FLASH_BIT; //使能FLASH更新标志事件
  276. }
  277. else
  278. {
  279. printf("\x15");
  280. }
  281. }
  282. else
  283. {
  284. printf("\x15");
  285. }
  286. memset(rx_buf,0,270);
  287. HAL_UARTEx_ReceiveToIdle_DMA(&huart1,(uint8_t *)rx_buf,270);
  288. }
  289. }
  290. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
  291. {
  292. printf("error now\r\n");
  293. }
  294. /* USER CODE END 4 */
  295. /**
  296. * @brief This function is executed in case of error occurrence.
  297. * @retval None
  298. */
  299. void Error_Handler(void)
  300. {
  301. /* USER CODE BEGIN Error_Handler_Debug */
  302. /* User can add his own implementation to report the HAL error return state */
  303. __disable_irq();
  304. while (1)
  305. {
  306. }
  307. /* USER CODE END Error_Handler_Debug */
  308. }
  309. #ifdef USE_FULL_ASSERT
  310. /**
  311. * @brief Reports the name of the source file and the source line number
  312. * where the assert_param error has occurred.
  313. * @param file: pointer to the source file name
  314. * @param line: assert_param error line source number
  315. * @retval None
  316. */
  317. void assert_failed(uint8_t *file, uint32_t line)
  318. {
  319. /* USER CODE BEGIN 6 */
  320. /* User can add his own implementation to report the file name and line number,
  321. ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  322. /* USER CODE END 6 */
  323. }
  324. #endif /* USE_FULL_ASSERT */

最后就是跳转代码,这里用了点汇编,也是借鉴了一个大佬的写法。

  1. typedef void (*load_sys)(void);
  2. load_sys Load_SYS;
  3. __ASM void MSR_SP(uint32_t addr)//汇编传参,addr是第一个变量,值直接给到r0寄存器
  4. {
  5. MSR MSP,r0//设置sp指针
  6. BX r14 //恢复现场
  7. }
  8. void Load_SYS_User(uint32_t addr)
  9. {
  10. if((*(uint32_t *)addr>0x20000000-1)&&((*(uint32_t *)addr<(0x20000000+0x10000))))//看看指针是否在合理区间(RAM中)
  11. {
  12. MSR_SP(*(uint32_t *)addr);//sp跳转
  13. Load_SYS = (load_sys)*(uint32_t *)(addr+4);//地址强转为函数,这里转的是(程序区)复位Reset_Handler回调函数
  14. BOOT_Clear();
  15. Load_SYS();//执行复位函数
  16. }
  17. }
  18. void BOOT_Clear(void)//初始化的清理
  19. {
  20. HAL_UART_DeInit(&huart1);
  21. HAL_SPI_DeInit(&g_spi2_handler);
  22. HAL_DeInit();
  23. }

基本上分区搭建的代码差不多就到这里了,后续还有一个细节要修改。

这里改为整片擦除,然后先烧录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 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/942319
推荐阅读
相关标签
  

闽ICP备14008679号