当前位置: 首页 > news >正文

[STM32][Bootloader][教程]STM32 HAL库 Bootloader开发和测试教程

0. 项目移植

对于不想知道其执行过程的朋友来说,可以直接移植,我的板子是STM32F411CER6, 512K M4内核

项目地址:

  1. Bootloader(可以自己写标志位用于自测,项目中这部分代码已经被注释,可以打开自行测试):
  2. 配套测试程序:

0.1 Bootloader移植

  1. 修改刷写大小,我用的Cubeide,我使用第一个扇区当作bootloader,其为16k

    image-20240801185742070

  2. 修改指示灯引脚

  3. 修改扇区开始地址,根据自己的芯片的内部FLASH扇区分配,分配对应的起始地址

    image-20240801190040055

  4. 修改分区开始地址 ,分区参考1.1中的分区表进行分区

    image-20240801190154563

  5. 修改刷写大小

    image-20240801184722840

0.2 应用程序和Bootloaer配合

  1. 应用程序只要正确的将程序刷写到对应的分区开始地址即可,刷写示例程序参照 2.2

  2. 应用程序可以选择性包含以下两个文件,VernonBL_Compatible.h文件用于指示Settings分区中各个变量的枚举值,便于和Bootloader交互,partition_table.h则保存分区表

    image-20240801190611285

  3. 最为重要的一步!!!

    重定义向量表,设置向量表偏移量,0x0000_8000是因为前两个分区占据了32k大小,换成十六进制为0x8000

    image-20240801191344375

  4. 魔术棒修改刷写地址

1. 整体思路

正常的裸机STM32直接开始执行程序,为了能够正式启动应用程序之前能过做更多的功能,比如固件更新等,因此我们需要Bootloader

在正常的STM32启动流程中,其实也有Bootloader的身影存在,即我们在开发的时候所看到的启动文件。

我们先来大致过一下正常的STM32是如何进行启动的。

  1. STM32首先将ROM的0x0800_0000映射成0x0000_0000
  2. STM32获取0x0800_0000的第一地址内的内容(连续取32位),此内容即为MSP堆栈指针,此后单片机便从此地址开始读取数据
  3. STM32获取从0x0800_0000偏移四个地址的内容(0x0800_0004)(因为上面读取了32位),此内容则为PC指针的内容,至此,单片机跳转到0x0800_0004中所代表地址(因为此地址的值给了PC指针),PC指针的地址刚好是函数SystemInit的地址
  4. SystemInit中负责相关时钟初始化等工作。

具体的启动细节这里不在解释,读者可自行查阅其他文章

1.1 分区介绍

分区有好几种分区方式,具体可以参见这个文章https://blog.csdn.net/ShenZhen_zixian/article/details/129064681

要想实现Bootloader启动,我们就应该先给ROM 进行分区,这里我们采用一种全新的方式,这种方式,我们就得采用奇数和偶数更新法,就是奇数版本号更新到Application,偶数版本更新到Application_2,因为我们两个分区的程序中断向量表映射位置是不同的。优点就是有一个版本的备份。

下图对STM32F411CEU6 512K的ROM进行分配的,STM32F1系列可以分配到1k一个扇区

image-20240801172309454

我们采用Bootloader分区+设置参数+双分区的形式,和其他教程不同的是,Application_2也用于运行程序,即:Bootloader只识别BOOT_PARTITION中的内容,用来识别跳转到第Application分区还是Application_2分区,这样做的好处是即使新版本任何错误,我们Bootloader可以自动切换回旧的版本运行。防止造成设备故障。

1.2 启动过程

阅读本章之前请先阅读这个文章,讲的很好很清楚:https://shatang.github.io/2020/08/12/IAP%E5%8D%87%E7%BA%A7-Bootloader%E5%88%B6%E4%BD%9C/

使用Bootloader之后,我们的启动过程为:先启动Bootloader,Bootloader再来启动应用程序。具体在Bootloader内应该:

  1. 判断栈顶指针是否合规
  2. 获得应用程序的PC指针
  3. 设置应用程序MSP堆栈指针
  4. 通过PC地址跳转到应用程序,开始执行应用程序

具体在应用程序内应该:

  1. 重定向向量表–>设置向量表偏移量(注意一定要设置偏移量(VECT_TAB_OFFSET)来完成重定向向量表,而不是设置FLASH_BASE来达到重定向向量表的效果,不然DMA之类的中断无法使用!!!后面会详细讲到)
  2. 检查是否有用户更新,用户更新的时候刷写到Application的另外一个分区
  3. 写Settings中的信息,用于指示Bootloader下一步启动时启动哪个Application

2. 代码编写

2.1 Boot loader编写

2.1.0 CubeMX配置

cubemx里面的这些引脚我相信各位一看就知道我配置了什么,简单地说除了必要的配置,我另外配置了串口、还有一个指示灯(PA0),指示灯使用Systick提供闪烁功能。

image-20240801175057628

要注意的是,STM32F411CEU6 HAL库Systick的中断回调默认官方对其进行了关闭,按照如下方式打开:

如果你的Systick_Handler打开是这样子的,只有一个HAL_IncTick()

image-20240801175544328

那么改成这样,把HAL_SYSTICK_IRQHandler()加进去

//stm32f4xx_it.c
/*** @brief This function handles System tick timer.*/
void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 */HAL_SYSTICK_IRQHandler();/* USER CODE END SysTick_IRQn 1 */
}

这样你main.c中才能写Systick回调

//mainc.c
void HAL_SYSTICK_Callback(void){bootloader_run_notify_led_count ++;if(bootloader_run_notify_led_count >= 600){bootloader_run_notify_led_count = 0;led_blink_on = ~led_blink_on;}
}

2.1.1 跳转函数

在编写跳转函数之前,我们应该先根据手册将扇区定义好,我这里用的STM32F411CEU6,其有512K Flash,因此根据图表,列出定义

image-20240801174331027

//partation_table.h
#define ADDR_FLASH_SECTOR_0         ((uint32_t)0x08000000) 	//sector0 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1         ((uint32_t)0x08004000) 	//sector1 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2         ((uint32_t)0x08008000) 	//sector2 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3         ((uint32_t)0x0800C000) 	//sector3 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4         ((uint32_t)0x08010000) 	//sector4 addr, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5         ((uint32_t)0x08020000) 	//sector5 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6         ((uint32_t)0x08040000) 	//sector6 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7         ((uint32_t)0x08060000) 	//sector7 addr, 128 Kbytes 

编写其跳转函数:我们根据官方的IAP程序中的示例,我们直接拿过来。

//main.c
typedef  void (*pFunction)(void);static pFunction JumpToApplication;
static uint32_t JumpAddress;uint8_t IAP_LoadAPP(uint32_t AppxAddr)
{if (((*(__IO uint32_t*)AppxAddr) & 0x2FFE0000 ) == 0x20000000){/* Jump to user application */JumpAddress = *(__IO uint32_t*) (AppxAddr + 4); //PC指针地址JumpToApplication = (pFunction) JumpAddress;/* Initialize user application's Stack Pointer */__set_MSP(*(__IO uint32_t*) AppxAddr); //设置MSP指针JumpToApplication();return 0;}return -1;
}

2.2.2 日志

打印点东西,表示我进入Bootloader了

void print_boot_message(void)
{printf("\r\n--------- Enter Vernon BootLoader --------\r\n");printf("\r\n");printf("========= flash partition table ==========\r\n");printf("|     name     |   offset   |    size    |\r\n");printf("--------------------------------------\r\n");printf("| bootloader   | 0x%08lx | 0x%08x |\r\n", BOOT_SECTOR_ADDR, BOOT_SECTOR_SIZE);printf("| setting      | 0x%08lx | 0x%08x |\r\n", SETTING_SECTOR_ADDR, SETTING_SECTOR_SIZE);printf("| application1 | 0x%08lx | 0x%08x |\r\n", APP_SECTOR_ADDR, APP_SECTOR_SIZE);printf("| application2 | 0x%08lx | 0x%08x |\r\n", APP2_SECTOR_ADDR, APP2_SECTOR_SIZE);printf("==========================================\r\n");printf("\r\n");
}

2.2.3 Flash刷写函数

下面这几个函数是用来写Settings这个分区里的标志位的,即BOOT_STATEBOOT_PARTITION

//flash_fun.c
int8_t read_settings_boot_state(void)
{return *(__IO uint8_t *)(SETTING_SECTOR_ADDR);
}int8_t write_settings_boot_state(uint8_t state)
{uint32_t sector_index;HAL_StatusTypeDef res;uint32_t read_buf;read_buf = *(__IO uint32_t *)(SETTING_SECTOR_ADDR); // 先把前四个字节数据读出来res = HAL_FLASH_Unlock();if (res != HAL_OK){printf("FLASH_UNLOCK ERROR\r\n");return -1;}sector_index = get_sector_from_addr(SETTING_SECTOR_ADDR);printf("[Bootloader]Erase ADDR 0x%08lx; Sector No.%ld...\r\n", SETTING_SECTOR_ADDR, sector_index);FLASH_Erase_Sector(sector_index, FLASH_VOLTAGE_RANGE_3);printf("[Bootloader]Flash ADDR 0x%08lx ...\r\n", SETTING_SECTOR_ADDR);// 把想要写入的信息写入readbuf中去,最后把32位信息一起写进去read_buf &= 0xffffff00; // 先把第一个字节数据清0,FLASH是高字节存储在高位,低字节存储在低位,所以应该把低字节设置为0read_buf |= state;res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, SETTING_SECTOR_ADDR, read_buf);if (res != HAL_OK){printf("[Bootloader]FLASH_WRITE ERROR\r\n");return -1;}res = HAL_FLASH_Lock();if (res != HAL_OK){printf("[Bootloader]FLASH_LOCK ERROR\r\n");return -1;}return 0;
}int8_t read_settings_boot_partition(void)
{return *(__IO uint8_t *)(SETTING_SECTOR_ADDR + SETTING_BOOT_PARTITION_OFFSET);
}int8_t write_settings_boot_partition(int8_t state)
{int sector_index;HAL_StatusTypeDef res;uint32_t read_buf;read_buf = *(__IO uint32_t *)(SETTING_SECTOR_ADDR); // 先把前四个字节数据读出来res = HAL_FLASH_Unlock();if (res != HAL_OK){printf("FLASH_UNLOCK ERROR\r\n");return -1;}sector_index = get_sector_from_addr(SETTING_SECTOR_ADDR);printf("[Bootloader]Erase ADDR 0x%08lx; Sector No.%d...\r\n", SETTING_SECTOR_ADDR, sector_index);FLASH_Erase_Sector(sector_index, FLASH_VOLTAGE_RANGE_3);printf("[Bootloader]Flash ADDR 0x%08lx ...\r\n", SETTING_SECTOR_ADDR);// 把想要写入的信息写入readbuf中去,最后把32位信息一起写进去read_buf &= 0xffff00ff;   // 先把第二个字节数据清0,FLASH是高字节存储在高位,低字节存储在低位,所以应该把低字节设置为0read_buf |= (state << 8); // 放在第二个字节的位置res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, SETTING_SECTOR_ADDR, read_buf);if (res != HAL_OK){printf("[Bootloader]FLASH_WRITE ERROR\r\n");return -1;}res = HAL_FLASH_Lock();if (res != HAL_OK){printf("[Bootloader]FLASH_LOCK ERROR\r\n");return -1;}return 0;
}

2.2.4 根据标志位启动对应应用程序

BOOT_STATE有三个状态,分别为运行状态,更新应用程序状态和应用程序更新完成状态,应用程序更新由用户编写的应用程序完成,这里只负责根据对应标志位跳转。

//main.c
boot_state = read_settings_boot_state();boot_partition_select = read_settings_boot_partition();switch(boot_state){case RUN_APP_STATE:printf("[Bootloader]Start to run APP[%d] ...\r\n", boot_partition_select);if(boot_partition_select == RUN_APP1_partition){err = IAP_LoadAPP(APP_SECTOR_ADDR);if(err != 0){printf("[Bootloader]Run App error, please flash the new bin...\r\n");}}else if(boot_partition_select == RUN_APP2_partition){err = IAP_LoadAPP(APP2_SECTOR_ADDR);if(err != 0){printf("[Bootloader]Run App error, please flash the new bin...\r\n");}}else{printf("[Bootloader]Can not find the select settings of the partition\r\n");}break;case UPDATE_APP_STATE:printf("[Bootloader]Update APP...\r\n");break;case SUCCESS_UPDATE_APP_STATE:printf("[Bootloader]Success Update APP...Then Reboot System\r\n");err = write_settings_boot_state(RUN_APP_STATE);if(err != 0){printf("FLASH ERROR!\r\n");}__ASM volatile ("cpsid i"); //关闭总中断HAL_NVIC_SystemReset();break;default:printf("[Bootloader]Unknown Update APP...Error Code : %x\r\n", boot_state);}

2.2 用户程序编写-测试

在应用程序中,使用YModem协议进行数据传输,写入新的固件,之后再由应用程序写入Setttings配置信息,重启之后Bootloader即可自动启动新更新的应用。

2.2.1 Flash和Ymodem函数的实现

这两个部分在Cubemx的实例中有,但是其Flash函数个人测试无法使用,还有Ymodem函数有bug,个人对其进行了修改和适配,篇幅限制,就不说改了哪里了,大家直接在Gitee克隆下来用吧,具体代码可以去库里面查看

2.2.1.1 Flash函数

在Bootloader中的函数中再添加

//flash_func.c
// FLash Function
/*** @brief  This function does an erase of all user flash area* @param  StartSector: start of user flash area* @retval 0: user flash area successfully erased*         1: error occurred*/
uint32_t flash_erase(uint32_t StartAdd)
{uint32_t UserStartSector;uint32_t SectorError;FLASH_EraseInitTypeDef pEraseInit;HAL_FLASH_Unlock();/* Get the sector where start the user flash area */UserStartSector = get_sector_from_addr(StartAdd);pEraseInit.TypeErase = TYPEERASE_SECTORS;pEraseInit.Sector = UserStartSector;pEraseInit.NbSectors = 5;pEraseInit.VoltageRange = VOLTAGE_RANGE_3;if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK){/* Error occurred while page erase */return (1);}HAL_FLASH_Lock();return (0);
}/*** @brief  This function writes a data buffer in flash (data are 32-bit aligned).* @note   After writing data buffer, the flash content is checked.* @param  StartAddress: start address for writing data buffer* @param  EndAddress: end address for writing data buffer* @param  Data: pointer on data buffer* @param  DataLength: length of data buffer (unit is 32-bit word)* @retval 0: Data successfully written to Flash memory*         -2: Error occurred while writing data in Flash memory*         -1: Written Data in flash memory is different from expected one*/
int8_t flash_write_continue(uint32_t StartAddress, uint32_t EndAddress,uint32_t *Data, uint32_t DataLength)
{int32_t i = 0;HAL_FLASH_Unlock();for (i = 0; (i < DataLength) && (StartAddress <= (EndAddress - 4)); i++){/* Device voltage range supposed to be [2.7V to 3.6V], the operation willbe done by word */if (HAL_FLASH_Program(TYPEPROGRAM_WORD, StartAddress, *(uint32_t *)(Data + i)) == HAL_OK){/* Check the written value */if (*(uint32_t *)StartAddress != *(uint32_t *)(Data + i)){/* Flash content doesn't match SRAM content */return (-1);}/* Increment FLASH destination address */StartAddress += 4;}else{/* Error occurred while writing data in Flash memory */return (-2);}}HAL_FLASH_Lock();return (0);
}
2.2.1.2 Ymodem函数

关于协议,可以看这篇https://blog.csdn.net/weixin_41865104/article/details/107388202

//ymedom.c
/********************************************************************************* @file    IAP/IAP_Main/Src/ymodem.c* @author  MCD Application Team* @brief   This file provides all the software functions related to the ymodem*          protocol.******************************************************************************* @attention** Copyright (c) 2017 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.********************************************************************************/
/** @addtogroup STM32F4xx_IAP_Main* @{*//* Includes ------------------------------------------------------------------*/
#include "common.h"
#include "ymodem.h"
#include "string.h"
#include "main.h"
#include "usart.h"
#include "VernonBL_Compatible.h"
#include "flash_func.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define CRC16_F /* activate the CRC16 integrity */
#define UartHandle huart1
#define APPLICATION_ADDRESS APP2_SECTOR_ADDR
#define APPLICATION_PARTITION_SIZE APP_SECTOR_SIZE
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
__IO uint32_t flashdestination;
/* @note ATTENTION - please keep this variable 32bit aligned */
uint8_t aPacketData[PACKET_1K_SIZE + PACKET_DATA_INDEX + PACKET_TRAILER_SIZE];
uint8_t aFileName[FILE_NAME_LENGTH];
/* Private function prototypes -----------------------------------------------*/
static HAL_StatusTypeDef ReceivePacket(uint8_t *p_data, uint32_t *p_length, uint32_t timeout);
uint16_t UpdateCRC16(uint16_t crc_in, uint8_t byte);
uint16_t Cal_CRC16(const uint8_t *p_data, uint32_t size);
uint8_t CalcChecksum(const uint8_t *p_data, uint32_t size);/* Private functions ---------------------------------------------------------*//*** @brief  Receive a packet from sender* @param  data* @param  length*     0: end of transmission*     2: abort by sender*    >0: packet length* @param  timeout* @retval HAL_OK: normally return*         HAL_BUSY: abort by user*/
static HAL_StatusTypeDef ReceivePacket(uint8_t *p_data, uint32_t *p_length, uint32_t timeout)
{uint32_t crc;uint32_t packet_size = 0;HAL_StatusTypeDef status;uint8_t char1;*p_length = 0;status = HAL_UART_Receive(&UartHandle, &char1, 1, timeout);if (status == HAL_OK){switch (char1){case SOH:packet_size = PACKET_SIZE;break;case STX:packet_size = PACKET_1K_SIZE;break;case EOT:break;case CA:if ((HAL_UART_Receive(&UartHandle, &char1, 1, timeout) == HAL_OK) && (char1 == CA)){packet_size = 2;}else{status = HAL_ERROR;}break;case ABORT1:case ABORT2:status = HAL_BUSY;break;default:status = HAL_ERROR;break;}*p_data = char1;if (packet_size >= PACKET_SIZE){status = HAL_UART_Receive(&UartHandle, &p_data[PACKET_NUMBER_INDEX], packet_size + PACKET_OVERHEAD_SIZE, timeout);/* Simple packet sanity check */if (status == HAL_OK){if (p_data[PACKET_NUMBER_INDEX] != ((p_data[PACKET_CNUMBER_INDEX]) ^ NEGATIVE_BYTE)){packet_size = 0;status = HAL_ERROR;}else{/* Check packet CRC */crc = p_data[packet_size + PACKET_DATA_INDEX] << 8;crc += p_data[packet_size + PACKET_DATA_INDEX + 1];if (Cal_CRC16(&p_data[PACKET_DATA_INDEX], packet_size) != crc){packet_size = 0;status = HAL_ERROR;}}}else{packet_size = 0;}}}*p_length = packet_size;return status;
}/*** @brief  Update CRC16 for input byte* @param  crc_in input value* @param  input byte* @retval None*/
uint16_t UpdateCRC16(uint16_t crc_in, uint8_t byte)
{uint32_t crc = crc_in;uint32_t in = byte | 0x100;do{crc <<= 1;in <<= 1;if (in & 0x100)++crc;if (crc & 0x10000)crc ^= 0x1021;}while (!(in & 0x10000));return crc & 0xffffu;
}/*** @brief  Cal CRC16 for YModem Packet* @param  data* @param  length* @retval None*/
uint16_t Cal_CRC16(const uint8_t *p_data, uint32_t size)
{uint32_t crc = 0;const uint8_t *dataEnd = p_data + size;while (p_data < dataEnd)crc = UpdateCRC16(crc, *p_data++);crc = UpdateCRC16(crc, 0);crc = UpdateCRC16(crc, 0);return crc & 0xffffu;
}/*** @brief  Calculate Check sum for YModem Packet* @param  p_data Pointer to input data* @param  size length of input data* @retval uint8_t checksum value*/
uint8_t CalcChecksum(const uint8_t *p_data, uint32_t size)
{uint32_t sum = 0;const uint8_t *p_data_end = p_data + size;while (p_data < p_data_end){sum += *p_data++;}return (sum & 0xffu);
}/* Public functions ---------------------------------------------------------*/
/*** @brief  Receive a file using the ymodem protocol with CRC16.* @param  p_size The size of the file.* @retval COM_StatusTypeDef result of reception/programming*/
COM_StatusTypeDef Ymodem_Receive(uint32_t *p_size)
{uint32_t i, packet_length, session_done = 0, file_done, errors = 0, session_begin = 0;// uint32_t flashdestination;uint32_t ramsource, filesize, packets_received;uint8_t *file_ptr;uint8_t file_size[FILE_SIZE_LENGTH], tmp;COM_StatusTypeDef result = COM_OK;*p_size = 0; //it may be a random value if you not assigned value in out of the function/* Initialize flashdestination variable */flashdestination = APP2_SECTOR_ADDR;while ((session_done == 0) && (result == COM_OK)){packets_received = 0;file_done = 0;while ((file_done == 0) && (result == COM_OK)){switch (ReceivePacket(aPacketData, &packet_length, DOWNLOAD_TIMEOUT)){case HAL_OK:errors = 0;switch (packet_length){case 2:/* Abort by sender */Serial_PutByte(ACK);result = COM_ABORT;break;case 0:/* End of transmission */Serial_PutByte(ACK);file_done = 1;break;default:/* Normal packet */if (aPacketData[PACKET_NUMBER_INDEX] != (uint8_t)packets_received){Serial_PutByte(NAK);}else{if (packets_received == 0){/* File name packet */if (aPacketData[PACKET_DATA_INDEX] != 0){/* File name extraction */i = 0;file_ptr = aPacketData + PACKET_DATA_INDEX;while ((*file_ptr != 0) && (i < FILE_NAME_LENGTH)){aFileName[i++] = *file_ptr++;}/* File size extraction */aFileName[i++] = '\0';i = 0;file_ptr++;while ((*file_ptr != ' ') && (i < FILE_SIZE_LENGTH)){file_size[i++] = *file_ptr++;}file_size[i++] = '\0';Str2Int(file_size, &filesize);/* Test the size of the image to be sent *//* Image size is greater than Flash size */if (*p_size > (APPLICATION_PARTITION_SIZE + 1)){/* End session */tmp = CA;HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);result = COM_LIMIT;}/* erase user application area */flash_erase(APPLICATION_ADDRESS);*p_size = filesize;Serial_PutByte(ACK);Serial_PutByte(CRC16);}/* File header packet is empty, end session */else{Serial_PutByte(ACK);file_done = 1;session_done = 1;break;}}else /* Data packet */{ramsource = (uint32_t)&aPacketData[PACKET_DATA_INDEX];/* Write received data in Flash */				if (flash_write_continue(flashdestination, APPLICATION_ADDRESS + APPLICATION_PARTITION_SIZE,(uint32_t*) ramsource, packet_length/4) == 0){//data transforming led blinkint value = 3;while(value --){HAL_Delay(50);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);HAL_Delay(50);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);}flashdestination += packet_length;Serial_PutByte(ACK);}else /* An error occurred while writing to Flash memory */{/* End session */Serial_PutByte(CA);Serial_PutByte(CA);result = COM_DATA;}}packets_received++;session_begin = 1;}break;}break;case HAL_BUSY: /* Abort actually */Serial_PutByte(CA);Serial_PutByte(CA);result = COM_ABORT;break;default:if (session_begin > 0){errors++;}if (errors > MAX_ERRORS){/* Abort communication */Serial_PutByte(CA);Serial_PutByte(CA);}else{Serial_PutByte(CRC16); /* Ask for a packet */}break;}}}return result;
}/*******************(C)COPYRIGHT 2016 STMicroelectronics *****END OF FILE****/

最后外部调用即可

image-20240801184722840

2.3 重定义向量表(重点看,有坑)

我们可以知道,我们的应用程序是写在了0x0800_8000的,那我们程序从这里开始不就可以了吗?事实也确实是这样,正常情况下应用程序应该从0x0800_0000开始,我们看下图

image-20240801191708495

这里定义了FLASH_BASE,也确实是这样,正常情况从0x0800_0000开始,所以我们把这个变量改成0x0800_8000不就可以了吗?中断向量表也在从这个地址的开始写着。这不是完美吗?有些博主也确实是这么做的。能运行吗?能,如果不涉及DMA的话(不是说只有DMA,只是因为我写应用程序的时候用到了DMA,它出现了问题)。

所以我们坚决不能改这个!

所以我们应该改的是偏移值:

image-20240801192438216

0x0000_8000是因为前两个分区占据了32k大小,换成十六进制为0x8000

其实源码中Note已经写的很清楚了,只不过我们不太注意哈哈。

这个小插曲我在学习这部分的时候所有博主都没说过,所以难免会出现这种问题,正常现象, 现在解决以免以后在工作中出现~

2.4 修改刷写地址

魔术棒里面修改地址和大小即可

image-20240801194649153

2.5 刷写测试

这里使用软件Tera Term 5,因为其支持1k的Ymodem,刷写速度较快

image-20240801184953131

插入开发板,打开串口,可以发现Bootloader启动了,当其出现C字样的时候,表示其可以进行刷写。

选择bin文件,使用Ymodem发送

image-20240801185228203

等待其刷写完成就可以了

image-20240801185259519

应用程序中,设置的烧写在Application_2这个分区里,所以我们可以通过keli看0x0804_0000这个地址的内容,如果有内容则刷写成功。

image-20240801185455402

相关文章:

[STM32][Bootloader][教程]STM32 HAL库 Bootloader开发和测试教程

0. 项目移植 对于不想知道其执行过程的朋友来说&#xff0c;可以直接移植&#xff0c;我的板子是STM32F411CER6, 512K M4内核 项目地址&#xff1a; Bootloader&#xff08;可以自己写标志位用于自测&#xff0c;项目中这部分代码已经被注释&#xff0c;可以打开自行测试&…...

如何手写一个SpringBoot框架

你好&#xff0c;我是柳岸花开。 在这篇文章中&#xff0c;我们将手写模拟SpringBoot的核心流程&#xff0c;让大家能够以一种简单的方式了解SpringBoot的大概工作原理。 项目结构 我们创建一个工程&#xff0c;包含两个模块&#xff1a; springboot模块&#xff0c;表示Spring…...

vite解决前端跨域步骤

Vite 解决跨域问题的原理主要是通过其内置的开发服务器功能实现的&#xff0c;具体来说&#xff0c;是通过 HTTP 代理&#xff08;HTTP Proxy&#xff09;机制。在开发环境中&#xff0c;Vite 服务器可以配置为一个代理服务器&#xff0c;将前端应用发出的请求转发到实际的后端…...

同步交互与异步交互:深入解析与选择

同步交互与异步交互&#xff1a;深入解析与选择 1、同步交互2、异步交互3、选择策略 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在软件开发的世界里&#xff0c;交互方式主要分为两大类&#xff1a;同步与异步。下面是对这两种方式的解…...

Day1

首先&#xff0c;大概学习了一下用anaconda去创建一个环境&#xff08;因为Django是有python版本的要求&#xff09;&#xff0c;然后学着去切换环境。 创建环境&#xff1a;conda create -n django_study python3.10 激活环境&#xff1a;conda activate django_study 删除环…...

Introduction to Data Analysis with PySpark

1.DataFrame and RDDs 2.Spark Architecture 3. Data Formats and Data Sources 倘若您觉得我写的好&#xff0c;那么请您动动你的小手粉一下我&#xff0c;你的小小鼓励会带来更大的动力。Thanks....

基于双PI控制器结构的六步逆变器供电无刷直流电机调速simulink仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 无刷直流电机&#xff08;BLDCM&#xff09;原理 4.2 六步换相逆变器 4.3 双PI控制器设计 5.完整工程文件 1.课题概述 基于双PI控制器结构的六步逆变器供电无刷直流电机调速simulink仿真。双PI控制…...

双向链表的基本操作

#include<iostream> #include<cmath> #include<cstring> using namespace std; typedef long long ll; typedef struct line {int data;struct line *pre;//前指针struct line *next;//后指针 }line,*a; line* init_line(line*head) {cout<<"请输…...

modbus tcp和modbusRTU的区别是什么?

Modbus是一种应用广泛的通信协议&#xff0c;主要用于工业自动化和过程控制系统。Modbus有多种变体&#xff0c;其中Modbus TCP和Modbus RTU是最常见的两种。以下是它们之间的主要区别&#xff1a; 1. 基本定义 Modbus RTU (Remote Terminal Unit): 是基于串行通信的协议&am…...

web小游戏开发:拼图(四)对调和移动拼图玩法的实现

web小游戏开发:拼图(四)对调和移动拼图玩法的实现 对调方式对调模式实现移动方式移动的实现小结对调方式 在完成了原始拼图玩法后,剩下两个玩法其实相对就变得简单的多了。 对调模式,简单来说,就是选中两个图块,然后位置对调一下。 那么,我们来整理一下,看看需要哪…...

前端:Vue学习 - 智慧商城项目

前端&#xff1a;Vue学习 - 智慧商城项目 1. vue组件库 > vant-ui2. postcss插件 > vw 适配3. 路由配置4. 登录页面静态布局4.1 封装axios实例访问验证码接口4.2 vant 组件 > 轻提示4.3 短信验证倒计时4.4 登录功能4.5 响应拦截器 > 统一处理错误4.6 登录权证信息存…...

KVM调整虚拟机与CPU铆钉(绑定)关系

虚拟机铆钉CPU 把虚拟机的vCPU绑定在物理CPU上&#xff0c;即VCPU只在绑定的物理CPU上调度&#xff0c;在特定场景下达到提升虚拟机性能的目的。比如在NUMAQ系统中&#xff0c;把vCPU绑定在同一个NUMA节点上&#xff0c;可以避免CPU跨节点访问内存&#xff0c;避免影响虚拟机运…...

华火电焰灶:烹饪新宠,温暖与美味的完美融合

在众多厨房电器中&#xff0c;华火电焰灶以其独特的魅力和卓越的性能脱颖而出&#xff0c;成为了众多家庭的烹饪新宠。今天&#xff0c;就让我们一同走进华火电焰灶的精彩世界&#xff0c;探索它的非凡之处。 华火电焰灶&#xff0c;首先吸引人的便是其创新的等离子电生明火技术…...

理想发周榜,不是新能源市场的原罪

余华在他的小说《在细雨中呼喊》曾写过这么一段话&#xff1a; “仓廪实而知礼节&#xff0c;衣食足而知荣辱”&#xff0c;在物质需求得到满足以前&#xff0c;精神文明的发展难免会有所滞后。所以&#xff0c;贫穷&#xff0c;不是原罪。 同样的&#xff0c;在如今的新能源…...

AHK是让任何软件都支持 Shift + 鼠标滚轮 实现界面水平滚动

目录 基本介绍 详细特点 图解安装 下载失败&#xff1f;缓慢&#xff1f; 创建并运行脚本代码&#x1f603; 新建空 xxx.ahk文件 vscode/记事本等编辑工具打开 复制并粘贴简易脚本 运行 其他问题 问题一&#xff1a;弹出无法执行此脚本 关闭脚本 基本介绍 AutoHot…...

如何在C语言中实现求解超级丑数

超级丑数是一个正整数&#xff0c;并且它的质因数只包含在给定的质数列表中。超级丑数的定义类似于丑数&#xff0c;但可以根据特定需求改变质因数的范围。下面是如何在C语言中实现求解超级丑数的代码。 我们使用最小堆&#xff08;优先队列&#xff09;来高效地生成超级丑数序…...

secExample靶场之java反序列化漏洞复现

我是使用kali虚拟机搭建的靶场&#xff0c;具体搭建步骤可以看我之前的博客内容。 访问靶机地址&#xff0c;打开java反序列化的 点进去后是如图的页面&#xff0c;随便输入一信息。 可以看到敏感信息直接显示在了页面上。 访问192.168.189.141:8080/cors1&#xff0c;可以看到…...

解决升级Linux内核后,open files设置无效的问题。

问题过程 操作系统是OpenEuler 20.03&#xff0c;内核由4.19.90-2112.8.0.0131.oe1.aarch64升级到kernel-4.19.90-2401.1.0.0233.oe1.aarch64后&#xff0c;重启系统后&#xff0c;重新开起来运行OceanBase就运行不起来了&#xff0c;提示open files must not be less than 20…...

关于防范勒索病毒Play新变种的风险提示

近日&#xff0c;工业信息化部网络安全威胁和漏洞信息共享平台监测发现针对 Linux的勒索病毒Play新变种&#xff0c;攻击对象主要为VMware ESXi 虚拟化环境&#xff0c;攻击目标包括制造、建筑业、IT、金融和房地产等行业。 Play勒索病毒又名 Balloonfly和PlayCrypt&#xff0…...

一款.NET开源、跨平台的DASH/HLS/MSS下载工具

前言 今天大姚给大家分享一款.NET开源&#xff08;MIT License&#xff09;、免费、跨平台的DASH/HLS/MSS下载工具&#xff0c;并且支持点播和直播&#xff08;DASH/HLS&#xff09;的内容下载&#xff1a;N_m3u8DL-RE。 网络流媒体传输协议介绍 DASH DASH是一种基于HTTP的…...

MATLAB学习日志DAY21

结构体&#xff08;2&#xff09; 如果将生成逗号分隔列表的表达式括在方括号中&#xff0c;MATLAB 会将该列表中的每一项都存储在数组中。示例中&#xff0c;MATLAB 创建一个数值行向量&#xff0c;该向量包含结构体数组 S 的每个元素的 score 字段&#xff1a; scores [S.…...

Spingboot请求tcp 方式

import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel;/*** 请求tcp接口** author Mr丶s* date 2024/7/1…...

leetcode刷题日记-括号生成

题目描述 题目解析 回溯的题目&#xff0c;不过这个两个if我就感觉有点难以理解了&#xff0c;不过仔细的思考了一下&#xff0c;确实考虑到了每个位置的情况&#xff0c;特别是针对右边括号 题目代码 class Solution:def generateParenthesis(self, n: int) -> List[str…...

小程序按钮分享

使用button设置&#xff1a; open-type"share"&#xff1a;来微信分享&#xff1b; html&#xff1a; <button open-type"share"></button>...

多模态多智能体,在实现系统2(深思熟虑)方面的探索

多模态和多智能体&#xff0c;在系统2&#xff08;深思熟虑&#xff09;方面的探索 提出背景理性的定义为什么理性定义是四大基本原则&#xff0c;而不是其他数量&#xff0c;又为何是这四个&#xff0c;而不是其他&#xff1f;理性 不等于 推理 通过多模态多智能体系统增强理性…...

【CAN通讯系列8】如何准确接收数据?

在 【CAN通讯系列7】波特率是什么&#xff1f;已经介绍了CAN位时间和采样点等概念&#xff0c;每1位由同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2)四个段组成&#xff0c;这个也成为位时序&#xff0c;采样点位置处于PBS1和PBS2的交界处&#xff0c;如…...

RabbitMQ知识总结(基本概念)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 基本概念 Producer&#xff1a; 消息的生产者&#xff0c;是一个向…...

Prel语言入门学习:一篇全面的指南

引言 在编程语言的海洋中&#xff0c;Prel是一个较少人知的新星。作为一种专为数据处理和分析设计的语言&#xff0c;Prel结合了现代编程语言的简洁性与功能性&#xff0c;提供了一种独特的解决方案&#xff0c;尤其适用于数据科学家和分析师。本文将详细介绍Prel语言的基础&am…...

在云服务器上自动化部署项目,jenkins和gitee

▮全文概述 在编写项目时&#xff0c;很头大的事情就是需要自己手动的上传jar包到服务器上启动。如果出现一点bug&#xff0c;就要重头上传和启动。这是一件很烦的事情&#xff0c;所以&#xff0c;可以使用jenkins和gitee实现项目的自动部署 ▮全流程 在本地提交代码到gitee …...

python 参数输入

在 Python 中&#xff0c;参数输入通常有多种方式&#xff0c;这取决于你要从何处获取参数。以下是几种常见的方法&#xff1a; 1. 命令行参数 使用 sys.argv 获取命令行参数&#xff0c;或者使用 argparse 模块进行更复杂的参数解析。 示例 1: 使用 sys.argv import sys# …...

广州做网站怎么样/seo成功案例分析

STL 故名思意标准模板库&#xff08;Standard Template Library&#xff09; #include <bits/stdc.h> //万能头文件常见使用方式&#xff1a; pair pair 是 一种模版类型。每个pair 可以存储两个值。这两种值无限制。也可以将自己写的struct的对象放进去。&#xff08;…...

制作个人免费网站展示设计/武汉网优化seo公司

首先 下载并安装好网易MuMu模拟器: https://mumu.163.com/mac/index.html 运行网易MuMu,打开后在首页打开设置->开发者选项->打开USB调试模式 如果已经打包好的apk文件&#xff0c;则直接将apk文件拖动到模拟器窗口&#xff0c;apk会被自动安装 ADB connect 这里需要说明…...

怎么样做游戏网站/搜索图片

💥 项目专栏:【机器学习项目实战案例目录】项目详解 + 完整源码 文章目录 一、支持向量机(SVC)实现乳腺癌肿瘤预测二、数据集介绍三、导包四、加载数据集五、数据处理5.1 数值型特征5.2 离散型特征六、配置流水线七、获取训练数据、测试集八、定义模型九、模型训练十、训练…...

网站推广的主要方法/seo网站seo

楼主工作的单位是一家欧洲公司&#xff0c;主营奢侈品的生产和销售&#xff0c;我们有一个PLM&#xff08;产品生命周期管理系统&#xff09;&#xff0c;用来管理产品的主数据&#xff0c;例如对部品及物料从设计到生产&#xff0c;以及BOM等主数据的管理&#xff0c;我们采购…...

怎么做幼儿园的网站/百度云盘登录

今天项目中需要自定义图片上传的保存路径&#xff0c;并且不需要按照日期自动创建存储文件夹和文件名&#xff0c;我的ueditor版本是1.3.6。下面记录一下我配置成功的方法&#xff0c;如果有什么不对的地方欢迎指出&#xff0c;共同学习&#xff1a; 1&#xff1a;我在本地项目…...

自己做h5网站/全国疫情高峰感染高峰

与Non-mutating Algorithms相比&#xff0c;变易算法能修改容器元素数据&#xff0c;可进行序列数据的复制、交换、替换、填充、移除、旋转、随机抖动、分割。还是参考叶至军的那本书以及网站Cplusplus.com copy 元素复制。该函数用于容器间元素拷贝&#xff0c;将迭代器区间[…...