QSPI

相关阅读:

mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境

mm32-2nd-bootloader技术白皮书(2)——QSPI外设简介

mm32-2nd-bootloader技术白皮书(3)——设计实现QSPI Flash的下载算法

mm32-2nd-bootloader技术白皮书(4)——设计实现简单的2nd bootloader

引言

在前文中已经实现了一个能够在 MDK 平台进行下载代码到 QSPI Flash 中的下载算法,以及一个能跳转执行应用程序的 2nd Bootloader,但若想将代码下载到 QSPI Flash上并运行,仍需对所需执行的代码文件进行少量的修改,使其能够在 QSPI Flash 上运行。

修改Linker文件

本文将 MindSDK 的 PLUS-F5270 hello_world 样例工程作为所需执行的文件,可通过 MindSDK 官网 (https://mindsdk.mindmotion.com.cn/) 获取该样例,如图 1 所示。

1.png

图1 通过官网获取 MindSDK PLUS-F5270 hello_world样例工程

打开 hello_world 样例工程,在 Options for Target 选项的Linker页面下,找到 Scatter File 选项,该选项中内容为 Linker 所使用的 Scatter File 文件路径,点击该选项右侧的 Edit ,此时样例工程中会弹出对应的 Scatter File 文件界面,如图 2 所示。

2.png

图2 打开scatter file文件界面

在弹出的 mm32f5277e_flash.scf 文件中,需要根据  QSPI 的存储器映像配置 __ROM_BASE 的数值,QSPI 外设的基础地址为 0x90000000,这个地址值来自于微控制器的用户手册的 “地址映射” 章节,如图 3 所示。因此,__ROM_BASE的数值应该为 0x90000000。

3.png

图3 QSPI 外设的存储器映像编址范围

QSPI Flash 的大小为8MB,其中 8MB = 8 * 1024 *1024 = 8388608,换算为 16 进制是 0x00800000,因此,__ROM_SIZE 的数值应该为 0x00800000。

Scatter File文件的内容修改如下:

...    
/*--------------------- Flash Configuration ----------------------------------
; <h> Flash Configuration
;   <o0> Flash Base Address <0x0-0xFFFFFFFF:8>
;   <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
 *----------------------------------------------------------------------------*/
#define __ROM_BASE      0x90000000
#define __ROM_SIZE      0x00800000
...

修改源代码

在配置好 Linker 所需的 Scatter File 文件后,需要对将要执行的文件进行检查,去除可能会影响到 QSPI Flash 运行的代码,需要检查的点如下:

  • 检查时钟初始化部分:

    是否存在复位 QSPI 模块的时钟或复位 QSPI 所使用的 GPIO 引脚时钟的情况

    若存在复位 QSPI 模块,则处理器将无法从 QSPI Flash 中读取下一个要执行的指令。

    若存在复位 QSPI 使用引脚的时钟,则运行到引脚时钟复位后,GPIO 复位,引脚不再作为 QSPI 的接口,因此将会影响到 QSPI 与 QSPI Flash 之间的通信。

    在 “hello_world” 工程中,以上代码可能会出现在 board 目录下的 “clock_init.c” 中。

  • 检查引脚初始化部分:

    是否占用 QSPI 所使用的引脚

    若引脚初始化时,占用 QSPI 所需引脚,将会影响与 QSPI Flash 的通信

    在 “hello_world” 工程中,以上代码可能会出现在 board 目录下的 “pin_init.c” 中。

  • 不能出现以间接模式操作 QSPI Flash 的代码

    若执行间接模式操作 QSPI Flash 的代码,会中断直接读模式,而直接读模式是执行存储在 QSPI Flash 的应用程序的基础,因此,执行间接模式操作 QSPI Flash 的代码会使应用程序跑飞。

    在 “hello_world” 工程中,没有执行间接模式操作 QSPI Flash 的代码。

添加下载算法到可执行文件

该可执行样例最终将在 QSPI Flash 中存储并运行,因此,需要添加根据实际所使用的 QSPI Flash芯片而配置的下载算法到样例工程中,以帮助调试器将指定位置的数据写入 Flash。

以配置完成的 PLUS-F5270 hello_world 样例工程为例,打开 Options for Target 的 Debug 页面,选择 Settings 选项进入 Cortex Jlink/JTrace Target Driver Setup 页面的 Flash Download ,选择 Add 选项,进入下载算法选择列表,选择前文中设置好的 MM32F5270 QSPI FlashLoader 下载算法,点击 Add 进行添加,如图 4 所示。

4.png

图4 添加下载算法到可执行文件中

此处需注意,若下载算法的大小大于 Flash Download 中 RAM for Algorithm 的 Size 选项中的大小,会导致在下载时出现 "Cannot Load Flash Programming Algorithm" 的问题,此时需适当将 Size 的大小调大一些,本文中设置 Size 为 0x2000。

验证

配置完成在 QSPI Flash 上运行的可执行文件后,可尝试使用下载算法将整个工程下载到 QSPI Flash 中并使用 2nd Bootloader 执行这个工程。

先将前文中实现运行在片内 Flash 的 2nd Bootloader 下载到 PLUS-F5270 开发板中,再将已经配置好的添加了下载算法的 hello_world 样例工程下载到开发板中的 QSPI Flash 中。下载成功后复位微控制器,通过串口调试器可看到输出字符 "hello_world"。

5.png

图5 使用QSPI Flash的hello_world样例工程运行结果

在调试模式下,可以从 Disassemby 窗口观察到进入main函数后地址处于 0x90000950,属于 QSPI Flash 的范围内,由此可见,整个 hello_world 下载到 QSPI Flash中并且正确运行。

6.png

图6 通过调试查看样例起始地址

至此,已经验证了下载算法可用, 2nd Bootloader 可用,修改后的样例工程也可用。

但仍需验证下中断是否可用,本文修改 hello_world 工程,验证 SysTick_Handler() 能否正确执行。

volatile uint32_t systime = 0u;

int main(void)
{
    uint8_t ch;

    BOARD_Init();

    printf("hello, world\r\n");

    SysTick_Config(CLOCK_SYSTICK_FREQ / 1000u);

    while (1)
    {
        if (systime > 1000)
        {
            systime = 0;
            putchar('*');
        }
    }
}

void SysTick_Handler()
{
    systime++;
}

将修改后的样例工程下载并运行在 QSPI Flash 上,其结果如图 7 所示。

7.png

图7 验证SysTick中断可用的样例结果

至此,也验证了中断也可正确执行。

总结

为了编译可在QSPI Flash上运行的可执行文件,需要作出以下两件事:

修改 Linker 文件,将 ROM 的位置和大小修改为 QSPI Flash 的映射地址和大小

修改源代码中所有可能会影响访问QSPI Flash的代码,例如 GPIO 的配置,时钟的配置等

可以发现编译可在 QSPI Flash上运行的可执行文件并不难,因此可以轻松将应用程序迁移到 QSPI Flash 里。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 29

相关阅读:

mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境

mm32-2nd-bootloader技术白皮书(2)——QSPI外设简介

简介

前文中了解了微控制器可以使用 QSPI 接口连接 QSPI Flash,来实现扩展微控制器的 Flash 空间,存储较大 code size 的应用程序。

但通过 QSPI 接口连接的 QSPI Flash,无法直接使用已有的片内 Flash 的下载算法下载程序,例如在 MDK 平台上编译的程序,在没有下载算法的情况下,是不能直接点击 MDK 的下载按钮或调试按钮下载调试程序的,这样会对使用 QSPI Flash 存储执行程序带来很大的麻烦。

因此,本文将使用 MDK 平台,以 PLUS-F5270 开发板为例,讲解如何制作适用于 MM32F5270 系列 MCU 的 QSPI Flash 下载算法,让用户在 MDK 平台中下载应用程序就如同在片内 Flash 中那样方便。

生成下载算法文件

在 Keil-MDK 的安装目录下,包含一个下载算法的模板文件,例如:MDK 安装在 C 盘目录下,则工程模板的位置在 "C:\Keil_v5\ARM\Flash" ,模板名称为 "_Template",如图 1 所示。

1.png

图1 下载算法模板的目录与包含文件

复制整个 "_Template"文件到该目录下,并重命名为“MM32F5270_QSPI_FlashLoader”,当然也可以换成其它名字;进入该文件夹,将所有名为 "NewDevice" 的文件重命名,建议同文件夹名保持一致。

重命名文件后,打开 ".uvprojx" 后缀的工程文件,进入 MDK 工程,在 Project 栏中可以看到,除了 "Abstract.txt" 文件外,只有 "FlashPrg.c" 与 "FlashDev.c" 两个文件。

修改MDK工程选项配置

打开 Options for Target,进入 Device 页面,PLUS-F5270 使用的微控制器为 MM32F5277E9PV,在左下角的芯片选择中,选择 MindMotion -> MM32F5270 Series->MM32F5277E -> MM32F5277E9PV"。

由于 MM32F5270 系列芯片使用 STAR-MC1 内核处理器,不能使用 AC5 编译器编译代码,需选择 Target 页面,将 Code Generation 的 ARM Compiler 版本选择为 Use default compiler version 6。

选择 Output 页面,修改 Name of Executable 选项中的内容以表示设备,建议该修改名称与文件夹保持一致。修改页面如图 2 所示。

2.png

图2 MDK的工程选项配置界面

由于下载算法通常是在 Flash 目录下开发,使用下载算法时要将下载算法放到 Flash 目录下,避免调试下载算法时,来回将下载算法文件复制到前一级目录。因此,在工程选项配置中,将输出下载算法的路径配置到前一级目录下。选择 User 页面,修改 After Build/Rebuild 的Run #1为cmd.exe /C copy "Objects\%L" "..\@L.FLM",如图 3 所示。

3.png

图3 Keil-MDK的输出下载算法路径配置界面

Flash 编程算法使用 Read-Only Position Independent 与 Read-Write Position Independent。通过观察 "MM32F5270_QSPI_FlashLoader" 文件夹下的 "Target.lin" 文件可知,不管是代码还是数据,都被放入与地址无关的空间内,因此在编译时,要将代码和数据以“和地址无关”的方式编译。选择 C/C++(AC6) 页面,勾选 Read-Only Position Independent 与 Read-Write Position Independent;选择 Asm 页面,也勾选 Read-Only Position Independent 与  Read-Write Position Independent 选项,如图 4 所示。

4.png

图4 Keil-MDK的工程算法编译方式选项配置界面

此处需注意要将 C/C++(AC6) 页面下的 Warnings: 选项选为AC5-like Warnings,否则会产生路径名不同的 warning。

FlashDev.c(25): warning: non-portable path to file '"..\FlashOS.h"'; specified path differs in case from file name on disk [-Wnonportable-include-path]
#include "..\FlashOS.H"        // FlashOS Structures
         ^~~~~~~~~~~~~~
         "..\FlashOS.h"

查看整个 MM32F5270_QSPI_FlashLoader 工程文件,会发现当前工程中没有启动文件与主函数,默认只有几个功能函数,在这种情况下,编译会爆出下面的警告:

L6305W: Image does not have an entry point. (Not specified or not set due to multiple choices.)

但下载算法本身是不需要启动文件和主函数的,只在下载代码时由调试器调用下载算法,因此,需忽略该 warning。选择 Linker 界面,修改 Misc controls 中的内容为 --diag_suppress L6305 ,如图 5 所示。

5.png

图5 配置 MDK 工程禁用错误 L6305

修改FlashDev.c文件

在 MM32F5270_QSPI_FlashLoader工程文件中,FlashDev.c 文件的内容由实际所使用的 QSPI Flash 芯片决定,该文件中包含一个名为 FlashDevice 的结构体,本文以 W25Q128 芯片为例,作为与 MM32F5270 MCU 相连接的 QSPI Flash 芯片,该芯片容量为 16MB,扇区大小为 4KB,页大小为 256Byte。则结构体FlashDevice 的配置如下:

    struct FlashDevice const FlashDevice  =  {
   FLASH_DRV_VERS,              // Driver Version, do not modify!
   "MM32F5270 QSPI FlashLoader",// Device Name 
   EXTSPI,                      // Device Type
   0x90000000,                  // Device Start Address
   0x01000000,                  // Device Size in Bytes (16MB)
   256,                         // 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
   0x001000, 0x000000,          // Sector Size  4kB (1 Sectors)
   SECTOR_END
};

若使用 W25Q64 芯片,其芯片容量 8MB,Device Size in Bytes 行的值需修改为 0x00800000。

修改好后,先进行一次编译,然后在 MM32F5270 的任意工程中查看列表,以MM32F5270中的 hello_world 样例工程为例。

打开 hello_world 样例工程的 Options for Target 选项,到 Debug  页面下,打开调试器的 Settings, 到 Flash Download 页面下,进入 Add 选项,可看见列表中新增了一个名为 MM32F5270 QSPI FlashLoader 的下载算法,列表界面如图 6 所示。

6.png

图6 MDK 的下载算法添加列表

当然,这个下载算法还尚且不能使用,仍需要进行功能填充。若要让下载算法可用,首先,需要添加必要的文件,使用 MindSDK 生成任意一个 MM32F5270 的工程,提取其中的 device 目录到下载算法的目录下。

下载算法需要使用到的文件如图 7 所示:

7.png

图7 在工程中添加下载算法所需文件

添加完所需文件后,需添加头文件地址才能使用对应文件,如图 8 所示。

8.png

图8 在工程中添加头文件地址

为什么没有把所有的驱动文件都包含进去呢?这是由于 FlashLoader 需尽可能的小,如果把所有的驱动都添加进去的话,下载算法就会变得很大很大,要知道,下载算法没有启动文件和主函数,编译器不知道哪些函数没有用到,因此不会进行优化,将没有用到的函数删除。因此仅添加 GPIO,QSPI 和 RCC 三个必须使用到的驱动,如有必要,甚至可以写好下载算法后,将这三个驱动文件中没有用到的函数也进行删除。

QSPI Flash芯片的驱动开发

要想将代码下载到 QSPI Flash 中,就要根据 QSPI Flash 手册中的对应要求来操作 QSPI Flash,为此,需要为 QSPI Flash 芯片设计驱动,当然,为了减少代码量,仅实现必要的功能。

QSPI Flash 支持单线,双线和四线操作,本文为方便编程,仅描述实现单线模式的操作,且认为 QSPI Flash 没有对任何块实施写保护等操作,同时,QSPI Flash 没有进入所谓的 QPI 模式。当然,上述特殊需求也能够在 Flashloader 中实现,但本文不再详细赘述如何实现。

在 FlashPrg.c 文件中设计并添加 QspiFlash_Init()、QspiFlash_IsBusy()、QspiFlash_EnableWrite()、QspiFlash_EraseSector()、QspiFlash_WritePage() 函数作为 QSPI Flash 芯片的驱动函数。

QspiFlash_Init() 函数

设计函数QspiFlash_Init()用于初始化,该函数包括 GPIO 的初始化,QSPI 模块的初始化,QSPI 直接模式读的初始化,由于仅使用单线模式,故无需额外打开 QSPI Flash 的四线模式。以上初始化的需求如下:

  • GPIO 的初始化

    配置 GPIO 使其作为 QSPI 的信号线,供 QSPI 外设与 QSPI Flash 进行通信。

  • QSPI 模块的初始化

    使其能够与 QSPI Flash 进行通信,实现读数据和写数据的功能。

  • QSPI 直接模式读的初始化

    为了使系统能够通过访问地址的形式直接读取到 QSPI Flash 中的数据,调试器下载程序后,会通过访问地址的形式读取 QSPI Flash 中的数据,与下载的数据进行对比,实现校验功能。

需要注意的是,GPIO 要根据 QSPI 实际使用到的引脚进行配置,QSPI Flash 的初始化要根据 QSPI Flash 的数据手册进行调整,可能部分 QSPI Flash 在读写数据之前,会有一些针对 QSPI Flash 的初始化操作。

void QspiFlash_Init(void)
{
    /* enable periph clock. */
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOA
                        | RCC_AHB1_PERIPH_GPIOB
                        | RCC_AHB1_PERIPH_GPIOG
                        | RCC_AHB1_PERIPH_QSPI
                        , true);

    /* enable gpio. */
    GPIO_Init_Type gpio_init;
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    gpio_init.Speed   = GPIO_Speed_50MHz;

    gpio_init.Pins    = GPIO_PIN_3; /* QSPI_DA1. */
    GPIO_Init(GPIOA, &gpio_init);
    GPIO_PinAFConf(GPIOA,gpio_init.Pins, GPIO_AF_10);

    gpio_init.Pins    = GPIO_PIN_3 | GPIO_PIN_10; /* QSPI_DA2 & QSPI_CS. */
    GPIO_Init(GPIOB, &gpio_init);
    GPIO_PinAFConf(GPIOB,gpio_init.Pins, GPIO_AF_10);

    gpio_init.Pins    = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8; /* QSPI_DA0, QSPI_SCK & QSPI_DA3. */
    GPIO_Init(GPIOG, &gpio_init);
    GPIO_PinAFConf(GPIOG,gpio_init.Pins, GPIO_AF_10);

    /* enable qspi. */
    QSPI_Init_Type qspi_init;
    qspi_init.SpiMode           = QSPI_SpiMode_3;
    qspi_init.SckDiv            = 8u;
    qspi_init.CsHighLevelCycles = 9u;
    qspi_init.RxSampleDelay     = 1u;

    QSPI_Init(QSPI, &qspi_init);

    /* enable qspi direct xfer mode. */
    QSPI_DirectXferConf_Type qspi_direct_conf;
    qspi_direct_conf.CmdBusWidth  = QSPI_BusWidth_1b;
    qspi_direct_conf.CmdValue     = 0x0B; /* fast read. */
    qspi_direct_conf.AddrBusWidth = QSPI_BusWidth_1b;
    qspi_direct_conf.AddrWordWidth    = QSPI_WordWidth_24b;
    qspi_direct_conf.AltBusWidth  = QSPI_BusWidth_None;
    qspi_direct_conf.DummyCycles  = 8u;
    qspi_direct_conf.DataBusWidth = QSPI_BusWidth_1b;

    QSPI_EnableDirectRead(QSPI, &qspi_direct_conf);
}

QspiFlash_IsBusy() 函数

QspiFlash_IsBusy() 函数通过读取 QSPI Flash 的寄存器值,确定 QSPI Flash 是否处于忙碌状态。在向 QSPI Flash 中写入数据之后,需要使用该函数等待 QSPI Flash 将写进去的数据完全烧写到 Flash 单元中。

判断本文使用的 QSPI Flash 是否忙碌的方法是:读取这个 QSPI Flash 寄存器 2 中的第一个比特位是否为1,若为1 则是忙碌状态。

具体的判断方法,还是需要根据使用的 QSPI Flash 手册来确定判断方法。

bool QspiFlash_IsBusy(void)
{
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x05; /* get reg1 value. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
    xfer_conf.DataLen       = 1;

    QSPI_SetIndirectReadConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}

    uint32_t reg1 = QSPI_GetIndirectData(QSPI);

    if (0 == (reg1 & 0x01) )
    {
        return false;
    }
    else
    {
        return true;
    }
}

QspiFlash_EnableWrite() 函数

QspiFlash_EnableWrite() 函数主要是为了进行写使能操作,保证数据可写入。

每次进行任何写操作时,都需要执行一次写使能操作。需要注意,每一次写操作完成后,写使能就会关闭(当然,还需视具体芯片而定)。

void QspiFlash_EnableWrite(bool enable)
{
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    if (true == enable)
    {
        xfer_conf.CmdValue      = 0x06; /* enbale write. */
    }
    else
    {
        xfer_conf.CmdValue      = 0x04; /* disable write. */
    }
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_None;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}
}

QspiFlash_EraseSector() 函数

QspiFlash_EraseSector() 函数执行擦除扇区操作。

void QspiFlash_EraseSector(uint32_t addr)
{
    QspiFlash_EnableWrite(true);
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x20; /* sector erase. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
    xfer_conf.AddrValue     = addr & 0x00FFFFFF;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_None;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}

    while(QspiFlash_IsBusy() ) /* wait for erase done. */
    {}

    QspiFlash_EnableWrite(false);
}

QspiFlash_WritePage() 函数

QspiFlash_WritePage() 函数用于执行写数据操作。

注意,擦数据是按块(或者叫做扇区)擦除,写操作是按页进行写,页的大小是小于块的大小的。块大小和页大小已经在前面的 FlashDev.c 中定义好了,调试下载器会按照上面定义的大小进行块擦除和页写入。

void QspiFlash_WritePage(uint32_t addr, uint32_t size, uint8_t * data)
{
    QspiFlash_EnableWrite(true);
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x02; /* sector erase. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
    xfer_conf.AddrValue     = addr & 0x00FFFFFF;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
    xfer_conf.DataLen       = size;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {
        if (size > 0 && 0 != (QSPI_GetStatus(QSPI) & QSPI_STATUS_FIFO_EMPTY) )
        {
            QSPI_PutIndirectData(QSPI, *data++);
            size--;
        }
    }

    while(QspiFlash_IsBusy() ) /* wait for erase done. */
    {}

    QspiFlash_EnableWrite(false);
}

修改FlashPrg.c文件

在 MM32F5270_QSPI_FlashLoader 工程的 FlashPrg.c 文件中,根据用户手册对于 Flash 描述和 Flash 控制器的特性,适配编程算法。

使用上述QSPI Flash芯片驱动开发函数对FlashPrg.c文件中的模板函数进行填充。

Init()函数

初始化函数除了初始化 QSPI Flash 本身,还需要初始化 MCU 的时钟,使其工作在片内 RC 振荡器提供的 8M 时钟环境下。

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) 
{
    /* Switch to HSI. */
    RCC->CR |= RCC_CR_HSION_MASK; /* Make sure the HSI is enabled. */
    while ( RCC_CR_HSIRDY_MASK != (RCC->CR & RCC_CR_HSIRDY_MASK) )
    {
    }
    RCC->CFGR = RCC_CFGR_SW(0u); /* Reset other clock sources and switch to HSI. */
    while ( RCC_CFGR_SWS(0u) != (RCC->CFGR & RCC_CFGR_SWS_MASK) ) /* Wait while the SYSCLK is switch to the HSI. */
    {
    }

    /* Reset all other clock sources. */
    RCC->CR = RCC_CR_HSION_MASK;

    /* Disable all interrupts and clear pending bits. */
    RCC->CIR = RCC->CIR; /* clear flags. */
    RCC->CIR = 0u; /* disable interrupts. */

    QspiFlash_Init();
    return (0);                                  /* Finished without Errors. */
}

UnInit()函数

无需任何操作。

UnInit() 函数在下载程序的操作完成后被调用,如果 Flashloader 操作的是片内 Flash,可能会在该函数内实现一个对片内 Flash 上锁的操作,保证 Flash 中的数据不会被意外修改,而本文使用的 QSPI Flash 不需要类似的操作,因此这个函数不用做任何改动,当然,也得视具体的 QSPI Flash 而定。

EraseChip()函数

QSPI Flash 本身具有擦除整颗芯片数据的指令,但为了减少代码量,可以使用循环调用擦除扇区的方法来擦除整颗芯片的数据。

int EraseChip (void) 
{
    for (uint32_t i = 0; i < 0x01000000; i+= 0x1000)
    {
        QspiFlash_EraseSector(i);
    }
    return (0);                                  /* Finished without Errors. */
}

EraseSector() 函数

此函数的目的是擦除扇区。

需要注意的是,Flash_EraseSector() 需要传入的参数是 QSPI Flash 芯片内的地址,而不是在 MCU 上映射的地址,因此需要使用 0x00FFFFFF 掩码取有效地址。

int EraseSector (unsigned long adr) 
{
    QspiFlash_EraseSector(adr & 0x00FFFFFF);
    return (0);                                  /* Finished without Errors. */
}

ProgramPage()函数

此函数的目的是将程序写入 Flash 中。

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) 
{
    QspiFlash_WritePage(adr,sz,buf);
    return (0);                                /* Finished without Errors. */
}

总结

完成上述下载算法所需的函数后,下载算法也就制作完成了。使用这个下载算法,就可以实现下载程序到 QSPI Flash 的操作,但本文还没有讲该怎么实现这个可以存储在 QSPI Flash 中的应用程序,也没有讲解该怎么执行这个程序,所以暂时还没有办法验证这个下载算法是否可用,不过后续也将会讲解如何跳转执行存储在 QSPI Flash 中的应用程序,和如何让应用程序能够存储在 QSPI Flash 中。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 119

相关阅读:

mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境

SPI,DSPI 和 QSPI

在了解什么是 QSPI 之前,我们先了解下什么是 SPI:

SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口,一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,为 PCB 的布局上节省空间,提供方便,主要应用在 EEPROM,Flash,RTC,AD转换,传感器等设备上。

SPI 的四根信号线:

  • CS

    片选信号线,每个设备都占用一个片选信号线,当 CS 信号线显低电平时,表示该设备被选中,则 SCK,MOSI 和 MISO 信号有效。每个设备都拥有属于自己的 CS 信号线,主机选中设备时,会拉低指定设备的 CS 线电平。使用 CS 信号线,可以使多个设备共同使用同一份 SCK,MOSI 和 MISO 信号线,减少引脚数量,也可以告诉设备本次访问已结束,方便下次重新发起访问请求。

  • SCK

    时钟信号线,为 SPI 通信过程中提供时钟信号,当 SCK 每次产生上升沿或下降沿时,MOSI 和 MISO 传输一比特数据。

  • MOSI

    主机发送,从机接收信号线,主机通过该信号线发送数据,从机从该信号线接收到数据。

  • MISO

    主机接收,从机发送信号线,与 MOSI 相反,这里主机要接收数据,从机要发送数据。两根串行数据信号线保证了主从机之间可以全双工通信。

SPI 可以实现全双工通信,但与 SPI Flash 通信的时候反而不需要进行全双工通信:在 Host 不把指令和地址发送完之前,SPI Flash 不会知道 Host 要干嘛,自然不会主动向 Host 发送数据;在 SPI Flash 向 Host 发送数据的时候,Host 也没有必要发送任何数据,因此,当 Host 与 SPI Flash 通信时,总是处于半双工状态,也总有一根数据信号线处于空闲状态。如图 1 所示。

1.png

图1 SPI 模式访问 SPI Flash

为了把这跟信号线充分利用起来,从而发明了 DSPI 。

当 SPI Flash 准备给 Host 发送数据的时候,如果把 MOSI 也作为 SPI Flash 给 Host 发送数据的数据信号线使用,则每个 SCK 时钟可以发送两比特的数据:原来的MOSI (现在称之为 D0)发送第一个比特,MISO(现在称之为 D1)发送第二个比特,在不增加信号线的基础上,一个时钟发送两个比特数据,相当于传输速率提升了一倍!这就叫做 DSPI,D 是 Dual 的意思。如图 2 所示。

2.png

图2 DSPI 模式访问 SPI Flash

但 DSPI 的速度好像还是有些慢,如果再增加两条数据线(D3 和 D4),让传输速度提升到 DSPI 的两倍,那么 DSPI 就变成了 QSPI,Q 是 Quad 的缩写。

相比较于 SPI,QSPI 在数据传输的时候,一个 SCK 时钟能传输 4 比特数据,两个 SCK 时钟就能传输一字节的数据,性能提升,让 CPU 直接执行存储在 SPI Flash 的程序变成了可能。如图 3 所示。

3.png

图3 QSPI 模式访问 SPI Flash

能否再让数据引脚增加一倍,8 根数据线岂不是速度更快,那就了解下 FSMC 吧,直接 16 根数据信号线,速度自然变得更快了,但使用 SPI FLash,不就是图一个线少嘛。

为了方便描述,我们将使用 QSPI 接口连接的 SPI Flash 称之为 QSPI Flash。一般来说,在与 QSPI Flash 通信时,并不是全程都是四根数据信号线传输数据,当同一时间只有一根数据线在传输数据的模式可称为单线模式,两根数据线可称为双线模式,四根数据线可称为四线模式。

通信过程

在使用 QSPI 与 QSPI Flash 通信的过程中,可分为五个阶段:

  • 指令阶段

    传输方向为 Host 发送给 QSPI Flash,目的是告诉 QSPI Flash 接下来要做什么事情,其长度只能为 8 bit。

  • 地址阶段

    传输方向为 Host 发送给 QSPI Flash,目的是告诉 QSPI Flash 要从哪个地址开始读或写数据,其长度可能为 8 bit,16 bit,24 bit 和 32 bit。

  • 交替字节阶段:

    传输方向为 Host 发送给 QSPI Flash,具体用法需参考所使用的 QSPI Flash 手册,传输方向由 Host 发送给 QSPI Flash,其长度可能为 8 bit,16 bit,24 bit 和 32 bit。

  • 空指令周期阶段

    给 QSPI Flash 缓冲时间准备数据,等待 n 个 SCK 周期,n 取值范围 0 ~ 31,具体值需参考所使用 QSPI Flash 手册。这段时间 Host 只产生特定数量的 SCK 时钟,不从数据信号线中读取或发送任何内容。

  • 数据阶段

    读数据或写数据,长度不定,由主机决定发送或接收多少数据。

在与 QSPI Flash 通信时,其通信过程如图 4 所示:

4.png

图4 QSPI 通信过程

CS(NSS)信号线拉低,表示 QSPI Flash 器件被微控制器选中,然后发送一个字节大小的指令,紧接着发送地址,然后是交替字节,空指令周期,最后是数据,任意两个阶段之间都是紧挨着的。

一般来说,每次与 QSPI Flash 通信都至少包含指令阶段,其余的阶段都是可选的,例如读写 QSPI Flash 的寄存器值,只需要指令阶段和数据阶段;写使能和写失能,只需要指令阶段;读写数据,需要指令,地址,数据阶段,可能还有空指令周期阶段。

但有一种情况下,可能会省略掉指令阶段,部分 QSPI Flash 支持仅在第一次读数据的时候,需要指令阶段,下一次读数据的时候,直接由地址阶段开始,无需指令阶段。

一般 QSPI Flash 还支持一种叫做 QPI 模式的通信方式,当 QSPI Flash 进入 QPI 模式后,所有的阶段都只能使用四线模式通信,包括指令阶段,通过这种方式,能够极大减少指令阶段和地址阶段花费的 SCK 时钟数。

通信实例

图 5 是一款型号为 FM25Q16A QSPI Flash 的 Fast Read Quad Output 的传输波形示意图:

5.png

图5 FM25Q16A 的 Fast Read Quad Output 的传输波形示意图

从图中可以看到,四线快读数据的过程中分为四个步骤:

  • 指令阶段

Host 发送了一个 8 bit 长度的指令 0x6B,使用单线模式。

  • 地址阶段

一段 24bit 长度的地址,使用单线模式。

  • 空指令周期

8 bit 的空指令周期。

  • 数据阶段

以四线模式连续读取存储在 QSPI Flash 指定位置的数据。

可以发现,除了数据阶段,其它阶段均以单线模式工作,数据阶段则是四线模式,每两个时钟读取一个字节的数据。当读取一个四字节数据时,总共花费了 48 个 SCK 时钟。

通过内存地址访问 QSPI Flash

前文中了解了如何通过 QSPI 接口访问 QSPI Flash,但如果每次通信都要配置 QSPI 外设的寄存器,再通过 QSPI 的数据寄存器获取数据的话,则无法让微控制器直接执行存储在 QSPI Flash 中的程序(XIP)。

因此,可以将 QSPI Flash 映射到一段指定的内存地址空间内,例如,指定到 0x90000000 ~ 0x9FFFFFFF 这段 256MB 大小的空间内,当 CPU 访问 0x90000008 这段地址时,QSPI 模块将会读取 QSPI Flash 中 0x00000008 的数据,如果 CPU 继续访问 0x9000000C 的数据时,QSPI 模块将会读取 QSPI Flash 中 0x0000000C 的数据。对 CPU 而言,访问存储在 QSPI Flash 中的数据,就只是访问某一段地址空间的数据。这种读数据的方式称为 QSPI 的直接读模式,如图 6 所示。

6.png

图6 QSPI 的直接读模式

值得注意的是,32 位的 CPU 一般会连续读取 4 字节的数据,当 CPU 读取 0x90000008 这段地址时,实际读取了 0x90000008 ~ 0x9000000B 四个字节的数据,但 QSPI 不会每读取一字节数据,就发起一次指令阶段+地址阶段+数据阶段的过程,而是以指令阶段+地址阶段+数据阶段读一字节,再读一字节,再读一字节……的方式读数据,当后面 CPU 继续访问 0x0000000C 时,就会跳过指令阶段和地址阶段,直接继续数据阶段再读一字节,再读一字节……直到微控制器访问了一个不连续的地址时,QSPI 才会重新发起指令阶段和地址阶段。

因为地址连续的访问 QSPI Flash,QSPI 无需再次发起指令和地址阶段,所以其访问速度会比地址不连续的访问速度要快。

计算机程序有一个特点,叫做时间局限性和空间局限性:

  • 时间局限性

    最近被访问的单元,很可能在不久的将来还要被访问。

  • 空间局限性

    最近被访问的单元,很可能它附近的单元也即将被访问。

基于上述的特点,一般支持 QSPI 扩展 Flash 的微控制器会使用 CACHE 来暂存 QSPI Flash 中的内容,当 CPU 再次访问  QSPI Flash 前一段时间曾访问过的地址时,实际上访问的是暂存在 CACHE 中的数据,以此来减少访问 QSPI Flash 所花费的时间,加快程序执行的速度。在实际使用 QSPI Flash 存储应用程序的时候,效果可能不比片内 Flash 差多少。

直接读模式能够读取 QSPI Flash 中的数据,但 QSPI Flash 中的程序不是一直不变的,不管程序员调试代码,还是 OTA 升级,都需要读数据之外的操作,这时候,如果还使用直接读模式访问 QSPI Flash 的话,就显得不太合适了:直接模式下,我们只需要配置一次 QSPI 访问 QSPI Flash 各阶段的配置,就能不断读取 QSPI Flash 中的数据,虽然方便,但缺少一定的自由度,例如无法实现写操作等过程。

因此,QSPI 还提供了间接模式,间接模式下,需要指定每一次数据传输过程的每一个细节:如指令阶段的参数是什么,地址阶段的具体地址值是多少等,虽然复杂,但给了我们充分通过 QSPI 访问 QSPI Flash 的方法,例如,当我们希望写修改 QSPI Flash 中的数据时,就可以使用间接写模式,将数据阶段的传输方向由 QSPI Flash 到 Host 转变为 Host 到 QSPI Flash,而在直接模式下,数据阶段只能是由 QSPI Flash 到 Host。

总结

在本章中,介绍了 SPI,DSPI,QSPI的区别,访问 QSPI Flash 的过程,以及微控制器如何通过 QSPI 的直接读模式访问 QSPI Flash。

微控制器通过 QSPI 的直接读模式访问 QSPI Flash,为直接执行存储在 QSPI Flash 中的应用程序提供了可能,这也是 2nd Bootloader 的目的所在。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 206
订阅 RSS - QSPI