NXP SPIFI(QSPI)应用详解与程序固件分散加载

NXP SPIFI(QSPI)应用详解与程序固件分散加载1 SPIFI 标准 SPIFI SPIFLASHINTE 百度百科的定义 SPIFI 是 SPI 闪存接口专利技术的缩写 可以帮助 32 位嵌入式微片器使用小尺寸 低成本的串行闪存替代大尺寸 高成本的并行闪存 利用 SPIFI 技术 外部串行闪存可以映射到微控制器内存中 达到片上内存读取效果 关于 SPIFI 的资料并不多 因为 SPIFI 是 NXP 恩智浦 公司提出来并应用在自身各系列的 MC


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

(0)
上一篇 2026年3月17日 下午12:39
下一篇 2026年3月17日 下午12:39


相关推荐

  • SQL函数大全汇总

    SQL函数大全汇总SQL 中包含以下七种类型的函数 一 聚合函数聚合函数 返回汇总值 它对其应用的每个行集返回一个值 AVG 表达式 返回表达式中所有的平均值 仅用于数字列并自动忽略 NULL 值 COUNT 表达式 返回表达式中非 NULL 值的数量 可用于数字和字符列 COUNT 返回表中的行数 包括有 NULL 值的列 MAX 表达式 返回表达式中的最大值 忽略 NULL 值 可用于数字 字符和日期时间列 MIN 表达式 返回表达式中的最小值 忽略 NULL 值 可用于数字 字符和日期

    2026年3月19日
    2
  • 解析offsetHeight,clientHeight,scrollHeight之间的区别「建议收藏」

    解析offsetHeight,clientHeight,scrollHeight之间的区别「建议收藏」在网上搜了一下,结论非常笼统,讲IE从不讲版本,因此自己做了测试并上传结论。以下结论皆是在标准模式下测试通过的,没有测试quirk模式。clientHeight大部分浏览器对clientHeight都没有什么异议,都认为是内容可视区域的高度,也就是说页面浏览器中可以看到内容的这个区域的高度,即然是指可看到内容的区域,滚动条不算在内。但要注意padding是算在内。其计算方式为clien…

    2025年10月20日
    5
  • Linux 安装JDK详细步骤

    Linux 安装JDK详细步骤Linux 系统上一般会安装好 OpenJDK 所以安装 JDK 之前 需要卸载系统自带的 OpenJDK 以及相关的 Java 文件 一 卸载系统自带的 OpenJDK 以及相关的 Java 文件 1 查看 Java 信息以及相关的 Java 文件查看 JDK 信息 输入 java version 检测 jdk 的安装包 输入 rpm qa grepjava2 接着删除相关 Java 文件 并检查是否删除完即可删除输入 rpm enodeps 包名检查是否删除完 输入 rpm qa

    2026年3月16日
    2
  • NXP iMX8 存储性能测试

    NXP iMX8 存储性能测试1 简介 NXPiMX8 系列应用处理器是 NXP 发布的基于 Cortex A72 A53 A35 和 Coretex M4 M7 等架构的 ARM 处理器 对于存储部分 主要支持 MMC5 1 SDMemoryCard 0 SATA3 0 USB3 0 总线 本文就基于上述总线连接相应存储外设进行一些简单的性能对比测试 本文所演示的 ARM 平台来自于 Toradex 基于 NXPiMX8QPARM 处理器的 ApalisiMX8QP 嵌入式平台 2 准备 a Apalis

    2026年3月26日
    3
  • matlab命令,应该很全了!「建议收藏」

    matlab命令,应该很全了!「建议收藏」一、常用对象操作:除了一般windows窗口的常用功能键外。1、!dir可以查看当前工作目录的文件。!dir&可以在dos状态下查看。2、who可以查看当前工作空间变量名,whos可以查看变量名细节。3、功能键:功能键快捷键说明方向上键Ctrl+P返回前一行输入方向下键Ctrl+N返回下一行输入方向左键Ctrl+B

    2026年4月16日
    4
  • img写盘工具安装Linux,USB Image Tool:Windows下的直接写盘利器 【开源硬件佳软介绍 2】…

    img写盘工具安装Linux,USB Image Tool:Windows下的直接写盘利器 【开源硬件佳软介绍 2】…本期的 开源软件佳软介绍 我们讨论直接写盘 前言 略谈各种开发板常用的 系统镜像 树莓派 RaspberryPi 的用户经常提出这样一个问题 注 A 为什么不能把 img 镜像拷贝到 SD 卡中 而必须要用专门的刷卡软件 这个问题 我们从磁盘结构讲起 磁盘内部所有的存储区域 必然分为分区表和数据区域两大块 而数据区域按分区表分区后 每个分区还被文件系统封装和管理 注 B 分区表占据磁盘最前端的少量存

    2026年3月18日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号