天天看点

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第80章       STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

本章节为大家讲解MDK下载算法制作方法。

80.1 初学者重要提示

80.2 MDK下载算法基础知识

80.3 创建MDK下载算法通用流程

80.4 QSPI Flash的MDK下载算法制作

80.5 QSPI Flash的MDK下载算法使用方法

80.6 实验例程说明

80.7 总结

  1.   QSPI Flash的相关知识点可以看第78章和79章。
  2.   QSPI Flash下载算法文件直接采用HAL库制作,方便大家自己修改。

Flash编程算法是一种用于擦除应用程序或将应用程序下载到Flash的程序代码。MDK本身支持的各种器件都自带下载算法,存放在MDK各种器件的软件包里面,以STM32H7为例,算法存放在\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软件包版本不同,数值2.6.0不同),但不支持的需要我们自己制作,本章教程为此而生。

80.2.1 程序能够通过下载算法下载到芯片的核心思想

认识到这点很重要:通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。

80.2.2 算法程序中擦除操作执行流程

擦除操作大致流程:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作
  •   加载算法到芯片RAM。
  •   执行初始化函数Init。
  •   执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
  •   执行Uinit函数。
  •   操作完毕。

80.2.3 算法程序中编程操作执行流程

编程操作大致流程:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作
  •   针对MDK生成的axf可执行文件做Init初始化,这个axf文件是指的大家自己创建应用程序生成的。
  •   查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
    •   加载算法到RAM。
    •   执行Init函数。
    •   加载用户到RAM缓冲。
    •   执行Program Page页编程函数。
    •   执行Uninit函数。

80.2.4 算法程序中校验操作执行流程

校验操作大致流程:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作
  •   校验要用到MDK生成的axf可执行文件。校验就是axf文件中下载到芯片的程序和实际下载的程序读出来做比较。
    •   查看校验算法是否存在
      •   如果有,加载应用程序到RAM并执行校验。
      •   如果没有,计算CRC,将芯片中读取出来的数据和RAM中加载应用计算输出的CRC值做比较。
    •   替换BKPT(BreakPoint断点指令)为 B. 死循环指令。
    •   执行RecoverySupportStop,恢复支持停止。
    •   执行DebugCoreStop,调试内核停止。
  •   运行应用:
    •   执行失败。
    •   执行成功,再执行硬件复位。
  •   操作完毕,停止调试端口。

下面是MDK给的一种大致操作流程,不限制必须采用这种方法,自己创建也可以的。

80.3.1 第1步,使用MDK提供好的程序模板

位于路径:\Keil\ARM\Pack\ARM\CMSIS\version\Device\_Template_Flash。

效果如下:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.3.2 第2步,修改工程名

MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根据自己的需要做修改。比如修改为MyDevice.uvprojx。

80.3.3 第3步,修改使用的器件

在MDK的Option选项里面设置使用的器件。

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.3.4 第4步,修改输出算法文件的名字

这个名字是方便用户查看的,比如设置为stm32h7,那么输出的算法文件就是stm32h7.flm。

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

注:MDK这里设置的名字与下面位置识别出来的算法名无关:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

这个名字是在FlashDev.c里面定义的。

80.3.5 第5步,修改编程算法文件FlashPrg.c

模板工程里面仅提供了接口函数,内容需要用户自己填。

/* 
   Mandatory Flash Programming Functions (Called by FlashOS):
                int Init        (unsigned long adr,   // Initialize Flash
                                 unsigned long clk,
                                 unsigned long fnc);
                int UnInit      (unsigned long fnc);  // De-initialize Flash
                int EraseSector (unsigned long adr);  // Erase Sector Function
                int ProgramPage (unsigned long adr,   // Program Page Function
                                 unsigned long sz,
                                 unsigned char *buf);

   Optional  Flash Programming Functions (Called by FlashOS):
                int BlankCheck  (unsigned long adr,   // Blank Check
                                 unsigned long sz,
                                 unsigned char pat);
                int EraseChip   (void);               // Erase complete Device
      unsigned long Verify      (unsigned long adr,   // Verify Function
                                 unsigned long sz,
                                 unsigned char *buf);

       - BlanckCheck  is necessary if Flash space is not mapped into CPU memory space
       - Verify       is necessary if Flash space is not mapped into CPU memory space
       - if EraseChip is not provided than EraseSector for all sectors is called
*/

/*
 *  Initialize Flash Programming Functions
 *    Parameter:      adr:  Device Base Address
 *                    clk:  Clock Frequency (Hz)
 *                    fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
 *    Return Value:   0 - OK,  1 - Failed
 */

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  De-Initialize Flash Programming Functions
 *    Parameter:      fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
 *    Return Value:   0 - OK,  1 - Failed
 */

int UnInit (unsigned long fnc) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Erase complete Flash Memory
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseChip (void) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Erase Sector in Flash Memory
 *    Parameter:      adr:  Sector Address
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseSector (unsigned long adr) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Program Page in Flash Memory
 *    Parameter:      adr:  Page Start Address
 *                    sz:   Page Size
 *                    buf:  Page Data
 *    Return Value:   0 - OK,  1 - Failed
 */

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}      

80.3.6 第6步,修改配置文件FlashDev.c

模板工程里面提供简单的配置说明:

struct FlashDevice const FlashDevice  =  {
   FLASH_DRV_VERS,             // Driver Version, do not modify!
   "New Device 256kB Flash",   // Device Name 
   ONCHIP,                     // Device Type
   0x00000000,                 // Device Start Address
   0x00040000,                 // Device Size in Bytes (256kB)
   1024,                       // Programming Page Size
   0,                          // Reserved, must be 0
   0xFF,                       // Initial Content of Erased Memory
   100,                        // Program Page Timeout 100 mSec
   3000,                       // Erase Sector Timeout 3000 mSec

// Specify Size and Address of Sectors
   0x002000, 0x000000,         // Sector Size  8kB (8 Sectors)
   0x010000, 0x010000,         // Sector Size 64kB (2 Sectors) 
   0x002000, 0x030000,         // Sector Size  8kB (8 Sectors)
   SECTOR_END
};      

注:名字New Device 256kB Flash就是我们第4步所说的。MDK的Option选项里面会识别出这个名字。

80.3.7 第7步,保证生成的算法文件中RO和RW段的独立性,即与地址无关

C和汇编的配置都勾选上:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

汇编:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:

(1)加载以响应运行事件。

(2)在不同情况下使用其他例程的不同组合加载到内存中。

(3)在执行期间映射到不同的地址。

使用Read-Write position independence同理,表示的可读可写数据段。

80.3.8 第8步,将程序可执行文件axf修改为flm格式

通过下面的命令就可以将生成的axf可执行文件修改为flm。

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.3.9 第9步,分散加载设置

我们这里的分散加载文件直接使用MDK模板工程里提供好的即可,无需任何修改。

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

分散加载文件中的内容如下:

; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW,+ZI)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}      

--diag_suppress L6305用于屏蔽L6503类型警告信息。

特别注意,设置了分散加载后,此处的配置就不再起作用了:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

下面将QSPI Flash算法制作过程中的几个关键点为大家做个说明。

80.4.1 第1步,制作前重要提示

这两点非常重要:

  •   程序里面不要开启任何中断,全部查询方式。
  •   HAL库里面各种时间基准相关的API全部处理掉。简单省事些,我们这里是直接注释,采用死等即可。无需做超时等待,因为超时后,已经意味着操作失败了,跟死等没有区别。

80.4.2 第2步,准备一个工程模板

 推荐大家直接使用我们本章工程准备好的模板即可,如果大家自己制作,注意一点,请使用当前最新的HAL库。

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.4.3 第3步,修改HAL库

这一步比较重要,主要修改了以下三个文件:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

主要是修改了HAL库时间基准相关的几个API,并注释掉了一批无关的API。具体修改内容,大家可以找个比较软件,对比修改后的这个文件和CubeH7软件包V1.8.0(软件包里面的HAL库版本是V1.9.0)的差异即可。

80.4.4 第4步,时钟初始化

我们已经用不到滴答定时器了,直接在bsp.c文件里面对滴答初始化函数做重定向:

/*
*********************************************************************************************************
*    函 数 名: HAL_InitTick
*    功能说明: 重定向,不使用
*    形    参: TickPriority
*    返 回 值: 无
*********************************************************************************************************
*/
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    return HAL_OK;
}      

然后就是HSE外置晶振的配置,大家根据自己的板子实际外挂晶振大小,修改stm32h7xx_hal_conf.h文件中HSE_VALUE大小,实际晶振多大,这里就修改为多大:

#if !defined  (HSE_VALUE) 
#define HSE_VALUE    ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */      

最后修改PLL:

/*
*********************************************************************************************************
*    函 数 名: SystemClock_Config
*    功能说明: 初始化系统时钟
*                System Clock source            = PLL (HSE)
*                SYSCLK(Hz)                     = 400000000 (CPU Clock)
*               HCLK(Hz)                       = 200000000 (AXI and AHBs Clock)
*                AHB Prescaler                  = 2
*                D1 APB3 Prescaler              = 2 (APB3 Clock  100MHz)
*                D2 APB1 Prescaler              = 2 (APB1 Clock  100MHz)
*                D2 APB2 Prescaler              = 2 (APB2 Clock  100MHz)
*                D3 APB4 Prescaler              = 2 (APB4 Clock  100MHz)
*                HSE Frequency(Hz)              = 25000000
*               PLL_M                          = 5
*                PLL_N                          = 160
*                PLL_P                          = 2
*                PLL_Q                          = 4
*                PLL_R                          = 2
*                VDD(V)                         = 3.3
*                Flash Latency(WS)              = 4
*    形    参: 无
*    返 回 值: 1 表示失败,0 表示成功
*********************************************************************************************************
*/
int SystemClock_Config(void)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    HAL_StatusTypeDef ret = HAL_OK;

    /* 锁住SCU(Supply configuration update) */
    MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0);

    /* 
      1、芯片内部的LDO稳压器输出的电压范围,可选VOS1,VOS2和VOS3,不同范围对应不同的Flash读速度,
         详情看参考手册的Table 12的表格。
      2、这里选择使用VOS1,电压范围1.15V - 1.26V。
    */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

    /* 使能HSE,并选择HSE作为PLL时钟源 */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
    RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
        
    RCC_OscInitStruct.PLL.PLLM = 5;
    RCC_OscInitStruct.PLL.PLLN = 160;
    RCC_OscInitStruct.PLL.PLLP = 2;
    RCC_OscInitStruct.PLL.PLLR = 2;
    RCC_OscInitStruct.PLL.PLLQ = 4;        
        
    RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
    RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;    
    ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
    if(ret != HAL_OK)
    {
        return 1;        
    }

    /* 
       选择PLL的输出作为系统时钟
       配置RCC_CLOCKTYPE_SYSCLK系统时钟
       配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线
       配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线
       配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线
       配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
       配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线     
    */
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 | \
                                 RCC_CLOCKTYPE_PCLK2  | RCC_CLOCKTYPE_D3PCLK1);

    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;  
    RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2; 
    RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; 
    RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; 
    
    /* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */
    ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
    if(ret != HAL_OK)
    {
        return 1;
    }

    /*
      使用IO的高速模式,要使能IO补偿,即调用下面三个函数 
      (1)使能CSI clock
      (2)使能SYSCFG clock
      (3)使能I/O补偿单元, 设置SYSCFG_CCCSR寄存器的bit0
    */
    __HAL_RCC_CSI_ENABLE() ;

    __HAL_RCC_SYSCFG_CLK_ENABLE() ;

    HAL_EnableCompensationCell();

    __HAL_RCC_D2SRAM1_CLK_ENABLE();
    __HAL_RCC_D2SRAM2_CLK_ENABLE();
    __HAL_RCC_D2SRAM3_CLK_ENABLE();
    
    return 0;
}      

80.4.5 第5步,配置文件FlashDev.c的实现

配置如下:

struct FlashDevice const FlashDevice  =  {
    FLASH_DRV_VERS,                   /* 驱动版本,勿修改,这个是MDK定的 */
    "ARMFLY_STM32H7x_QSPI_W25Q256",   /* 算法名,添加算法到MDK安装目录会显示此名字 */
    EXTSPI,                           /* 设备类型 */
    0x90000000,                       /* Flash起始地址 */
    32 * 1024 * 1024,                 /* Flash大小,32MB */
    4 * 1024,                         /* 编程页大小 */
    0,                                /* 保留,必须为0 */
    0xFF,                             /* 擦除后的数值 */
    1000,                             /* 页编程等待时间 */
    6000,                             /* 扇区擦除等待时间 */
    64 * 1024, 0x000000,              /* 扇区大小,扇区地址 */
    SECTOR_END    
};      

注释已经比较详细,大家根据自己的需要做修改即可。注意一点,算法名ARMFLY_STM32H7x_QSPI_W25Q256会反馈到这个地方:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.4.6 第6步,编程文件FlashPrg.c的实现

下面将文件中实现的几个函数为大家做个说明:

  •   初始化函数Init
/*
*********************************************************************************************************
*    函 数 名: Init
*    功能说明: Flash编程初始化
*    形    参: adr Flash基地址,芯片首地址。
*             clk 时钟频率
*             fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失败
*********************************************************************************************************
*/
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) 
{
    int result = 0;
 
    /* 系统初始化 */
    SystemInit(); 

    /* 时钟初始化 */
    result = SystemClock_Config();
    if (result  != 0)
    {
        return 1;        
    }

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 内存映射 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }

    return 0;
}      

初始化完毕后将其设置为内存映射模式。

  •   复位初始化函数Uinit

擦除,编程和校验函数后都会调用此函数。

/*
*********************************************************************************************************
*    函 数 名: UnInit
*    功能说明: 复位初始化
*    形    参: fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失败
*********************************************************************************************************
*/
int UnInit (unsigned long fnc) 
{ 
    int result = 0;

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 内存映射 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);
}      

复位初始化这里,直接将其设置为内存映射模式。

  •   整个芯片擦除函数EraseChip

如果大家配置勾选了MDK Option选项中此处的配置,会调用的整个芯片擦除:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

实际应用中不推荐大家勾选这里,因为整个芯片擦除太耽误时间,比如32MB QSPI Flash整个芯片擦除需要300秒左右。

另外,如果大家的算法工程里面没有添加此函数,MDK会调用扇区擦除函数来实现,直到所有扇区擦除完毕。

/*
*********************************************************************************************************
*    函 数 名: UnInit
*    功能说明: 复位初始化
*    形    参: fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失败
*********************************************************************************************************
*/
int UnInit (unsigned long fnc) 
{ 
    int result = 0;

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 内存映射 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);
}      
  •   扇区擦除函数EraseSector

如果大家配置勾选了MDK Option选项中此处的配置,会调用扇区擦除:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作
/*
*********************************************************************************************************
*    函 数 名: EraseSector
*    功能说明: 扇区擦除
*    形    参: adr 擦除地址
*    返 回 值: 无
*********************************************************************************************************
*/
int EraseSector (unsigned long adr) 
{    
    int result = 0;

    /* 地址要在操作的芯片范围内 */
    if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
    {
        return 1;
    }
    
    adr -= QSPI_FLASH_MEM_ADDR;
    
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 扇区擦除 */
    result = QSPI_EraseSector(adr);  
    if (result != 0)
    {
        return 1;
    }    

    /* 内存映射 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return 0;   
}      

这里要注意两点:

(1)     程序里面的操作adr -= QSPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0x90000000。

(2)     这里执行的擦除大小要前面FlashDev.c文件中配置的扇区大小一致,这里是执行的64KB为扇区进行擦除。

  •   页编程函数ProgramPage

页编程函数实现如下:

/*
*********************************************************************************************************
*    函 数 名: ProgramPage
*    功能说明: 页编程
*    形    参: adr 页起始地址
*             sz  页大小
*             buf 要写入的数据地址
*    返 回 值: 无
*********************************************************************************************************
*/
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) 
{
    int size;
    int result = 0;

    /* 地址要在操作的芯片范围内 */    
    if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
    {
        return 1;
    }
   
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
        
    adr -= QSPI_FLASH_MEM_ADDR;
    size =  sz;
    
    /* 页编程 */
    while(size > 0)
    {
        if (QSPI_WriteBuffer(buf, adr, 256) == 1)
        {
            QSPI_MemoryMapped(); 
            
            return 1;   
        }
        size -= 256;
        adr += 256;
        buf += 256;
    }
    
    /* 内存映射 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);                      
}      

这里注意两点:

(1)     W25Q256的页大小是256字节,前面FlashDev.c中将页编程大小设置为4096字节,所以此程序要做处理。

(2)     程序里面的操作adr -= QSPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0x90000000。

  •   读取和校验函数

我们程序中未做读取和校验函数。

(1)     如果程序中未做读取函数,那么MDK会以总线方式进行读取,这也是为什么每个函数执行完毕都设置为内存映射模式的原因。

(2)     如果程序中未做校验函数,那么MDK会读取数据做CRC校验。

80.4.7 第7步,修改QSPI Flash驱动文件(引脚,命令等)

最后一步就是QSPI Flash(W25Q256)的驱动修改,大家可以根据自己的需求做修改。使用的引脚定义在文件bsp_qspi_w25q256.c(做了条件编译,包含了H7-TOOL和STM32-V7板子):

/* 
    STM32-V7开发板接线

    PG6/QUADSPI_BK1_NCS     AF10
    PF10/QUADSPI_CLK        AF9
    PF8/QUADSPI_BK1_IO0     AF10
    PF9/QUADSPI_BK1_IO1     AF10
    PF7/QUADSPI_BK1_IO2     AF9
    PF6/QUADSPI_BK1_IO3     AF9

    W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB
        
    H7-TOOL开发板接线

    PG6/QUADSPI_BK1_NCS     AF10
    PB2/QUADSPI_CLK         AF9
    PD11/QUADSPI_BK1_IO0    AF10
    PD12/QUADSPI_BK1_IO1    AF10
    PF7/QUADSPI_BK1_IO2     AF9
    PD13/QUADSPI_BK1_IO3    AF9
*/

/* QSPI引脚和时钟相关配置宏定义 */
#if 0
#define QSPI_CLK_ENABLE()               __HAL_RCC_QSPI_CLK_ENABLE()
#define QSPI_CLK_DISABLE()              __HAL_RCC_QSPI_CLK_DISABLE()
#define QSPI_CS_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOG_CLK_ENABLE()
#define QSPI_CLK_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOB_CLK_ENABLE()
#define QSPI_BK1_D0_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_CLK_ENABLE()
#define QSPI_BK1_D1_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_CLK_ENABLE()
#define QSPI_BK1_D2_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D3_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_CLK_ENABLE()

#define QSPI_MDMA_CLK_ENABLE()          __HAL_RCC_MDMA_CLK_ENABLE()
#define QSPI_FORCE_RESET()              __HAL_RCC_QSPI_FORCE_RESET()
#define QSPI_RELEASE_RESET()            __HAL_RCC_QSPI_RELEASE_RESET()

#define QSPI_CS_PIN                     GPIO_PIN_6
#define QSPI_CS_GPIO_PORT               GPIOG
#define QSPI_CS_GPIO_AF                 GPIO_AF10_QUADSPI

#define QSPI_CLK_PIN                    GPIO_PIN_2
#define QSPI_CLK_GPIO_PORT              GPIOB
#define QSPI_CLK_GPIO_AF                GPIO_AF9_QUADSPI

#define QSPI_BK1_D0_PIN                 GPIO_PIN_11
#define QSPI_BK1_D0_GPIO_PORT           GPIOD
#define QSPI_BK1_D0_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D1_PIN                 GPIO_PIN_12
#define QSPI_BK1_D1_GPIO_PORT           GPIOD
#define QSPI_BK1_D1_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D2_PIN                 GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT           GPIOF
#define QSPI_BK1_D2_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D3_PIN                 GPIO_PIN_13
#define QSPI_BK1_D3_GPIO_PORT           GPIOD
#define QSPI_BK1_D3_GPIO_AF             GPIO_AF9_QUADSPI
#else
#define QSPI_CLK_ENABLE()               __HAL_RCC_QSPI_CLK_ENABLE()
#define QSPI_CLK_DISABLE()              __HAL_RCC_QSPI_CLK_DISABLE()
#define QSPI_CS_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOG_CLK_ENABLE()
#define QSPI_CLK_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D0_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D1_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D2_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D3_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()

#define QSPI_MDMA_CLK_ENABLE()          __HAL_RCC_MDMA_CLK_ENABLE()
#define QSPI_FORCE_RESET()              __HAL_RCC_QSPI_FORCE_RESET()
#define QSPI_RELEASE_RESET()            __HAL_RCC_QSPI_RELEASE_RESET()

#define QSPI_CS_PIN                     GPIO_PIN_6
#define QSPI_CS_GPIO_PORT               GPIOG
#define QSPI_CS_GPIO_AF                 GPIO_AF10_QUADSPI

#define QSPI_CLK_PIN                    GPIO_PIN_10
#define QSPI_CLK_GPIO_PORT              GPIOF
#define QSPI_CLK_GPIO_AF                GPIO_AF9_QUADSPI

#define QSPI_BK1_D0_PIN                 GPIO_PIN_8
#define QSPI_BK1_D0_GPIO_PORT           GPIOF
#define QSPI_BK1_D0_GPIO_AF             GPIO_AF10_QUADSPI

#define QSPI_BK1_D1_PIN                 GPIO_PIN_9
#define QSPI_BK1_D1_GPIO_PORT           GPIOF
#define QSPI_BK1_D1_GPIO_AF             GPIO_AF10_QUADSPI

#define QSPI_BK1_D2_PIN                 GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT           GPIOF
#define QSPI_BK1_D2_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D3_PIN                 GPIO_PIN_6
#define QSPI_BK1_D3_GPIO_PORT           GPIOF
#define QSPI_BK1_D3_GPIO_AF             GPIO_AF9_QUADSPI
#endif      

硬件设置了之后,剩下就是QSPI Flash相关的几个配置,在文件bsp_qspi_w25q256.h:

主要是下面这几个:

#define QSPI_FLASH_MEM_ADDR         0x90000000

/* W25Q256JV基本信息 */
#define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
#define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB */
#define QSPI_PAGE_SIZE      256                     /* 页大小,256字节 */
#define QSPI_END_ADDR       (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */
#define QSPI_FLASH_SIZES    32 * 1024 * 1024         /* Flash大小,2^25 = 32MB*/

/* W25Q256JV相关命令 */
#define WRITE_ENABLE_CMD                        0x06    /* 写使能指令 */
#define READ_ID_CMD2                            0x9F    /* 读取ID命令 */
#define READ_STATUS_REG_CMD                     0x05    /* 读取状态命令 */
#define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD         0x21    /* 32bit地址扇区擦除指令, 4KB */
#define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD       0x34    /* 32bit地址的4线快速写入命令 */
#define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD    0xEC    /* 32bit地址的4线快速读取命令 */

#define BLOCK_ERASE_64K_4_BYTE_ADDR_CMD         0xDC    /* 4字节地址,64K扇区 */

#define BULK_ERASE_CMD                          0xC7    /* 整片擦除 */      

编译本章教程配套的例子,生成的算法文件位于此路径下:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.5.1 下载算法存放位置

生成算法文件后,需要大家将其存到MDK安装目录,有两个位置可以存放,任选其一,推荐第2种:

  •   第1种:存放到MDK的STM32H7软包安装目录里面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软包版本不同,数值2.6.0不同)。
  •   第2种:MDK的安装目录 \ARM\Flash里面。
【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.5.2 下载配置

注意这里一定要够大,否则会提示算法文件无法加载:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。推荐使用AXI SRAM(地址0x24000000),因为这块RAM空间足够大。

如果要下载程序到QSPI Flash里面,需要做如下配置:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.5.3 调试配置

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。

如果要做调试下载,需要做如下配置:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

80.5.4 验证算法文件是否可以正常使用

为了验证算法文件是否可以正常使用,大家可以运行本教程第82章或者83章配套的例子。

本章配套例子:V7-060_QSPI Flash的MDK下载算法制作。

编译后,算法文件会存到此路径下:

【STM32H7教程】第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作

本章节就为大家讲解这么多,为了熟练掌握,大家可以尝试自己实现一个Flash下载算法。

微信公众号:armfly_com

安富莱论坛:www.armbbs.cn

安富莱淘宝:https://armfly.taobao.com