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

news2024/11/13 9:54:08

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是高字节存储在高位,低字节存储在低位,所以应该把低字节设置为0
    read_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是高字节存储在高位,低字节存储在低位,所以应该把低字节设置为0
    read_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 will
           be 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 blink
								int 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1970005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国智能物流头部集成商的“江湖地位”及其“独门秘笈”

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 物流仓储自动化领域犹如一片充满机遇与挑战的江湖&#xff0c;各大企业群雄逐鹿&#xff0c;各展所长。这些企业&#xff0c;如同金庸小说中的武林高手&#xff0c;不仅拥有深厚的内功…

后台列表复制功能

html&#xff1a; <el-button click"copy(row)">复制</el-button><!-- 复制弹框 --> <el-dialog :close-on-click-modal"false" title"复制" width"600px" :visible.sync"copyVisible" append-to-bod…

博世战胜三星,577亿最大笔收购,豪赌杀入自动化新业务

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 德国工业巨头博世再次震惊业界&#xff01;近日&#xff0c;这家总部位于斯图加特的科技公司以74亿欧元&#xff08;约合人民币577亿&#xff09…

自动化测试常用函数(Java方向)

目录 一、元素的定位 1.1 cssSelector 1.2 xpath 1.2.1 获取HTML页面所有的节点 1.2.2 获取HTML页面指定的节点 1.2.3 获取⼀个节点中的直接子节点 1.2.4 获取⼀个节点的父节点 1.2.5 实现节点属性的匹配 1.2.6 使用指定索引的方式获取对应的节点内容 二、操作测试对…

麒麟系统如何删除光盘刻录痕迹??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

一文解读ReentrantLock

本期说一下ReentrantLock的相关面试题。 Lock接口 是JDK层面锁 悲观锁 可重入锁。&#xff08;可重入锁&#xff08;Reentrant Lock&#xff09;是一种支持线程重复获取锁的锁机制。当一个线程已经获得了可重入锁后&#xff0c;它可以再次请求该锁而不会被阻塞&#xff0c;这就…

第G6周:CycleGAN实战

本文为365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊 可参考论文&#xff1a;《Unpaired Image-to-Image Translation》 1、CycleGAN 能做什么&#xff1f; CycleGAN的一个重要应用领域是Domain Adaptation&#xff08;域迁移&#xff1a;可以通俗的理解为…

Stable Diffusion 使用详解(5)---- 光影效果与场景融入

目录 背景 底模选取 提示词 ControlNet openpose illumination 效果 背景 有一家服装品牌店&#xff0c;需要绘制一款模特穿着某个英文LOG的漂亮服装&#xff0c;这是一种很常见UI作画需求&#xff0c;这类需求实际上可以透过选取正确的底模 controlNet 进行完美的实现…

vite vue3 Webstorm multiple export width the same name “default“

系统格式不一样&#xff0c;导致代码文件格式冲突导致的&#xff0c;解决方法找到对应的文件&#xff0c;将文件类型切换成LF。

软件测试--兼容性测试

兼容性测试综述 软件兼容性测试是指检查软件之间是否能够正确的交互和共享信息 交互可以同时运行于同一台计算机上的两个程序之间&#xff0c;甚至在相隔几千公里通过因特网连接的不同计算机上的两个程序之间进行。还可以离线介质如导出到介质然后导入到其他计算机的其他软件…

2024年最新护眼台灯攻略:孩视宝、飞利浦和书客护眼台灯哪个好

在当今数字时代&#xff0c;无论是工作还是学习&#xff0c;长时间面对电子屏幕已成为日常。这对眼睛健康提出了挑战&#xff0c;尤其是对于成长中的孩子&#xff0c;正确的照明环境对保护视力至关重要。因此&#xff0c;选择一款高质量的护眼台灯成为了许多家庭的刚需。 如今…

OPenCV高级编程——OPenCV形态学之腐蚀、膨胀、开运算、闭运算、形态学梯度等详解

目录 引言 形态学基础 结构元素&#xff08;Structuring Element&#xff09; 基本形态学操作 腐蚀&#xff08;Erosion&#xff09; 膨胀&#xff08;Dilation&#xff09; 开运算&#xff08;Opening&#xff09; 闭运算&#xff08;Closing&#xff09; 高级形态学…

读零信任网络:在不可信网络中构建安全系统06授权

1. 授权 1.1. 授权决策不容忽视&#xff0c;所有访问请求都必须被授权 1.2. 数据存储系统和其他各支撑子系统是授权的基石 1.2.1. 子系统提供访问控制的权威数据源和评估依据&#xff0c;直接影响授权决策 1.2.2. 谨慎区分各子系统的职责和能力&#xff0c;需要将其严格隔离…

高数经典反例记录(持续更新)

这篇博客总结了一些易混淆的概念以及经典反例&#xff0c;全部看完会有收获的&#xff0c;后期可能会继续补充&#xff01; 1.概念模糊 2.极限存在/不存在问题

豹5全新价格引爆市场,技术平权开启SUV新篇章

关注汽车市场的小伙伴&#xff0c;想必都知道最近方程豹品牌的豹5车型&#xff0c;打出了23.98万元至30.28万元的全新价格区间&#xff0c;重新定义了SUV市场的竞争格局。 方程豹的这一举动&#xff0c;立刻引发了市场的热烈讨论&#xff1a;“豹5现在值得入手吗&#xff1f;”…

科普文: jdk 1.7和 jdk 1.8 中ConcurrentHashMap 原理浅析

1. 前言 为什么要使用 ConcurrentHashMap 主要基于两个原因&#xff1a; 在并发编程中使用 HashMap 可能造成死循环(jdk1.7,jdk1.8 中会造成数据丢失)HashTable 效率非常低下 2. ConcurrentHashMap 结构 jdk 1.7 和 jdk 1.8 中&#xff0c;ConcurrentHashMap 的结构有着很…

软件产品测试报告包括哪些内容?专业软件测试服务供应商推荐

在当今快速发展的软件行业中&#xff0c;软件产品测试报告的重要性愈加凸显。卓码软件测评作为专业的软件测试服务供应商&#xff0c;深知一份高质量的测试报告对于开发团队、管理层以及客户的重要性。 软件产品测试报告是对软件产品在测试过程中所表现出来的各项指标和特性的…

【架构师进阶必备】Spring - 运行时以四种方式动态注册 bean

Spring — 运行时以四种方式动态注册 bean 1. 概述 在本教程中&#xff0c;我们将学习“使用 spring 动态注册 bean”或“在运行时动态将 bean 添加到 spring-context”。在Spring 应用程序中加载和删除 bean 时&#xff0c;无需在运行时重新启动应用程序即可完成此操作。 如…

先用先发!小样本故障诊断新思路!Transformer-SVM组合模型多特征分类预测/故障诊断(Matlab)

先用先发&#xff01;小样本故障诊断新思路&#xff01;Transformer-SVM组合模型多特征分类预测/故障诊断&#xff08;Matlab&#xff09; 目录 先用先发&#xff01;小样本故障诊断新思路&#xff01;Transformer-SVM组合模型多特征分类预测/故障诊断&#xff08;Matlab&#…

【RabbitMQ】路由模式(Routing)

一、基本概念 生产者&#xff08;Producer&#xff09;&#xff1a;发送消息到交换机的程序。在发送消息时&#xff0c;需要指定一个路由键。交换机&#xff08;Exchange&#xff09;&#xff1a;接收生产者发送的消息&#xff0c;并根据路由键将消息路由到与之匹配的队列。在…