QSPI协议 - 超越XIP:在内存映射、四线模式与DMA协同中压榨极致性能
该文章同步至OneChan
当四线并行、内存映射与DMA交织,我们如何平衡带宽、延迟与系统资源,实现极致吞吐?
导火索:一个QSPI Flash的“高速”读取性能瓶颈
在一个高性能嵌入式系统中,使用QSPI Flash存储程序代码和数据。系统设计时预期通过内存映射模式实现快速的XIP执行,但实际测试发现:
- 在内存映射模式下,CPU直接读取QSPI Flash的速度仅为理论带宽的30%
- 启用缓存后性能有所提升,但偶尔会出现数据一致性问题
- 使用DMA从QSPI Flash拷贝数据到RAM时,带宽利用率也不到50%
逻辑分析仪捕获的波形显示,在连续读取数据时,QSPI接口在每读取256字节后会有大约2μs的停顿。进一步分析发现,这是因为Flash内部页编程和读取的页边界限制,以及QSPI控制器在跨页时需要重新发送命令序列。
矛盾点在于:QSPI硬件提供了四线并行和高达133MHz的时钟频率,理论上可以提供532Mbps的带宽(4线×133MHz)。但实际性能受限于Flash本身的特性、QSPI控制器的配置、系统总线的竞争以及软件访问模式。要榨取极致性能,必须深入理解每一层的细节。
第一性原理:重新审视四线SPI与内存映射
设计的演进:从单线SPI到四线QSPI
传统SPI使用两条数据线(MOSI和MISO)实现全双工,但实际读操作中,MOSI线只用于发送命令和地址,大部分时间处于未充分利用状态。QSPI通过增加数据线数量,将读数据的带宽提高4倍。
单线SPI读取序列: 命令(8位) + 地址(24位) + 哑元周期 + 数据(多位,每时钟1位) 四线QSPI读取序列(标准): 命令(1线, 8位) + 地址(4线, 24位) + 哑元周期 + 数据(4线, 每时钟4位) 四线QSPI读取序列(快速): 命令(4线, 8位) + 地址(4线, 24位) + 哑元周期 + 数据(4线, 每时钟4位)关键洞察:
命令阶段:可以单线发送,也可以四线发送。四线发送命令需要Flash支持,且命令本身需要设计为四线兼容(通常命令字节被重复4次)。
地址阶段:通常使用四线发送,每个时钟周期传输4位地址,24位地址只需6个时钟。
哑元周期:由于Flash从接收到地址到准备好数据需要一定时间(tACC),这段时间用哑元时钟填充。哑元周期数取决于Flash的延迟和QSPI时钟频率。
数据阶段:四线传输,每个时钟周期传输4位数据。
内存映射模式的实现原理
内存映射模式是QSPI最吸引人的特性之一。它将外部Flash映射到处理器的地址空间,使得CPU可以直接通过加载指令(如LDR)读取Flash内容,而无需软件干预。
硬件支持:QSPI控制器内部有一个地址转换器,当CPU访问特定的内存区域(如0x9000_0000)时,QSPI控制器自动将访问转换为QSPI读事务。
转换过程:
CPU发起读访问(地址0x9000_1234) ↓ QSPI控制器截获该访问 ↓ 将地址0x9000_1234转换为Flash偏移0x1234 ↓ 构造QSPI读命令序列:命令 + 地址(0x1234) + 哑元 + 数据 ↓ 通过QSPI接口从Flash读取数据 ↓ 将数据返回给CPU优势:代码可以像在内部Flash一样在外部Flash中运行(XIP),无需先拷贝到RAM。
挑战:每次访问都有固定的开销(命令、地址、哑元),并且访问是随机的,无法利用突发读取的优化。此外,缓存对性能影响巨大。
性能模型:理论带宽 vs. 实际带宽
理论带宽计算:
假设QSPI时钟为100MHz,四线数据,则理论带宽为:
100MHz × 4位/时钟 = 400Mbps = 50MB/s实际带宽模型:
实际带宽受限于每次读事务的开销和数据传输时间。
总时间 = 命令时间 + 地址时间 + 哑元时间 + 数据时间 命令时间:命令位数 / (线数 × 时钟频率) 地址时间:地址位数 / (线数 × 时钟频率) 哑元时间:哑元周期数 / 时钟频率 数据时间:数据位数 / (线数 × 时钟频率)示例:四线模式,100MHz,命令8位(单线),地址24位(四线),哑元周期8,读取256字节(2048位)数据。
命令时间 = 8位 / (1线 × 100MHz) = 80ns 地址时间 = 24位 / (4线 × 100MHz) = 60ns 哑元时间 = 8周期 / 100MHz = 80ns 数据时间 = 2048位 / (4线 × 100MHz) = 5120ns 总时间 = 80+60+80+5120 = 5340ns 有效数据 = 256字节 实际带宽 = 256字节 / 5340ns ≈ 47.9MB/s 效率 = 47.9 / 50 = 95.8%但上述计算假设了连续读取,且没有考虑跨页边界、缓存未命中、总线竞争等因素。实际效率会低得多。
性能陷阱:QSPI系统的五大瓶颈
瓶颈一:Flash内部的页边界限制
大多数QSPI Flash内部被划分为页(通常256或512字节)。连续读操作不能跨越页边界,否则需要额外的延迟。
问题:当读取操作跨越页边界时,Flash内部需要重新定位到下一页,可能导致额外的延迟(通常几微秒)。
解决方案:
- 软件避免跨页访问,或者将跨页访问拆分为两次。
- 使用QSPI控制器的自动递增地址功能,让硬件处理页边界。
瓶颈二:命令序列的配置不当
QSPI控制器需要正确配置命令序列,包括命令、地址宽度、数据模式、哑元周期等。配置不当会导致性能下降或通信失败。
常见错误:
- 哑元周期设置过小,数据尚未准备好就采样,导致读取错误。
- 哑元周期设置过大,增加不必要的延迟。
- 没有使能四线模式,仍然使用单线模式传输数据。
优化方法:根据Flash数据手册和实际时钟频率计算最佳哑元周期。有些QSPI控制器支持自动检测Flash所需哑元周期。
瓶颈三:内存映射模式下的缓存效应
内存映射模式下,CPU通过缓存访问QSPI Flash。缓存行的大小通常为32字节。每次缓存未命中,QSPI控制器会发起一次读事务。但缓存行读事务可能不是最优的。
示例:缓存行32字节,但QSPI控制器可能只支持最大8字节的突发读取。为了读取32字节,需要4次8字节的读取,每次都有命令、地址、哑元的开销。
优化:
- 配置QSPI控制器支持更大的突发长度(如64字节)。
- 使用预取指功能,提前读取后续数据。
瓶颈四:系统总线竞争
QSPI控制器通过总线(如AXI)连接到内存系统,与CPU、DMA等共享带宽。当多个主机同时访问时,QSPI的访问可能被阻塞。
影响:在内存映射模式下,CPU读取QSPI Flash时,如果总线被其他主机占用,则读取延迟增加,导致CPU停顿。
优化:
- 提高QSPI控制器的总线优先级。
- 使用缓存减少对QSPI的访问次数。
瓶颈五:软件访问模式
软件以何种方式访问QSPI Flash对性能影响巨大。随机访问和顺序访问的性能差异可达10倍以上。
顺序访问:可以利用Flash的连续读模式,只需发送一次命令和地址,然后连续读取数据。
随机访问:每次读取都需要完整的命令序列,开销巨大。
优化:尽量将数据顺序存储,并顺序访问。使用DMA进行大块数据搬运,减少CPU干预。
超越XIP:高级优化技巧
技巧一:精确计算哑元周期
哑元周期必须足够长,以覆盖Flash的访问时间(tACC)。tACC通常与电压、温度有关,且在不同的时钟频率下,所需的哑元周期数不同。
计算公式:
所需哑元周期数 = ceil( tACC × 时钟频率 ) - 固定延迟其中固定延迟包括命令和地址的传输时间。
示例:tACC = 40ns,时钟频率 = 100MHz,命令8位单线(80ns),地址24位四线(60ns)。
则从命令开始到数据准备好需要40ns,但命令和地址已经用了140ns,所以理论上不需要哑元周期。但实际上,由于内部流水线,可能仍需要少量哑元周期。
实践:通过实验确定最小哑元周期。从最大值开始递减,直到读取错误,然后增加1-2个周期作为余量。
技巧二:配置QSPI控制器的寄存器
以STM32的QSPI为例,需要配置多个寄存器以实现最佳性能。
// 示例:STM32 QSPI初始化代码片段voidQSPI_Init(void){// 1. 配置时钟分频,得到100MHz时钟QUADSPI->DCR|=(0<<QUADSPI_DCR_CKMODE_Pos);// 模式0,时钟空闲为低QUADSPI->CR|=(0<<QUADSPI_CR_PRESCALER_Pos);// 不分频,输入时钟为100MHz则输出100MHz// 2. 配置Flash大小(地址位数)QUADSPI->DCR|=(23<<QUADSPI_DCR_FSIZE_Pos);// 24位地址,2^24=16MB// 3. 配置采样边缘QUADSPI->CR|=(1<<QUADSPI_CR_SAMPLE_SHIFTING_Pos);// 在时钟后半周期采样,提高时序余量// 4. 配置双闪存模式(如果使用两个Flash并行)// QUADSPI->CR |= (1 << QUADSPI_CR_DFM_Pos); // 使能双闪存模式,数据线变为8条// 5. 配置中断和DMAQUADSPI->CR|=(1<<QUADSPI_CR_TCIE_Pos);// 使能传输完成中断// 使能DMAQUADSPI->CR|=(1<<QUADSPI_CR_DMAEN_Pos);}技巧三:内存映射模式的配置与优化
内存映射模式需要正确配置存储器映射的区域和访问属性。
// 配置内存映射模式voidQSPI_EnableMemoryMappedMode(void){// 1. 配置读命令序列QUADSPI->CCR=(0xEB<<QUADSPI_CCR_INSTRUCTION_Pos)|// 快速读四线命令,0xEB(3<<QUADSPI_CCR_ADDRESSMODE_Pos)|// 24位地址(3<<QUADSPI_CCR_ADDRESSIZE_Pos)|// 四线地址(0<<QUADSPI_CCR_ABMODE_Pos)|// 无交替字节(3<<QUADSPI_CCR_ABMODE_Pos)|// 四线数据(6<<QUADSPI_CCR_DCYC_Pos)|// 哑元周期=6(1<<QUADSPI_CCR_DMODE_Pos);// 四线数据// 2. 设置内存映射模式QUADSPI->CCR|=(1<<QUADSPI_CCR_FMODE_Pos);// 内存映射模式// 3. 使能QSPIQUADSPI->CR|=(1<<QUADSPI_CR_EN_Pos);// 4. 在MPU中配置该区域为可缓存、可预取MPU_Config(0x90000000,16*1024*1024,MPU_REGION_ENABLE|MPU_REGION_CACHEABLE|MPU_REGION_BUFFERABLE);}注意:内存映射区域通常配置为可缓存(Cacheable)和可缓冲(Bufferable),以提高性能。但需要处理缓存一致性问题。当QSPI Flash的内容被DMA或另一个处理器更新时,必须清洗缓存。
技巧四:DMA与QSPI的协同
DMA可以用于在QSPI Flash和RAM之间搬运数据,减轻CPU负担。但需要仔细配置DMA和QSPI的握手。
// 使用DMA从QSPI Flash读取数据到RAMvoidQSPI_DMA_Read(uint32_tflash_addr,uint8_t*ram_addr,uint32_tsize){// 1. 配置DMADMA_InitTypeDef dma_init;dma_init.Channel=DMA_CHANNEL_3;dma_init.Direction=DMA_PERIPH_TO_MEMORY;dma_init.PeriphInc=DMA_PINC_DISABLE;dma_init.MemInc=DMA_MINC_ENABLE;dma_init.PeriphDataAlignment=DMA_PDATAALIGN_WORD;// QSPI数据寄存器为32位dma_init.MemDataAlignment=DMA_MDATAALIGN_WORD;dma_init.Mode=DMA_NORMAL;dma_init.Priority=DMA_PRIORITY_HIGH;HAL_DMA_Init(&dma_init);// 2. 配置QSPI为间接读模式QUADSPI->CCR=(0xEB<<QUADSPI_CCR_INSTRUCTION_Pos)|// 快速读四线命令(3<<QUADSPI_CCR_ADDRESSMODE_Pos)|// 24位地址(3<<QUADSPI_CCR_ADDRESSIZE_Pos)|// 四线地址(3<<QUADSPI_CCR_DMODE_Pos)|// 四线数据(6<<QUADSPI_CCR_DCYC_Pos);// 哑元周期QUADSPI->DLR=size-1;// 设置数据长度(字节数-1)QUADSPI->AR=flash_addr;// 设置地址// 3. 启动DMAHAL_DMA_Start(&hdma,(uint32_t)&QUADSPI->DR,(uint32_t)ram_addr,size/4);// 4. 启动QSPI传输QUADSPI->CCR|=(1<<QUADSPI_CCR_FMODE_Pos);// 间接读模式QUADSPI->CR|=(1<<QUADSPI_CR_EN_Pos);// 5. 等待DMA完成HAL_DMA_PollForTransfer(&hdma,HAL_DMA_FULL_TRANSFER,1000);}DMA配置要点:
- QSPI的数据寄存器通常是32位的,因此DMA的传输宽度应设置为字(32位)。
- 数据长度需要根据字节数换算为字数。
- 在启动DMA后再启动QSPI传输,避免数据丢失。
技巧五:双闪存模式(Dual Flash)的配置
对于更大的存储需求,可以使用两个QSPI Flash,分别连接到QSPI控制器的两组数据线。在双闪存模式下,数据线扩展到8条,每个时钟周期可以传输8位数据。
接线方式:
Flash1: IO0, IO1, IO2, IO3 Flash2: IO4, IO5, IO6, IO7配置:
// 使能双闪存模式QUADSPI->CR|=(1<<QUADSPI_CR_DFM_Pos);// 配置Flash1和Flash2的片选// 通常通过地址的最高位来选择:A24=0选择Flash1,A24=1选择Flash2// 因此,两个Flash的地址空间是连续的,总容量为32MB(每个16MB)性能提升:理论上,双闪存模式可以将带宽提高一倍。但需要注意,两个Flash必须具有相同的型号和特性,且需要同时进行相同的操作。
QSPI系统设计检查清单(10条)
1. 时钟配置检查
问题:QSPI时钟频率是否在Flash支持范围内?是否考虑了信号完整性?
验证:测量SCK信号,确保边沿陡峭,过冲小。
检查点:时钟频率 ≤ Flash最大频率,走线长度匹配,终端电阻适当。
2. 命令序列配置
问题:命令序列(命令、地址、哑元、数据)的线数和时序是否正确?
验证:用逻辑分析仪捕获波形,对比Flash数据手册。
检查点:命令码正确,地址位数匹配,哑元周期足够,数据线数正确。
3. 内存映射配置
问题:内存映射区域是否配置了正确的缓存和缓冲属性?
验证:测试随机访问和顺序访问的性能,对比缓存开启和关闭的情况。
检查点:MPU/MMU配置正确,缓存策略合理(通常Write-Back, Read-Allocate)。
4. 哑元周期优化
问题:哑元周期是否针对当前时钟频率和电压温度优化?
验证:在不同温度和电压下测试读取稳定性。
检查点:哑元周期在极端条件下仍能稳定工作,且不过大。
5. 跨页处理
问题:软件或硬件是否处理了Flash的页边界?
验证:测试跨页读取,检查是否有性能下降或错误。
检查点:QSPI控制器支持自动递增地址,或软件将跨页访问拆分。
6. DMA配置
问题:DMA与QSPI的握手是否正确?传输效率如何?
验证:测量DMA传输大量数据的时间和CPU占用率。
检查点:DMA传输完成中断正常,无数据丢失,带宽利用率>80%。
7. 总线竞争管理
问题:QSPI控制器在总线上的优先级是否足够?
验证:在总线高负载下测试QSPI访问延迟。
检查点:QSPI控制器优先级高于其他主机,或使用带宽预留。
8. 双闪存同步
问题:双闪存模式下,两个Flash的特性是否匹配?片选逻辑是否正确?
验证:分别测试每个Flash,然后测试同时访问。
检查点:两个Flash型号相同,时序一致,片选信号正确。
9. 功耗管理
问题:QSPI接口的功耗是否在预算内?是否在低功耗模式下正确关闭?
验证:测量不同工作频率下的电流消耗。
检查点:在睡眠模式下禁用QSPI时钟,动态调整时钟频率。
10. 错误处理
问题:是否检测和处理QSPI传输错误?是否有重试机制?
验证:模拟传输错误(如断开数据线),检查系统响应。
检查点:错误标志被检测,有重试机制,重试次数可配置。
总结:在带宽、延迟与复杂度之间权衡
QSPI协议通过增加数据线数量、支持内存映射和DMA,为外部Flash提供了高性能的访问接口。然而,真正的极致性能来自于对每一层细节的深入理解和优化:
- 硬件层面:信号完整性、终端匹配、时钟频率。
- 配置层面:命令序列、哑元周期、缓存策略。
- 架构层面:内存映射、DMA使用、总线竞争。
- 软件层面:访问模式、错误处理、低功耗管理。
QSPI的性能优化是一个系统工程,需要硬件工程师、驱动开发者和系统架构师紧密合作。只有将Flash特性、控制器配置、系统架构和软件访问模式综合考虑,才能榨取出QSPI接口的每一分性能。
当您下次设计基于QSPI的系统时,请记住:四线并行带来的不仅是带宽的提升,还有时序复杂度的增加。内存映射提供了便利,但也引入了缓存一致性的挑战。DMA减轻了CPU负担,但需要仔细配置握手。在追求极致性能的路上,每一个环节都不可或缺。
思考题:在您的QSPI应用中,是否遇到了性能瓶颈?您是如何分析并解决的?是调整了哑元周期,还是优化了软件访问模式?
下篇预告:接下来我们将探讨eSPI协议。在《LPC的继承者:在引脚节约、虚拟外设与安全启动间重塑板级架构》中,我们将揭示:eSPI如何用更少的引脚实现更强的功能?虚拟外设如何改变主板设计?安全启动如何集成?以及eSPI在取代传统LPC接口时面临的兼容性挑战。
