SPIFI(QSPI)使用

1. SPIFI 标准
  SPIFI(SPI FLASH INTERFACE),百度百科的定义:SPIFI是SPI闪存接口专利技术的缩写,可以帮助32位嵌入式微片器使用小尺寸、低成本的串行闪存替代大尺寸、高成本的并行闪存。利用SPIFI技术,外部串行闪存可以映射到微控制器内存中,达到片上内存读取效果。关于SPIFI的资料并不多,因为SPIFI是NXP(恩智浦)公司提出来并应用在自身各系列的MCU中,NXP MCU手册关于SPIFI的介绍如下图。在了解SPIFI前,先回顾并不陌生的SPI flash接口。
这里写图片描述

1.1 SPI分类
  SPI(Serial Peripheral Interface)是摩托罗拉提出的一种高速、全双工的串行通信总线。标准SPI是4根线,分别时钟线(CLK)、片选(CS)、数据输出(DO)、数据输入(DI),后面摩托罗拉在标准SPI的基础上,又提出了Dual SPI和Quad SPI,目前很多厂家的串行flash已经支持此三类SPI,根据命名规则,一般带Q的型号是支持的,如华邦W25Q16(W25X16不支持)。

  以为W25Q16为例,根据其引脚图和数据手册可以确定支持三种类型SPI。其引脚图如下。
这里写图片描述
Standard SPI: CLK,/CS,DI,DO,/WP,/Hold
Dual SPI: CLK,/CS, IO0,IO1,/WP,/Hold
Quad SPI: CLK,/CS,IO0,IO1,IO2,IO3

标准SPI
 CLK(Serial Clock):时钟线
 /CS(Chip Select):片选接口
 DI(Serial Data Input):数据输入端口
 DO(Serial Data Output):输出输出端口
 /WP:写保护引脚
 /Hold:保持引脚
DSPI:
 增加为2根数据线。
QSPI:
 增加为4根数据线。

1.2 SPIFI与SPI
  通过上述,可以发现SPIFI和QSPI异曲同工,而NXP的数据手册中亦提到SPIFI和QSPI的描述,只是SPIFI专门应用于串行闪存,QSPI可以应用在支持QSPI的各类外设中。基于各类原因,QSPI专利或是作自身MCU特点,NXP应该是对QSPI进行改进,衍生出“SPIFI”这高大上的名称。因此,如果在此之前使用过QSPI的,对于SPIFI使用会易于理解。但NXP对外宣传SPIFI是花费很长时间研究出来的非常厉害的专利,底层代码并不开源,以库的形式提供,因此使用起来调用库API即可。

2. SPIFI
2.1 SPIFI外设
  到目前为止,NXP只在中高端系列的MCU提供SPIFI功能,如LPC1800、LPC4300、LPC4088等。本人使用的是LPC4088平台,因为在此之前,刚好使用过RT-Thread推的LPC4088平台。

2.2 SPIFI IO
  SPIFI驱动底层接口以库的形式提供,官方不开源,由于无法修库,因此只能使用NXP手册指定的IO口。
这里写图片描述

2.3 串行flash选择
  串行flash选择同样需使用NXP手册中指定的,目SPIFI库支持众多半导体厂商主流型号的flash,特别偏门或者新推出型号暂未支持,不排除后期NXP更新SPIFI库。不开源,有优势也有弊端,优势是用户无需关心底层配置,直接调用相关API即可。弊端是无法定制修改,不够灵活。目前支持的串行flash如下图。
这里写图片描述

2.4 SPIFI 库
  SPIFI库包含三个文件,驱动库、函数接口、初始化源码,分别是“spifi_drv_M4.lib”、“spifi_rom_api.h”、“SPIFI.c”。本人使用的是LPC4088,输入Cortex M4内核,因此使用的是M4的库。一般库文件会随NXP板级支持包提供,不过本人之前资料并未提供,是自行在NXP官网获得。“SPIFI.c”是官方提供的一个初始化例子,不是必须的,使用时可以根据设计框架要求放置到指定位置。

2.4.1 SPIFI API
   主要API函数接口或者说经常使用到的函数接口,有三个:初始化(spifi_init),编程(写)(spifi_program),擦除(spifi_erase)。此时,可能有人会有疑问,为什么读函数接口没有提供。因为,初始化完成后MCU会将串行flash的物理地址映射到MCU内部总线地址上(0x28000000—0x28FFFFFF),关于读操作,只需像平常访问内存地址一样即可读取对应地址上的值。理论上写(编程)也是可以的,但写操作与串行flash类型、型号相关脸,必须遵循页写、块写、扇区写等,而不同的厂家、不同型号的flash页、块、扇区大小都有可能不一样。擦除操作同理需遵循页擦除、扇区擦除等,因此必须调用SPIFI库提供的函数接口访问。
  其他函数API可以在““spifi_rom_api.h”头文件中查看,在一些特殊场合可能会用到,如一些测试、状态切换、快读等功能。但前提条件是注意初始化成功,因为SPIFI传入参数是以“函数指针方式”,如果未初始化成功,访问时会出现异常。

2.5 SPIFI 内存映射
  NXP将串行flash物理地址映射到MCU内部内存总线上,映射到内存地址范围为0x28000000—0x28FFFFFF,因此最大支持16MB串行flash,对于我们使用来说,16M足够大了,特别是用来存储程序的情况。但目前Keil编译器中,只支持4M空间加载大小。
  NXP对flash物理地址映射,具有非常大优势,比如在读操作时,我们无需关心SPIFI总线或者flash实际物理地址,只需知道flash大小即可,然后可以通过映射后的地址(0x28000000)进行访问。比如,对于W25Q16 2MB的flash映射地址为0x28000000—0x28200000,读取W25Q16首地址1字节数,可以这样操作:

char *p;
char data;

p = (char*)0x28000000;
data = *p;


3. SPIFI 使用
3.1 添加SPIFI 库
  以NXP提供的LPC4088 SPIFI测试工程为例,进行对SPIFI使用测试。首先检查SPIFI库,在首次编译该工程时,提示没有找到相关库,然后是从NXP网络下载获得。如果不存在拷贝到指定编译路径。
这里写图片描述

  第二步添加到工程。
这里写图片描述

3.2 初始化
  初始化代码在“SPIFI.c”中,主要是对SPIFI相关的IO初始化,以及“SPIFI_RTNS”函数指针实例化。该代码是NXP提供的例程。

void SPIFI_Init (void) {
#ifdef USE_SPIFI_LIB
  /* Use spifi function names directly */
#else
  SPIFI_RTNS * pSpifi;
  pSpifi = (SPIFI_RTNS *)(SPIFI_ROM_PTR);
  /* Call functions via spifi rom table */
  #define spifi_init pSpifi->spifi_init
#endif

/* init SPIFI clock and pins */
  LPC_SC->PCONP      |=  (1UL << 16);        /* enable SPIFI power/clock   */

  LPC_IOCON->P2_7    &= ~(7UL <<  0);
  LPC_IOCON->P2_7    |=  (5UL <<  0);        /* SPIFI_CSN = P2.7  (FUNC 5) */
  LPC_IOCON->P0_22   &= ~(7UL <<  0);
  LPC_IOCON->P0_22   |=  (5UL <<  0);        /* SPIFI_CLK = P0.22 (FUNC 5) */
  LPC_IOCON->P0_15   &= ~(7UL <<  0);
  LPC_IOCON->P0_15   |=  (5UL <<  0);        /* SPIFI_IO2 = P0.15 (FUNC 5) */
  LPC_IOCON->P0_16   &= ~(7UL <<  0);
  LPC_IOCON->P0_16   |=  (5UL <<  0);        /* SPIFI_IO3 = P0.16 (FUNC 5) */
  LPC_IOCON->P0_17   &= ~(7UL <<  0);
  LPC_IOCON->P0_17   |=  (5UL <<  0);        /* SPIFI_IO1 = P0.17 (FUNC 5) */
  LPC_IOCON->P0_18   &= ~(7UL <<  0);
  LPC_IOCON->P0_18   |=  (5UL <<  0);        /* SPIFI_IO0 = P0.18 (FUNC 5) */

  if (spifi_init(&obj, 3, S_RCVCLK | S_FULLCLK, 48)) {
    while (1);
  }
}

代码中有几个注意点是:
1)目前只是支持提供官方的SPIFI 库来使用,即是需定义宏“USE_SPIFI_LIB”。
2)obj是一个全局结构体变量,该结构体是falsh的相关信息,有两个作用,一是初始化成功时,可查看flash信息,如型号、厂商、扇区大小等;另一个作用是,调用API访问flash时,该结构体地址作为实参传入,通过api头文件也可查看,所有访问函数第一个参数类型都是“SPIFIobj *”。
3)初始化,重点关注最后一个参数,也就是flash的时钟频率,其他参数参考例程。
4)可以直接调用API访问,也可以通过“SPIFI_RTNS”函数指针方式访问,但前提是该指针需实体化,且其中的函数指针需实例化,从库“spifi_rom_api.h”中看,目前只实现了初始化、写(编程)、擦除等函数实体,其他未实例化的函数指针如果去调用,程序可能会崩溃。如NXP函数指针的调用方式,这中方式也比较灵活。
5)详细接口或者函数指针查看“spifi_rom_api.h”文件。

SPIFIobj obj;
SPIFI_RTNS * pSpifi;
uint32_t error;
#ifdef USE_SPIFI_LIB
    pSpifi = &spifi_table;
#else
    pSpifi = (SPIFI_RTNS *)(SPIFI_ROM_TABLE);
#endif
    /* Initialize SPIFI driver */
    error = /*pSpifi->*/spifi_init(&obj, 4, S_RCVCLK | S_FULLCLK, 60);
    if (error) while (1);

3.3 通过SPIFI访问flash
  在使用标准SPI操作串行flash实现的功能,使用SPIFI也能实现,最大的不同是后者的速度远远高于标准SPI,而且作了地址映射,访问方式有稍微的差别。主要应用功能包括参数存储、文件系统、程序存储等。

3.3.1 存储参数
  作为参数存存储介质功能,比较易理解和实现,初始化成功后调用相关读、写、擦除API即可。

  ,不提供访问函数,因为作了内存映射,可以像访问内存一样读访问。

int SPIFIFlashReadBytes(uint32_t Addr,char *ReadBuff,uint32_t Len)
{
    uint16_t i;
    uint32_t addr;

    addr = Addr;        
    for(i = 0; i< Len; i++)
    {
        ReadBuff[i] = *(volatile uint8_t*)addr; //1字节访问
        addr++;
    }
    return Len;
}

  擦除,flash写之前,必须进行擦除操作。擦除与具体flash型号相关,支持页擦除、块擦除、整片擦除等。因此,擦除函数“spifi_erase” 会有一个与flash参数相关的入口参数,参数类型为“SPIFIopers”,需根据具体flash型号或者擦除条件设置该参数。下面为扇区擦除。

char SPIFIFlashEaseSecton(uint32_t BasePhysicalAddr,uint32_t LogicAddr)
{
    SPIFIopers  opers;

    opers.dest = (char *)(BasePhysicalAddr | LogicAddr);
    opers.length = 0x1000;      //4K扇区擦除
    opers.scratch = NULL;
    opers.options = S_VERIFY_ERASE;
    if (spifi_erase(&obj, &opers)) 
        return -1;

    return 0;
}

  写(编程),写操作也是与具体flash型号相关,目前主流串行flash都支持256字节页写。写函数“spifi_program”,同理需设置好“SPIFIopers”参数。下面为扇区写。

int SPIFIFlashSectonWrite(uint32_t Addr, char *WriteData)
{
    SPIFIopers  WriteOpers;
    uint32_t    Remainsize;
    uint32_t    i;

    if((Addr&0xFF) !=0) //256字节对齐
        return -1;
    WriteOpers.scratch  = NULL;
    WriteOpers.protect  = 0;
    WriteOpers.options  = S_CALLER_ERASE; 
    WriteOpers.dest     = obj.base + Addr;
    WriteOpers.length   = 256;   //256字节页写

    for ( i = 0 ; i < 4096/256; i++ )
    {
        /* Write */
        WriteOpers.dest = (char *)( obj.base + (i*256));
        if(spifi_program (&obj, (char *)WriteData, &WriteOpers)) 
            return -1;
        WriteData += 256;
    }
    return 0;
}

3.3.2 文件系统
  文件系统,后续再进行文件系统移植测试。

3.4 存储/执行程序
3.4.1 分散加载
  Keil中已经支持SPIFI映射功能,可以设置“分散加载(scatter loading)”文件(后缀为.sct的文件,在链接阶段使用到),即可通过jlink烧录程序到flash中,无需借助其他第三方烧录工具。执行程序功能则是由MCU内部控制,可能普通情况下不易直观观察到。

  在此之前,我们先了解下分散加载文件。分散加载它提供这样一种机制:可以将内存变量定位于不同的物理地址上的存储器或端口,通过访问内存变量即可达到访问外部存储器或外设的目的。同时通过分散加载,让大多数程序代码在高速的内部RAM中运行,从而使得系统的实时性大大增强。关于ARM的分散加载,可以详细去了解。

  以下为常见的分散加载文件,只有一个ROM空间和RAM空间。如果MCU中有多片跨地址的RAM或者ROM则可以指定多个加载语句。

LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x10000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

  同理,当我们增加了SPIFI flash时,相当于增加了一块ROM空间,也可以设置分散加载文件,将部分存储在SPIFI flash中的程序加载运行。如NXP的一个Demo将“LED.c”的执行程序存储在SPIFI flash中,那么分散加载文件会增加如下语句。

LR_ROM1 0x28000000 0x00400000  {
  ER_ROM1 0x28000000 0x00400000  {  ; load address = execution address
    LED.o (+RO)
  }
}


3.4.2 增加flash地址空间
  一般地,我们根据MCU内部flash空间大小,选择一个flash烧录地址块选项。Keil已经有支持SPIFI映射烧录,可以在使用内部flash的基础上,同时在“Flash Download”选择中增加“SPIFI flash”烧录块。如图,LPC4088的SPIFI最大支持16MB的串行flash,但目前Keil(包括Keil4和Keil5)只支持4MB空间的烧录映射,所以在选择flash容量时需考虑空间的利用率,超过4M空间很可能不支持烧录,具体没有使用大容量flash验证过。
这里写图片描述

3.4.3 程序存储配置
  如果需要将程序存储在SPIFI 外部flash中,只需设置好分散加载文件,编译代码时,在链接阶段编译器会设置相关地址信息,通过jlink烧录时即可将指定程烧录至外部flash。设置过程,有手动设置分散加载文件和自动设置。

手动设置:
  打开工程设置,“Options for target—>Linker”。如下图,将“1”选项去掉勾选,在选项“2”中选择对应路径的分散加载文件。后用记事本打开分散加载文件,手动输入需要加载的程序模块。
这里写图片描述

自动设置:
第一步:将上图中选项“1”勾选,选项“2”会自定屏蔽。
第二步:选中需要设置的程序源文件,“右击—>Options fo Files—>选择存储路径为SPIFI映射的地址范围”。以“LED.c”为例,如下图。
这里写图片描述
第三步:编译、链接。后在编译输出路径(用户可以更改)下输出生成分散加载文件,无需再手动输入。

  无论是手动设置还是让编译器自动设置,都可通过Jlink将设置好的程序烧录到SPIFI flash中。

4. 总结
1)SPIFI充分发挥了串行flash的性能(QSPI的flash用标准SPI访问,性能位完全发挥),降低了用户使用串行flash的门槛,使得用户专注于应用开发。
2)SPIFI 存储、执行程序,能够低成本、快速解决大工程执行程序问题。比如,当应用需要用到中英文字库时,一般的实现方式是,通过USB/串口将字库文件导入到串行flash中,然后使用时从flash中读取并解析字库。而SPIFI,可以直接将字库以ASII码形式存放在单独一个源文件中,通过分散加载文件指定存储路径为外部串行flash,即可实现;而且使用时直接通过数组形式索引

posted @ 2018-08-15 00:49  Acuity  阅读(691)  评论(0编辑  收藏  举报