文章目录
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不支持)。
- 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:
- 增加IO0—IO1数据线
QSPI:
- 增加IO0—IO3数据线
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外设
2.2 SPIFI IO
NXP SPIFI IO
2.3 串行flash选择
SPIFI库已支持的串行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内部总线地址上(0x—0x28FFFFFF),关于读操作,只需像平常访问内存地址一样即可读取对应地址上的值。理论上写(编程)也是可以的,但写操作与串行flash类型、型号相关脸,必须遵循页写、块写、扇区写等,而不同的厂家、不同型号的flash页、块、扇区大小都有可能不一样。擦除操作同理需遵循页擦除、扇区擦除等,因此必须调用SPIFI库提供的函数接口访问。
其他函数API可以在“spifi_rom_api.h”头文件中查看,在一些特殊场合可能会用到,如一些测试、状态切换、快读等功能。但前提条件是注意初始化成功,因为SPIFI传入参数是以“函数指针方式”,如果未初始化成功,访问时会出现异常。
2.5 SPIFI 内存映射
NXP将串行flash物理地址映射到MCU内部内存总线上,映射到内存地址范围为0x—0x28FFFFFF,因此最大支持16MB串行flash,对于我们使用来说,16M足够大了,特别是用来存储程序的情况。但目前Keil编译器中,只支持4M空间加载大小。
NXP对flash物理地址映射,具有非常大优势,比如在读操作时,我们无需关心SPIFI总线或者flash实际物理地址,只需知道flash大小即可,然后可以通过映射后的地址(0x)进行访问。比如,对于W25Q16 2MB的flash映射地址为0x—0x,读取W25Q16首地址1字节数,可以这样操作:
char *p; char data; p = (char*)0x; data = *p;
3. SPIFI 使用
3.1 添加SPIFI 库
SPIFI库
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); } }
代码中有几个注意点是:
- 目前只是支持提供官方的SPIFI 库来使用,即是需定义宏
“USE_SPIFI_LIB”。 - obj是一个全局结构体变量,该结构体是falsh的相关信息,有两个作用,一是初始化成功时,可查看flash信息,如型号、厂商、扇区大小等;另一个作用是,调用API访问flash时,该结构体地址作为实参传入,通过api头文件也可查看,所有访问函数第一个参数类型都是
“SPIFIobj *”。 - 初始化,重点关注最后一个参数,也就是flash的时钟频率,其他参数参考例程。
- 可以直接调用API访问,也可以通过
“SPIFI_RTNS”函数指针方式访问,但前提是该指针需实体化,且其中的函数指针需实例化,从库“spifi_rom_api.h”中看,目前只实现了初始化、写(编程)、擦除等函数实体,其他未实例化的函数指针如果去调用,程序可能会崩溃。如NXP函数指针的调用方式,这中方式也比较灵活。 - 详细接口或者函数指针查看
“spifi_rom_api.h”文件。
spifi对象实例初始化:
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 0x 0x00010000 { ; RW data .ANY (+RW +ZI) } }
同理,当我们增加了SPIFI flash时,相当于增加了一块ROM空间,也可以设置分散加载文件,将部分存储在SPIFI flash中的程序加载运行。如NXP的一个Demo将“LED.c”的执行程序存储在SPIFI flash中,那么分散加载文件会增加如下语句。
LR_ROM1 0x 0x00 { ER_ROM1 0x 0x00 { ; 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”中选择对应路径的分散加载文件。后用记事本打开分散加载文件,手动输入需要加载的程序模块。
- 自动设置:
无论是手动设置还是让编译器自动设置,都可通过Jlink将设置好的程序烧录到SPIFI flash中。
4. 总结
- SPIFI充分发挥了串行flash的性能(QSPI的flash用标准SPI访问,性能位完全发挥),降低了用户使用串行flash的门槛,使得用户专注于应用开发。
- SPIFI 存储、执行程序,能够低成本、快速解决大工程执行程序问题。比如,当应用需要用到中英文字库时,一般的实现方式是,通过USB/串口将字库文件导入到串行flash中,然后使用时从flash中读取并解析字库。而SPIFI,可以直接将字库以ASII码形式存放在单独一个源文件中,通过分散加载文件指定存储路径为外部串行flash,即可实现;而且使用时直接通过数组形式索引。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224116.html原文链接:https://javaforall.net
