打印
[STM32F4]

stm32f407vet6配置U盘,拖拽bin实现固件升级

[复制链接]
140|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-11-12 15:16 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、工程说明
使用stm32f407vet6芯片内置的ram配置成U盘,实现拖拽bin文件升级固件的功能。

该芯片具有512k的flash和192k的ram。由于板子未设计外置flash,内部flash扇区较大。如果你需要修改其中的1B的数据,需要把这128k的数据备份到ram,再修改ram里的数据,然后把128k的扇区擦除,再把ram里的数据重新写入flash里。这就是大扇区对U盘的影响,反正都要占用128k的ram,那我为什么不直接用ram做u盘?

(一)ram做u盘的优缺点
优点
写入方便,无需擦除,可直接修改。
写入速度比flash快。
缺点
配置成的U盘掉电数据丢失,如需保存需手动备份到flash。
占用ram空间,而ram本身容量不大。
二、硬件记录
升级固件按键:PB10
使用串口2
三、配置步骤
(一)时钟配置
选择High Speed Clock (HSE)为Crystal/Ceramic Resonator

er


(二)配置时钟树
按照要求配置时钟树,使各时钟满足系统需求,确保usb能获得正确的48mhz时钟信号。



(三)开启全速usb设备
设置Mode为Device_Only



(四)配置中间件,选择大容量设备
设置Class For FS IP为Mass Storage Class。



(五)修改最小堆size
没改的话,会出现问题(在问题记录里有写)



(六)使用FATFS
保持默认设置,不用修改什么。



(七)配置USART2,启用DMA并开启串口中断
DMA、中断如果没使用,可以不配置。根据自己的需要修改



四、函数修改
usbd_storage_if.c文件
存储设备参数修改
#define STORAGE_LUN_NBR 1:表示只有一个逻辑单元(1个u盘)。
#define STORAGE_BLK_NBR 0x100:存储设备总块数为256个,每个块可单独读写。
#define STORAGE_BLK_SIZ 0x200:每个块大小为512字节,通常是文件系统常见块大小(如FAT文件系统),256 * 512 = 128k(实际可用空间小于128k,因文件系统会占用部分空间)。
#define STORAGE_LUN_NBR                  1             // 逻辑单元编号(LUN,Logical Unit Number),表示存储设备的逻辑单元数量
                                                //这里设为1,表示只有一个逻辑单元(1个u盘)
#define STORAGE_BLK_NBR                  0x100         // 存储设备的总块数,表示总共0x100(256)个块。每个块都可以单独读写
#define STORAGE_BLK_SIZ                  0x200         // 每个块的大小,设置为0x200(512字节),这通常是文件系统中常见的块大小(例如FAT文件系统)
//256*512 = 128k(代表这个u盘大128k,文件系统会占用部分,所以实际可以使用的不足128k)


修改读写函数
修改STORAGE的读写函数,如果使用的是ram就修改成ram的读写函数,如果使用的是flash就修改成flash的读写函数。
U盘空间
    uint8_t disk_buffer[DISK_SIZE]; // U盘的空间


我这里写的ram的读写函数

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
        /* 确定要读取的起始地址 */
        uint32_t address = blk_addr * STORAGE_BLK_SIZ;
        uint32_t data_length = blk_len * STORAGE_BLK_SIZ;

       
        /* 检查缓冲区和地址的有效性 */
        if ((address + data_length) > DISK_SIZE) // 如果缓冲区无效或者地址超出范围
        {
                error_printf("%s ,地址超出范围\n" , __func__);
                return USBD_FAIL;  
        }
       
        //从 ram 读取数据复制到缓冲区
        memcpy(buf, disk_buffer + address, data_length);
       
        return (USBD_OK);
  /* USER CODE END 6 */
}


/**
  * @brief  写入数据到 Flash 的指定扇区。
  * @param  lun: 逻辑单元号 (LUN)。
  * @param  buf: 要写入的数据缓冲区。
  * @param  blk_addr: 起始块地址 (block address)。
  * @param  blk_len: 要写入的块数量 (block length)。
  * @retval USBD_OK 表示操作成功, 否则返回 USBD_FAIL。
  */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
    uint32_t address = blk_addr * STORAGE_BLK_SIZ;
    uint32_t data_length = blk_len * STORAGE_BLK_SIZ;
       
        static uint32_t temp = 0;
        temp += data_length;
       
        /* 检查缓冲区和地址的有效性 */
        if ((address + data_length) > DISK_SIZE) // 如果缓冲区无效或者地址超出范围
        {
                error_printf("%s ,地址超出范围\n" , __func__);
                return USBD_FAIL;  
        }
       
    // 写入数据
        memcpy(disk_buffer + address, buf,  data_length);

    return USBD_OK;  // 返回成功
}



user_diskio.c文件里
修改读写函数

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file    user_diskio.c
* @brief   This file includes a diskio driver skeleton to be completed by the user.
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2024 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under Ultimate Liberty license
* SLA0044, the "License"; You may not use this file except in compliance with
* the License. You may obtain a copy of the License at:
*                             www.st.com/SLA0044
*
******************************************************************************
*/
/* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
* Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
* To be suppressed in the future.
* Kept to ensure backward compatibility with previous CubeMx versions when
* migrating projects.
* User code previously added there should be copied in the new user sections before
* the section contents can be deleted.
*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
#include "usart.h"

#include "usbd_storage_if.h"
#include "../../User/flash/flash.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize(BYTE pdrv);
DSTATUS USER_status(BYTE pdrv);
DRESULT USER_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
DRESULT USER_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef USER_Driver =
        {
                USER_initialize,
                USER_status,
                USER_read,
#if _USE_WRITE
                USER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
                USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
* @brief  Initializes a Drive
* @param  pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS USER_initialize(
        BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
        /* USER CODE BEGIN INIT */
        if (pdrv != 0)
        {
                error_printf("%s ,u盘驱动号错误\n", __func__); // 只有一个u盘时,pdrv为0
                return RES_PARERR;                                                                // 返回参数错误
        }

        // 清空U盘
        // memset(disk_buffer, 0, sizeof(disk_buffer));
        Stat = 0x00; // 正常
        return Stat;
        /* USER CODE END INIT */
}

/**
* @brief  Gets Disk Status
* @param  pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS USER_status(
        BYTE pdrv /* Physical drive number to identify the drive */
)
{
        /* USER CODE BEGIN STATUS */
        // Stat = 0x00;
        return Stat;
        /* USER CODE END STATUS */
}

/**
* @brief  Reads Sector(s)
* @param  pdrv: Physical drive number (0..)
* @param  *buff: Data buffer to store read data
* @param  sector: Sector address (LBA)
* @param  count: Number of sectors to read (1..128)
* @retval DRESULT: Operation result
*/
DRESULT USER_read(
        BYTE pdrv,          /* Physical drive nmuber to identify the drive */
        BYTE *buff,          /* Data buffer to store read data */
        DWORD sector, /* Sector address in LBA */
        UINT count          /* Number of sectors to read */
)
{
        /* USER CODE BEGIN READ */
        // 检查驱动号是否正确
        if (pdrv != 0)
        {
                error_printf("%s ,u盘驱动号错误\n", __func__); // 只有一个u盘时,pdrv为0
                return RES_PARERR;                                                                // 返回参数错误
        }

        // 检查sector和count是否合法
        if (count == 0 || sector >= STORAGE_BLK_NBR)
        {
                error_printf("%s ,无效的sector或count\n", __func__);
                return RES_PARERR;
        }

        uint32_t address = sector * STORAGE_BLK_SIZ;
        uint32_t data_length = count * STORAGE_BLK_SIZ;

        /* 检查缓冲区和地址的有效性 */
        if ((address + data_length) > DISK_SIZE) // 如果缓冲区无效或者地址超出范围
        {
                error_printf("%s ,地址超出范围\n", __func__);
                return RES_PARERR;
        }

        // 写入数据
        memcpy(buff, disk_buffer + address, data_length);
        debug_printf(PRINT_USB_WR, "++[%s]++ : sector = %x , count = %d\r\n", __func__, sector, count);

        return RES_OK;
        /* USER CODE END READ */
}

/**
* @brief  Writes Sector(s)
* @param  pdrv: Physical drive number (0..)
* @param  *buff: Data to be written
* @param  sector: Sector address (LBA)
* @param  count: Number of sectors to write (1..128)
* @retval DRESULT: Operation result
*/
#if _USE_WRITE == 1
DRESULT USER_write(
        BYTE pdrv,                  /* Physical drive nmuber to identify the drive */
        const BYTE *buff, /* Data to be written */
        DWORD sector,          /* Sector address in LBA */
        UINT count                  /* Number of sectors to write */
)
{
        /* USER CODE BEGIN WRITE */
        /* USER CODE HERE */
        // 检查驱动号是否正确
        if (pdrv != 0)
        {
                error_printf("%s ,u盘驱动号错误\n", __func__); // 只有一个u盘时,pdrv为0
                return RES_PARERR;                                                                // 返回参数错误
        }

        // 检查sector和count是否合法
        if (count == 0 || sector >= STORAGE_BLK_NBR)
        {
                error_printf("%s ,无效的sector或count\n", __func__);
                return RES_PARERR;
        }

        uint32_t address = sector * STORAGE_BLK_SIZ;
        uint32_t data_length = count * STORAGE_BLK_SIZ;

        /* 检查缓冲区和地址的有效性 */
        if ((address + data_length) > DISK_SIZE) // 如果缓冲区无效或者地址超出范围
        {
                error_printf("%s ,地址超出范围\n", __func__);
                return RES_PARERR;
        }
        debug_printf(PRINT_USB_WR, "++[%s]++ : sector = %x , count = %d\r\n", __func__, sector, count);
        //  写入数据
        memcpy(disk_buffer + address, buff, data_length);

        return RES_OK;
        /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
* @brief  I/O control operation
* @param  pdrv: Physical drive number (0..)
* @param  cmd: Control code
* @param  *buff: Buffer to send/receive control data
* @retval DRESULT: Operation result
*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl(
        BYTE pdrv, /* Physical drive nmuber (0..) */
        BYTE cmd,  /* Control code */
        void *buff /* Buffer to send/receive control data */
)
{
        /* USER CODE BEGIN IOCTL */
        DRESULT res = RES_ERROR;

        if (pdrv != 0)
                return RES_PARERR; // 只支持驱动器0

        switch (cmd)
        {
        case CTRL_SYNC:
                // 这里检查是否完成所有挂起的写入操作
                // 这里可以直接返回RES_OK,因为使用的是ram做的U盘
                res = RES_OK;
                break;

        case GET_SECTOR_COUNT:
                // 设备上的总扇区数
                *(uint32_t *)buff = STORAGE_BLK_NBR;
                res = RES_OK;
                break;

        case GET_SECTOR_SIZE:
                // 每个扇区的大小
                *(uint16_t *)buff = STORAGE_BLK_SIZ;
                res = RES_OK;
                break;

        case GET_BLOCK_SIZE:
                // 擦除块的大小
                // 返回的BLOCK_SIZE不是字节的意思,单位是Sector
                *(uint32_t *)buff = 1; // 1个块等于1个扇区
                res = RES_OK;
                break;

        case CTRL_TRIM:
                // 通知设备不再使用某些扇区
                // 实现此操作以支持 TRIM 功能
                // 控制程序没写,直接返回错误码RES_PARERR,表示不支持 TRIM 操作
                res = RES_PARERR;
                break;

        default:
                res = RES_PARERR;
        }

        return res;
        /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/






五、代码说明
(一)描述符
USBD_DescriptorsTypeDef FS_Desc结构体,包含设备描述符、语言ID描述符、厂商字符串描述符等,用于向主机描述设备的各种属性。

USBD_DescriptorsTypeDef FS_Desc =
{
  USBD_FS_DeviceDescriptor,          // 设备描述符
  USBD_FS_LangIDStrDescriptor,       // 语言 ID 描述符
  USBD_FS_ManufacturerStrDescriptor, // 厂商字符串描述符
  USBD_FS_ProductStrDescriptor,      // 产品字符串描述符
  USBD_FS_SerialStrDescriptor,       // 序列号字符串描述符
  USBD_FS_ConfigStrDescriptor,       // 配置字符串描述符
  USBD_FS_InterfaceStrDescriptor     // 接口字符串描述符
#if (USBD_LPM_ENABLED == 1)
, USBD_FS_USR_BOSDescriptor          // BOS 描述符 (如果启用了 LPM 低功耗模式)
#endif
};


(二)3种写入、读取函数
STORAGE_Read_FS和STORAGE_Write_FS函数:在u盘连接电脑或往u盘中拖拽文件时调用,用于实现u盘数据的读写操作,需根据存储介质进行正确编写。
USER_read、USER_write和USER_ioctl函数:由用户编写,用于实现特定的读写和控制功能,在调用disk_read函数时自动调用USER_read函数来实现读取数据功能。
disk_write和disk_read函数:底层的读写函数,在执行文件系统操作时被调用,与USER_read和USER_write函数协同工作。
总结:需要修改:STORAGE_Read_FS、STORAGE_Write_FS、USER_read、USER_write、USER_ioctl函数

函数的参数说明

int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
/*uint8_t lun: 逻辑单元号(LUN),用于标识存储设备的不同逻辑分区。在多设备场景下使用。(就是多个u盘)
uint8_t *buf: 指向缓冲区的指针,用于存储读取的数据。
uint32_t blk_addr: 要读取的起始块地址(block address),以块为单位,通常用于标识读取数据在存储设备上的位置。
uint16_t blk_len: 要读取的块数量(block length),指明从起始块地址开始读取的块的数量。*/

//-------------------------------------------------

DRESULT USER_read (
        BYTE pdrv,      /* Physical drive nmuber to identify the drive */
        BYTE *buff,     /* Data buffer to store read data */
        DWORD sector,   /* Sector address in LBA */
        UINT count      /* Number of sectors to read */
)
DRESULT USER_write (
    BYTE pdrv,          /* Physical drive nmuber to identify the drive */
    const BYTE *buff,   /* Data to be written */
    DWORD sector,       /* Sector address in LBA */
    UINT count          /* Number of sectors to write */
)
DRESULT USER_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
/*作用:该函数负责从物理存储设备中读取数据并将其存储到提供的缓冲区中。
pdrv: 物理驱动器号,通常用来标识不同的存储设备。在大多数情况下,你只需要一个设备(如内部Flash),所以pdrv一般是0。
buff: 用于存储读取数据的缓冲区指针。读取的数据将被保存到这个缓冲区中。
sector: 要读取的扇区地址,以LBA(逻辑块地址)为单位。在Flash存储中,扇区是数据存储的基本单位。
count: 需要读取的扇区数量,通常为1到128。*/
//----------------------------------------------

DRESULT disk_write (
    BYTE pdrv,                /* Physical drive nmuber to identify the drive */
    const BYTE *buff,        /* Data to be written */
    DWORD sector,                /* Sector address in LBA */
    UINT count                /* Number of sectors to write */
)
DRESULT disk_read (
        BYTE pdrv,                /* Physical drive nmuber to identify the drive */
        BYTE *buff,                /* Data buffer to store read data */
        DWORD sector,                /* Sector address in LBA */
        UINT count                /* Number of sectors to read */
)


(三)f_mount
用于磁盘驱动器初始化(磁盘挂载函数),可初始化0盘、1盘等,需指定文件结构指针、路径及挂载选项。

FRESULT f_mount(FATFS*  fs /*(文件结构指针,全局定义一个即可)*/,       
                const TCHAR * /*path(字符串类型,表示其路径,如”0:“)*/,   
                BYTE  opt  /*(选项,0/1,通常是1表示立即挂载,0表示稍后挂载。)*/  
                 )


格式化后需要重新挂载磁盘,所以需要执行两步:

取消挂载 ​f_monut​(​NULL,“0:”,1),即将空设备挂到我们的逻辑磁盘上(相当于清空逻辑磁盘)
重新挂载设备​ f_mount( &fs ,"0: " ,1).
(四)f_mkfs
用于格式化磁盘,在物理磁盘上建立FATFS文件系统架构,包括目录、文件分配表等,格式化后需重新挂载磁盘。

FRESULT f_mkfs (
    const TCHAR* path,   // 驱动器路径,例如 "0:" 表示第一个逻辑驱动器(只有一个设备的时候就是"0:")
    BYTE opt,            // 格式化选项,指定 FAT 类型和扇区格式(以什么格式对U盘格式化)
    DWORD au,            // 每簇的扇区数,0 表示使用默认值
    void* work,          // 工作区的缓冲区(在格式化过程中需要使用一定的缓冲区来存储一些数据)文件分配表是 FAT 文件系统的核心部分,用于跟踪文件存储位置。工作缓冲区在格式化过程中,用来暂时存储并处理 FAT 相关的数据,然后再将它写入存储设备。
    UINT len             // 工作区缓冲区的大小
);



簇(Cluster):是文件系统中的一个基本存储单位,常用于 FAT 文件系统(如 FAT16、FAT32 和 exFAT)。簇是硬盘或存储介质上连续扇区(sector)的集合,是用于文件分配的最小单位。文件系统会以簇为单位来管理文件数据,而不是以扇区为单位。

簇:一个簇是由一个或多个扇区组成的。每个文件系统根据其格式选择一个簇的大小,通常为几个扇区的倍数(如 1 簇 = 4 扇区)。

扇区:存储设备的物理最小存储单位,通常是 512 字节或 4096 字节。

(五)USER_initialize
在调用挂载文件系统函数f_mount()时被调用,用于初始化存储设备。

DSTATUS USER_initialize (//在调用挂载文件系统函数f_mount()的时候会调用这个函数
BYTE pdrv           /* Physical drive nmuber to identify the drive */
)


(六)FATFS系统文件说明
user_diski.c:用户需根据硬件修改相关函数,如USER_read、USER_write等,以实现与硬件相关的数据读写操作。
f_mkfs函数:详细介绍了格式化磁盘的参数及功能,包括驱动器路径、格式化选项、每簇扇区数、工作区缓冲区等。
f_read函数:用于从文件中读取数据,需指定文件对象指针、缓冲区、要读取的字节数及实际读取的字节数等参数。
六、程序跳转注意问题
(一)bootloader
关闭中断:在程序跳转前,需关闭usb中断及全部中断,防止跳转过程中出现异常。
程序跳转代码

/// @brief 程序跳转
/// @param address 要跳转到的地址
void jump_to_application(uint32_t address)
{
    // 定义一个函数指针类型 pFunction,指向返回值为 void,且无参数的函数
    typedef void (*pFunction)(void);
    static pFunction JumpToApplication;

    // 程序的堆栈指针(栈顶指针)
    uint32_t stackPointer = *(__IO uint32_t *)address;

//    // 栈指针应指向有效的RAM区域(STM32F407的RAM范围为0x20000000 - 0x20020000)
//    if (stackPointer < 0x20000000 || stackPointer > 0x20020000) {
//        // 无效栈指针,说明没有有效的程序,执行错误处理
//        error_printf("无效栈指针,Invalid stack pointer: 0x%08lx\n", stackPointer);
//        return;
//    }

    // 读取应用程序的复位向量(入口地址)
    uint32_t resetVector = *(__IO uint32_t *)(address + 4);

    // 正常情况下,复位向量应指向falsh
    if (resetVector < FLASH_START_ADDR || resetVector >= FLASH_END_ADDR) {
        // 无效复位向量,说明没有有效的程序,执行错误处理
        error_printf("无效复位向量,Invalid reset vector: 0x%08lx\n", resetVector);
        return;
    }

    // 反初始化 USB 设备(假设你有实现 MY_DeInitUSB 函数)
    MY_DeInitUSB();  // 因为 USB 有中断

    // 禁用所有中断
    __disable_irq();

    // 设置主堆栈指针(MSP)为读取的堆栈指针值
    __set_MSP(stackPointer);

    // 将复位向量地址转换为函数指针
    JumpToApplication = (pFunction)resetVector;

    // 跳转到应用程序,开始执行应用程序的代码
    JumpToApplication();
}



(二)app
开中断:在初始化系统时钟前,开启全部中断,即__enable_irq();。
中断向量表地址偏移修改,根据自己的实际地址修改
__enable_irq();//开启全部中断
SCB->VTOR = 0X08000000 | 0XC000;
/*
SCB 是系统控制块(System Control Block)的缩写,于 Cortex-M 内核中,负责控制系统的一些重要功能。
VTOR 是 SCB 中的向量表偏移寄存器(Vector Table Offset Register),它用于指定中断向量表的基地址。
作用:将中断向量表的基地址偏移到app程序的地址
*/


此操作将中断向量表基地址偏移到app程序地址。 3. 系统时钟初始化程序的修改 在初始化系统时钟时,需检查PLL是否已配置好,因其参数不能修改。



七、知识记录
(一)hex文件解析
:100130003F0156702B5E712B722B732146013421C7
/*
这行数据的内容如下:
- : 是起始符。
- 10 表示本行有 16 个字节的数据。(1个字节)
- 0130 是起始地址,表示数据应写入内存的 0x0130 位置。(2个字节)
- 00 是记录类型,表示这是数据记录。(1个字节)
- 3F0156702B5E712B722B732146013421 是实际的数据。
- C7 是校验和,用于校验本行数据是否正确。(1个字节)
*/


(二)生成bin方法
在keil5中进行如下配置,使用fromelf --bin -o "$L@L.bin" "#L"命令将工程生成bin文件。



八、过程遇到的问题记录及解决方法
显示描述符获取失败
原因:使用stm32cubemx配置时钟源不当,导致usb时钟信号错误,电脑无法获取设备。
解决方法:检查并正确配置时钟源,确保时钟树及usb时钟配置无误。





显示大容量设备,但磁盘里看不到stm32的u盘
原因:堆空间不足。
解决方法:将堆改大,调整Minimum Heap Size参数。





格式化文件系统显示成功,但挂载文件系统失败
现象:



原因:格式化函数f_mkfs()参数写错。
解决方法:查看f_mkfs函数使用说明,确保参数正确。



从bootloader跳转到app程序失败
原因:前后程序时钟配置冲突,引导加载程序已配置时钟,应用程序重新配置时产生冲突,如HAL_RCC_OscConfig()失败。
解决方法
保证前后程序时钟配置一致,防止冲突。
若bootloader已配置好时钟,app可不配置时钟。





程序跳转后,app程序卡死
现象
使用keil烧录app程序正常运行。
在bootloader程序里烧录app程序,卡死在hal库的延时函数(通过打印数据猜测,未调试)。
在app调试下,卡死在特定程序中(如涉及HAL_Init、HAL_Delay等函数处)。
原因:在bootloader程序里开启了usb中断,但app程序中无usb相关中断,进入APP前未关闭该中断,导致找不到中断入口而死锁。
解决方法
在程序跳转之前反初始化usb:
void MY_DeInitUSB(void)
{
    // 停止 USB 设备
    USBD_Stop(&hUsbDeviceFS);

    // 反初始化 USB 设备
    USBD_DeInit(&hUsbDeviceFS);
}


HAL_Delay函数无法使用的情况分析
问题分析
系统时钟未正确配置:HAL_Delay依赖系统定时器中断,跳转失败可能导致时钟破坏或未正确配置,使SysTick中断失效。
中断被禁用或损坏:跳转失败可能导致中断禁用或中断表破坏,使SysTick中断无法触发。
系统进入异常状态:跳转失败后MCU进入错误状态(如硬件错误、陷入HardFault中断等),中断正常流程,包括HAL_Delay执行。
复位未完全执行:系统未彻底复位,跳转导致控制权丢失,HAL_Delay依赖的底层配置被破坏。
解决方案
确保时钟和中断配置正确:重新初始化HAL库和系统时钟,即HAL_Init(); SystemClock_Config();。
使用软件方式实现延迟:编写基于循环的延迟函数,如void software_delay(uint32_t delay_ms) { for (uint32_t i = 0; i < delay_ms * 1000; i++) { __asm("nop"); } },精度较低,适用于短暂延迟。
复位MCU:调用NVIC_SystemReset()进行MCU复位,确保系统重新启动稳定。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_54762282/article/details/143331725

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1923

主题

15596

帖子

11

粉丝