|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-07-29  |  gingko  |  初次建立  | 
\\
\\
\\
\\
\\
\\ 
===== STM32CubeMX教程五十八——MDK FLM实验 =====
1.在主界面选择File-->New Project   或者直接点击ACCEE TO MCU SELECTOR  
{{ :icore4tx:icore4tx_cube_58_1.png?direct |}}
2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置
在搜索栏的下面,提供的各  种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32H750IBKx。
{{ :icore4tx:icore4tx_cube_58_2.png?direct |}}
3.配置RCC,使用外部时钟源
{{ :icore4tx:icore4tx_cube_58_3.png?direct |}}
4.时基源选择SysTick
{{ :icore4tx:icore4tx_cube_58_4.png?direct |}}
5.将PA10,PB7,PB8设置为GPIO_Output
{{ :icore4tx:icore4tx_cube_58_5.png?direct |}}
6.引脚模式配置
{{ :icore4tx:icore4tx_cube_58_6.png?direct |}}
7.配置串口
{{ :icore4tx:icore4tx_cube_58_7.png?direct |}}
在NVIC Settings一栏使能接收中断
{{ :icore4tx:icore4tx_cube_58_8.png?direct |}}
引脚配置
{{ :icore4tx:icore4tx_cube_58_9.png?direct |}}
8.配置ADC
{{ :icore4tx:icore4tx_cube_58_10.png?direct |}}
{{ :icore4tx:icore4tx_cube_58_11.png?direct |}}
9.配置QUADSPI
{{ :icore4tx:icore4tx_cube_58_12.png?direct |}}
10.配置SDMMC
{{ :icore4tx:icore4tx_cube_58_13.png?direct |}}
{{ :icore4tx:icore4tx_cube_58_14.png?direct |}}
11.配置FATFS
{{ :icore4tx:icore4tx_cube_58_15.png?direct |}}
12.配置SPI
{{ :icore4tx:icore4tx_cube_58_16.png?direct |}}
{{ :icore4tx:icore4tx_cube_58_17.png?direct |}}
13.配置FMC
{{ :icore4tx:icore4tx_cube_58_18.png?direct |}}
14.时钟源设置,选择外部高速时钟源,配置为最大主频
{{ :icore4tx:icore4tx_cube_58_19.png?direct |}}
{{ :icore4tx:icore4tx_cube_58_20.png?direct |}}
15.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可  IDE我们使用的是 MDK V5.27
{{ :icore4tx:icore4tx_cube_58_21.png?direct |}}
16.点击Code Generator,进行进一步配置
{{ :icore4tx:icore4tx_cube_58_22.png?direct |}}
  * **Copy all used libraries into the project folder**
  * 将HAL库的所有.C和.H都复制到所建工程中
    * 优点:这样如果后续需要新增其他外设又可能不再用STM32CubeMX的时候便会很方便
    * 缺点:体积大,编译时间很长
  * **Copy only the necessary library files**
  * 只复制所需要的.C和.H(推荐)
    * 优点:体积相对小,编译时间短,并且工程可复制拷贝
    * 缺点:新增外设时需要重新用STM32CubeMX导入
  * **Add necessary library files as reference in the toolchain project configuration file**
  * 不复制文件,直接从软件包存放位置导入.C和.H
    * 优点:体积小,比较节约硬盘空间
    * 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径
  * 自行选择方式即可
  * **Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral**
  * 每个外设生成单独的.c和.h文件
  * **Backup previously genareated files when re-generating**
  * 重新生成时备份以前产生的文件
  * **Keep User Code when re-generating**
  * 重新生成时保留用户代码
  * **Delete previously generated files when not re-generated**
  * 重新生成时删除以前生成的文件
  * **Set all free pins as analog (to optimize the power consumption)**
  * 没用到的引脚设置为模拟状态
17.然后点击GENERATE CODE  创建工程
{{ :icore4tx:icore4tx_cube_58_23.png?direct |}}
{{ :icore4tx:icore4tx_cube_58_24.png?direct |}}
创建成功,打开工程。
\\
\\
\\
\\
===== 实验五十八:MDK FLM实验——直接下载QSPI Flash =====
==== 一、 实验目的与意义 ====
  - 了解STM32的QSPI结构。
  - 了解STM32的QSPI特征。
  - 掌握STM32的QSPI的使用方法。
  - 掌握MDK FLM的使用方法。
  - 掌握KEIL MDK 集成开发环境使用方法。
==== 二、 实验设备及平台 ====
  - iCore4TX 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.3.29da532fLkazHH&id=614919247574|点击购买]]。
  - JLINK(或相同功能)仿真器。[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]
  - Micro USB线缆。
  - Keil MDK 开发平台。
  - STM32CubeMX开发平台。
  - 装有WIN XP(及更高版本)系统的计算机。
==== 三、 实验原理 ====
  * 绝大部分 STM32 系列芯片内部都具有 FLASH 存储器用于存储程序,安装芯片包后即自带相应的 FLASH 下载算法,在工程里直接选择即可。而 STM32H750 系列芯片内部只有一个 128KB 的 FLASH 存储器, 代码量稍微大一点就放不下了,所以其程序代码需要存放在外部的存储器中,使用时需要自己提供对应FLASH 的下载算法给 KEIL 软件。
  * 外部闪存的使用提供了更高的存储能力,并且具有类似的性能水平,同时提供了经济高效的解决方案来满足增加的闪存空间的需求。
  * **外部存储器代码执行原理:**
  * STM32 Cube H7固件软件包提供了多个应用程序,以演示如何从内部闪存启动以及如何配置外部存储器并跳转到用户应用程序(位于外部存储器上)。有两种可用的方案:XiP和BootROM。
  * •XiP方案旨在从外部闪存(QSPI或FMC-NOR闪存)“就地执行”。用户应用程序代码应与目标执行存储器地址(外部QSPI或FMC-NOR闪存)链接。
  * •BootROM方案旨在演示如何从内部闪存启动,配置外部RAM存储器(SDRAM或SRAM),将用户应用程序二进制文件从代码存储区(SDCARD或SPI-Flash存储器)复制到外部SDRAM。或外部SRAM,然后跳转到用户应用程序。用户应用程序代码应与目标执行存储器地址(externalSDRAM或SRAM)链接。
  * 外部存储器启动应用程序负责初始化所需的资源,以使外部存储器可用。该应用程序根据用户配置初始化所需的资源。
  * 外部存储器启动应用程序必须设置主堆栈指针,并配置要在外部存储器上执行的应用程序。 这种类型的引导方案支持大量的用户应用程序。外部存储器引导应用程序可确保在设置之后不再需要任何资源都可以重置或释放,然后再跳转到用户应用程序。下图说明了该引导方案。
{{ :icore4tx:icore4tx_arm_hal_58_1.png?direct&700 |}} 
  * 控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL 校准”的电平信号,才能实现通讯。
  * **XiP:**
  * XiP模型基于直接从用于代码存储的外部非易失性存储器执行代码。此执行模型需要内存映射支持,以授予CPU对执行代码用户应用程序的直接访问权限。通过FMC / QSPI接口可在外部NOR / QSPI闪存上使用XiP模型。
  * 以下流程图说明了XiP模型的操作流程。
{{ :icore4tx:icore4tx_arm_hal_58_2.png?direct&700 |}} 
  * **BootROM:**
  * BootROM模型基于所选易失性存储器中的代码执行。 当二进制数据存储在没有内存映射接口的内存中时(例如对于SDCARD),此执行模型是合适的。 当二进制数据以低吞吐量存储在存储器中时(例如SPI-NOR(使用带有1行的QSPI模拟),该模型也适用。
  * 以下流程图说明了BootROM模型的操作流程。
{{ :icore4tx:icore4tx_arm_hal_58_3.png?direct&700 |}} 
  * **用户配置由以下定义定义:**
  * •DATA_AREA:用于指定用于数据保存的易失性存储器。支持的内存(取决于所使用的板)是:
    * –USE_EXTERNAL_SDRAM:外部SDRAM用于保存数据
    * –USE_EXTERNAL_SRAM:外部SRAM用于保存数据
    * –USE_EXTERNAL_PSRAM:使用外部PSRAM进行数据保存
    * –USE_INTERNAL_SRAM:内部SRAM用于保存数据
  * •CODE_AREA:用于指定用户应用程序的执行位置。对于BootROM模式,此区域可以是易失性内存;对于XiP模式,此区域可以是非易失性。支持的内存(取决于所使用的硬件)是:
    * –XiP模型:BINARY_AREA必须未定义:
    * USE_QSPI:QSPI Flash用于执行代码
    * USE_NOR:FMC-NOR用于执行代码
    * –BootROM模型:必须定义BINARY_AREA
    * USE_EXTERNAL_SDRAM:外部SDRAM用于代码执行
    * USE_EXTERNAL_SRAM:外部SRAM用于代码执行
    * USE_EXTERNAL_PSRAM:外部PSRAM用于代码执行
    * USE_INTERNAL_SRAM:内部SRAM用于代码执行
  * •BINARY_AREA:仅在BootROM模型中定义。它用于指定包含用户应用程序的二进制文件的位置。根据所选的配置,还需要其他定义。支持的内存(取决于所使用的硬件):
    * –USE_SPI_NOR:SPI NOR闪存用于二进制存储
    * BINARY_BASE_OFFSET:SPI NOR闪存中二进制文件的偏移量
    * BINARY_SIZE:二进制图像的大小
    * –USE_SDCARD:SDCard用于二进制存储
  * •BINARY_FILENAME:要执行的二进制文件的名称
    * 用户应确保所选的存储器包含代码和数据,以至少覆盖正确的用户启动应用程序。之后,用户应用程序可以初始化所需的任何其他内存。
|  引导模型  |  存储器  |
|  XiP  |  QSPI Flash memory  |
|  :::  |  NOR Flash memory(on FMC)  |
|  BootROM  |  SPI-NOR(emulated with QSPI 1 line)  |
|  :::  |  SDCARD  |
|  Volatile memory  |  Internal SRAM  |
|  :::  |  External SRAM  |
|  :::  |  External SDRAM  |
|  :::  |  External PSRAM  |
  * 外部存储器用户应用程序的描述:
  * 必需的更新
  * 外部存储器应用程序基于一种特定的引导模式,该模式不同于标准引导模式,并且支持从片上应用程序到片外应用程序的平稳过渡。
  * 随着应用程序位置的更改,用户必须完成两个更新:
    * •确保使用所需的链接器文件以及与所选引导选项相对应的内存映射。
    * •更新VTOR的设置以使用正确的地址。
  * **加载和调试**
  * STM32 Cube H7中提供EWARM IDE的补丁与MDK-ARM IDE专用包。
  * XiP模型提供了类似于内部Flash调试的无缝加载和调试体验。使用STM32 Cube Programmer在外部Flash存储器上加载应用程序。
  * 在BootROM模型中,应用程序被编译并链接以从外部易失性存储器执行:
    * •外部SDRAM:对于STM32H750,链接器地址为0xD0000000。
    * •外部SRAM:用于STM32H750的链接器地址0x68000000,然后将应用程序二进制文件存储到SPI_NOR闪存或SDCARD中。引导应用程序将用户应用程序从存储区复制到执行RAM区域,因此,IDE(MDK-ARM或EWARM)外部内存Flash loader无法处理应用程序的加载模式(作为应用程序的链接地址)和存储地址不同)。
  * 根据BINARY_AREA定义(在启动应用程序的“ memory.h”文件中指定),此模型要求使用以下两种不同的加载模式:
    * •SPI_NOR
      * 用户应用程序应存储在SPI-NOR闪存中,地址为0x90000000。必须使用STM32 Cube Programmer完成。应用程序的输出应为二进制格式,以便能够指定另一个加载地址,即SPI-Flash地址。
    * •SD卡
      * 用户应将二进制文件(内部版本的输出)手动复制到用于存储用户应用程序的SDCARD中,然后将SDCARD插入开发板。
  * 在本实验中,我们通过编写Bootloader程序,使用给定的算法引导APP程序下载到iCore4T 的QSPI FLASH中。
==== 四、 实验程序 ====
=== 1.主函数 ===
int main(void)
{
  int flash_id;
  CPU_CACHE_Enable();
  HAL_Init();
  i2c.initialize();
  axp152.initialize();
  axp152.set_dcdc1(3500);//[ARM & FPGA]
  axp152.set_dcdc2(1200);//[FPGA INT]
  axp152.set_dcdc3(3300);//[DCOUT3]
  axp152.set_dcdc4(3300);//[DCOUT4]
  axp152.set_aldo1(3300);//[BK3]
  axp152.set_aldo2(3300);//[ALDOOUT2]
  axp152.set_dldo1(3300);//[BK0]
  axp152.set_dldo2(3300);//[BK1]
  HAL_Delay(200);
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_FMC_Init();
  MX_USART2_UART_Init();
  MX_QUADSPI_Init();
  MX_ADC1_Init();
  MX_ADC3_Init();
  MX_SDMMC1_SD_Init();
  MX_FATFS_Init();
  MX_SPI4_Init();
  BSP_QSPI_Init();
  usart2.initialize(115200);
  usart2.printf("iCore4TX Bootloader(W25Q64) V%s\r\n",VER);
  
  /* 初始化 w25q64 */
  W25QXX_ExitQPIMode();
  W25QXX_Reset();
  flash_id = BSP_QSPI_FLASH_ReadID();
  W25QXX_EnterQPIMode();  
   
  if(flash_id == 0xEF4017){
    usart2.printf("FLASH(W25Q64) init success!\r\n");
    usart2.printf("Jump to Flash!\r\n");
  }else{
    usart2.printf("FLASH(W25Q64) init fail!\r\n");
    while(1){
      HAL_Delay(50);
      LED_ON;
      HAL_Delay(50);
      LED_OFF;
    }
  }
  LED_ON;
  QSPI_EnableMemoryMappedMode(&hqspi);
  CPU_CACHE_Disable();
  SysTick->CTRL = 0;
  /* 初始化用户应用程序的堆栈指针并跳转到用户应用程序*/
  JumpToApplication = (pFunction) (*(__IO uint32_t*) (APPLICATION_ADDRESS + 4));
  __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
  JumpToApplication();
  while (1)
  {
  }
}
=== 2.QSPI FLASH退出QPI模式 ===
void W25QXX_ExitQPIMode(void)
{   
    QSPI_CommandTypeDef cmd;
    
    cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    cmd.Instruction = W25X_ExitQPIMode;
    
    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00; 
    
    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;
    
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;
    
    cmd.DummyCycles = 0;
    
    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    
    HAL_QSPI_Command(&hqspi, &cmd, 100);
    
    w25qxx_mode = W25QXX_MODE_SPI;
}
 
=== 3.QSPI FLASH进入QPI模式 ===
void W25QXX_EnterQPIMode(void)
{
    uint8_t dat;
    
    QSPI_CommandTypeDef cmd;
    
    dat = W25QXX_ReadSR(2); //先读出状态寄存器2的原始值
    if ((dat & QE_MASK) == 0x00) //QE位未使能
    {
        W25QXX_WriteEnable(1); //写使能
        dat |= QE_MASK; //使能QE位
        W25QXX_WriteSR(2, dat); //写状态寄存器2
    }
    
    cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    cmd.Instruction = W25X_EnterQPIMode;
    
    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00; 
    
    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;
    
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;
    
    cmd.DummyCycles = 0;
    
    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    
    HAL_QSPI_Command(&hqspi, &cmd, 100);
    
    w25qxx_mode = W25QXX_MODE_QPI;
    
    cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    cmd.Instruction = W25X_SetReadParameters;
    cmd.DataMode = QSPI_DATA_4_LINES;
    cmd.NbData = 1;
    dat = 0x03 << 4; //设置P4&P5=11,8个dummy clocks,104MHz
    W25QXX_WriteEnable(1);
    if (HAL_QSPI_Command(&hqspi, &cmd, 100) == HAL_OK)
    {
        HAL_QSPI_Transmit(&hqspi, &dat, 100);
    }
}
=== 4.QSPI FLASH复位 ===
void W25QXX_Reset(void)
{
    QSPI_CommandTypeDef cmd;
    if (w25qxx_mode)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    }
    cmd.Instruction = W25X_EnableReset;
    
    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0;
    
    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;
    
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;
    cmd.DummyCycles = 0;
    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    
    W25QXX_WaitBusy();
    if (HAL_QSPI_Command(&hqspi, &cmd, 100) == HAL_OK)
    {
        cmd.Instruction = W25X_ResetDevice;
        HAL_QSPI_Command(&hqspi, &cmd, 100);
    }
}
=== 5.写QSPI FLASH ===
//写SPI FLASH
void W25QXX_Write(uint8_t *pbuf, uint32_t addr, uint16_t size)
{
  uint32_t sec_pos;
  uint32_t sec_off;
  uint32_t sec_rem;
  uint32_t i;
  uint8_t *W25QXX_BUF;
  
  W25QXX_BUF = w25qxx_buf;
  sec_pos = addr / 4096; //扇区地址
  sec_off = addr % 4096; //在扇区内的偏移
  sec_rem = 4096 - sec_off; //扇区剩余空间大小
  #ifdef DEBUG
  printf("addr:%08X size:%hu\r\n", addr, size); //测试用
  #endif
  if(size <= sec_rem)
  {
    sec_rem = size; //不大于4096个字节
  }
  while(1)
  {
    W25QXX_Read(W25QXX_BUF, sec_pos * 4096, 4096); //读出整个扇区的内容
    for (i = 0; i < sec_rem; i++) //校验数据
    {
      if (W25QXX_BUF[sec_off + i] != 0xFF)
        break; //需要擦除
    }
    if (i < sec_rem) //需要擦除
    {
      W25QXX_SectorErase(sec_pos); //擦除这个扇区
      for (i = 0; i < sec_rem; i++) //复制
      {
        W25QXX_BUF[sec_off + i] = pbuf[i];
      }
      W25QXX_Write_NoCheck(W25QXX_BUF, sec_pos * 4096, 4096); //写入整个扇区
    }
    else
    {
      W25QXX_Write_NoCheck(pbuf, addr, sec_rem); //写已经擦除了的,直接写入扇区剩余区间.
    }
    if (size == sec_rem)
    {
      break; //写入结束了
    }
    else //写入未结束
    {
      sec_pos++; //扇区地址增1
      sec_off = 0; //偏移位置为0
      pbuf += sec_rem; //指针偏移
      addr += sec_rem; //写地址偏移
      size-=sec_rem;        //字节数递减
      if (size > 4096)
      {
        sec_rem = 4096; //下一个扇区还是写不完
      }
      else
      {
        sec_rem = size; //下一个扇区可以写完了
      }
    }
  }
}
=== 6.BSP_QSPI_Erase_Block函数 ===
uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress)
=== 7.BSP_QSPI_Write函数 ===
uint8_t BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
==== 五、 实验步骤 ====
  * 1、把仿真器与iCore4TX的SWD调试口相连(直接相连或者通过转接器相连);
  * 2、把iCore4TX通过Micro USB线与计算机相连,为iCore4TX供电;
  * 3、打开 Keil MDK 开发环境,并打开bootloader_flash工程;
  * 4、烧写程序到 iCore4TX内部FLASH上;
{{ :icore4t:icore4t_arm_hal_58_4.png?direct |}} 
  * 5、把FLM文件复制到 KEIL 的安装目录,如:D:\Keil_v5\ARM\Flash;
  * 6、选择下载算法;
{{ :icore4tx:icore4tx_arm_hal_58_5.png?direct |}} 
  * 7、设置算法空间,取消校验;
{{ :icore4tx:icore4tx_arm_hal_58_6.png?direct |}} 
  * 8、烧写程序到 iCore4TX 的QSPI FLASH上;
  * 9、也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。
==== 六、 实验现象 ====
  * iCore4TX核心板ARM_LED不断闪烁。