ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32H7使用XSPI接口外扩HyperRAM(DDR SDRAM)实践详解

STM32H7使用XSPI接口外扩HyperRAM(DDR SDRAM)实践详解 1. 项目概述与方案选型思路1.1 为什么要在STM32H7上外扩RAM这一篇是STM32H7学习记录的第14篇主题是用XSPI接口把HyperBus接口的DDR SDRAM也就是常说的HyperRAM接到MCU上。先说清楚这个事到底解决什么问题。我们有相当大比例的GUI、音视频缓冲、协议栈场景单靠芯片内置RAM是不够的。以我常用的H7A3系列为例内部SRAM大概有1.4MB左右听上去还行可一旦跑TouchGFX带高清屏再来一个摄像头帧缓冲1.4MB瞬间就被吃干净。传统做法是外挂一颗SDRAM比如W9825G6KH16位总线、容量32MB性能确实不错但接起来非常折腾。SDRAM需要占用FMC控制器而且引脚特别多A0-A12、D0-D15、BA0、BA1、CLK、CKE、RAS#、CAS#、WE#一排引脚拉下来相当壮观。对于LQFP100这种封装来说基本是噩梦。HyperRAM就是在这种背景下出现的。它内部确实是DRAM颗粒但外部接口被封装成了一个类NOR Flash的同步串行总线数据通过最多8根DQ线传输时钟用CK数据选通用RWDS。对MCU来说它看起来更像一个“接在XSPI总线上的存储设备”而不是需要FMC维护刷新时序的传统SDRAM。这样一来引脚节省了一大半PCBA走线难度也明显降低而且HyperRAM内部自带刷新逻辑软件上不需要像SDRAM那样定期发刷新命令。我把标题叫“DDR SDRAM(HyperBus)”有人可能会疑惑DDR SDRAM和HyperRAM不是一回事吧严格说HyperRAM是Cypress推的HyperBus接口内一类产品官方叫法确实是“HyperBus Interface DDR SDRAM”。它使用DDR双沿采样技术每个时钟周期在上升沿和下降沿各传一次数据所以没有SDRAM那套复杂的bank管理却又享受到了DDR的高吞吐。这就是为什么标题里会把DDR SDRAM和HyperBus放一起准确理解是“用HyperBus总线访问的DDR型RAM”。1.2 XSPI、OCTOSPI和HyperBus到底分别指什么先把这个命名关系理清否则后面看手册会被绕晕。STM32H7系列早期型号H743、H750等带的是QUADSPI四线接口后来H7A3、H7B3这一批引入了OCTOSPI也就是八线SPI控制器。再往后的H7R、H7S以及新版本参考手册里ST把OCTOSPI彻底改名为XSPI。用户项目标题里写的XSPI本质上就是这一族支持1/2/4/8线、支持DDR模式的串行外设控制器。XSPI这个外设不是只用来接Flash的它把HyperRAM、PSRAM这类存储颗粒也列在了支持列表里。关键在于XSPI内置了一整套地址和时序生成逻辑可以把MCU侧的普通读写操作转换成HyperBus要求的CA命令序列。HyperBus的协议就是CS#拉低CK时钟沿上送入3字节的命令/地址CA然后根据读写方向交换数据。MCU侧你不需要自己组织这些CA帧XSPI控制器会根据你设置的AddressSize、BurstLength、DDR使能等参数自动生成。我记得刚接触HyperRAM时有个误区以为它和普通SPI Flash一样要先发命令码再发地址再读数据。其实HyperRAM的地址总线是26位的16M x 16bit的颗粒直接拼进CA序列里3字节CA中包含行地址、列地址、操作类型、突发长度、延迟位数等信息。命令和地址是合并的没有独立的指令码阶段。这对写驱动的人来说很舒服因为XSPI命令结构体里可以把InstructionMode直接关掉只保留Address和Data两个阶段。2. 硬件设计从颗粒选型到电路连接2.1 HyperRAM芯片怎么选容量和供电别搞错硬件上第一步是挑颗粒。市面上常见的HyperRAM有Infineon原Cypress的S27KL0641、S27KS0641ISSI的IS66WVS4M8BLL以及Apacer的APS256XXN。选型时重点看几个参数容量、电压、速度等级、封装。S27KL0641是64Mbit8MB容量1.8V供电最高支持DDR200也就是100MHz时钟下的DDR模式。IS66WVS4M8BLL也是8MB级别但ISSI有3.3V版本IS62WVS4M8BLL如果你的板子没有1.8V电源轨选3.3V版本会更省事。不过要注意1.8V版本通常是低功耗应用的选择3.3V版本速度快一点但功耗高看具体场景。容量选择上8MB是最常见的起步配置跑一个800x480带帧缓冲的GUI也够用。如果要用双缓冲甚至三缓冲建议直接上16MB或32MB。但容量越大对XSPI的地址位宽要求也越高比如32MB的HyperRAM地址需求就到25位以上部分段地址还需要额外配置。我建议初学者先从8MB入手代码和时序的判断都简单坑也少。供电组还要注意一个问题HyperRAM的VDD和VDDQ是两个独立的引脚。有些颗粒的VDD是1.8VVDDQ也是1.8V。让VDDQ单独供电主要是为了匹配不同MCU的IO电压。如果你的STM32H7的IO是3.3V而HyperRAM只支持1.8V IO中间就必须加电平转换。更稳妥的做法是直接选3.3V版本的HyperRAM或者选支持1.8V/1.2V双模式IO的新颗粒。看一眼数据手册里的IO电压范围再动手画板不然又是一顿飞线。2.2 XSPI硬件连接引脚与PCB走线要点以STM32H7A3/H7B3为例XSPI1外设可以映射到PB2、PE7、PA3、PG6等引脚。HyperRAM需要连接的基本信号有CK时钟、CS#片选、RWDS数据选通、DQ0到DQ78根数据线如果颗粒带RESET#则最好接到MCU的GPIO或RC复位电路上。XSPI为HyperRAM模式提供了专门的信号配置DQMS引脚由RWDS复用数据线用Octal模式就是DQ0到DQ7。电路设计里CK、CS#、RWDS以及DQ信号之间需要做等长处理。DDR模式的建立保持时间本来就紧张如果CK线和DQ线的长度差得太远采样窗口就会被吃掉。对于一个8MB的HyperRAM走线长度差控制在5mm以内是合理目标。在两层板上有时候难做到那至少保证CK线尽量短同时DQ线簇保持一致。如果MCU和HyperRAM不是同一个电源域或者它们之间有跨接、长走线、连接器那么串阻是值得加的。每根信号线上串22ohm左右的电阻能有效抑制过冲对信号完整性有实际帮助代价就是上升沿变缓一点点在100MHz的DDR200速率下通常可以接受。RWDS这根线在HyperRAM协议里有双重角色读模式下它由从机驱动用来给主机提供与数据同步的选通信号写模式下它变成由主机驱动相当于数据掩码/选通。所以RWDS不能当成普通GPIO随便拉一拉它是一根真正的双向信号线MCU侧对应的引脚必须配置成XSPI功能。上电和复位的顺序也不能忽略。HyperRAM数据手册里一般会给出上电后等待时间tRPH在此期间不能发起访问。我的做法是用一个RC延迟电路或者由MCU的GPIO控制RESET#上电后先保持RESET#低100ms再释放到高然后软件里再延迟50ms再初始化XSPI。虽然XSPI控制器本身也有复位系列处理但硬复位引脚多一道保障总归不亏。2.3 电源与去耦电容的实际处理HyperRAM对电源噪声并不是特别敏感但它工作在DDR模式下时瞬态电流变化快去耦电容绝对不能省。每个电源引脚旁边放一个100nF的0402电容引脚正下方再放一颗4.7uF或10uF的陶瓷电容是常见的组合。不要把所有去耦电容都堆在颗粒同一侧这是布线基本功但还是要强调一下尤其是CK频率超过50MHz之后电源回路电感对时序的影响会放大。如果板子上有1.8V供电轨注意它的载流能力。HyperRAM DDR模式全速读写时电流大概在几十毫安级别看着不大但如果你用LDO供电选一颗100mA以上输出能力的LDO就稳了不然DDR高速翻转时电源纹波会把采样点抖没。实测经验是100mA余量的LDO比勉强够用的LDO在长时间读写下稳定得多。3. 软件配置CubeMX参数到驱动代码3.1 CubeMX里XSPI的参数到底该怎么设打开STM32CubeMX在H7A3/H7B3的Pinout视图里找到XSPI1勾选Enabled然后切到Clock Configuration确认XSPI kernel clock的时钟源。XSPI的内核时钟可以选择PLL1Q、PLL2Q、PLL3Q或HSI等。我的选择是用PLL2Q输出设定到200MHz。为什么不用HSI因为DDR模式下XSPI对时钟抖动的要求比SDR模式高HSI的精度和稳定性虽然经过校准后可以工作但高速运行环境下不如PLL干净。当然还要配合分频器来得到目标总线时钟。在XSPI1 Parameter Settings里把模式设置成“内存映射模式”或者“间接模式”。如果只想快速验证读写先用间接模式如果最终想在地址0x90000000直接用指针访问HyperRAM则用内存映射模式。两种模式后面我会给代码。DQS功能要打开也就是RWDS信号有效否则在DDR模式下数据采样会失去基准。时钟分频这里先设置成2分频也就是200MHz的kernel clock经过2分频后得到100MHz的CK配合DDR模式就是200MT/s的翻转速率正好匹配HyperRAM的DDR200等级。等到验证通过后再尝试1分频追求更高性能。还有几个参数容易被忽略DeviceSize、AddressSize、SampleShifting、DataHoldTime。DeviceSize用来告诉XSPI外设访问的设备容量8MB对应是0x800000。AddressSize选24位或26位取决于颗粒型号S27KL0641是64Mbit容量地址空间需要26位IS66WVS4M8BLL是8MB24位地址就够了。SampleShifting其实就是读数据采样的相位调整DDR模式下可以尝试半周期或1/4周期偏移。DataHoldTime是写数据保持时间默认值能稳定工作先不动。我一般在CubeMX配置里还会手动打开“Memory mapped bypass mode”之类可选项时特别注意因为这些选项跟XSPI的FIFO、预取行为相关配置不当会导致读回的数据多出一拍或少一拍。保持默认能跑通之后再逐个实验微调。3.2 初始化代码流程与HyperRAM配置寄存器CubeMX生成的代码只是把外设时钟和GPIO配好HyperRAM还需要通过XSPI总线发送配置命令。标准流程分四步先写配置寄存器0CR0配置刷新周期和等待状态再写配置寄存器1CR1配置延迟和驱动强度然后等待颗粒初始化完成最后才允许正常读写。CR0中低4位用来配置刷新间隔典型值可以设成0xDF也就是每32个时钟周期刷新一次。具体数值可以参考颗粒手册不同供应商的标准略有差异。CR1中比较重要的是Initial Latency和Drive Strength。Initial Latency决定读操作时主机从发出地址到开始采样的等待周期数这个值如果设小了读数据会提前一拍如果设大了读数据会慢一整拍后续性能会变差。用HAL库操作XSPI时发送HyperRAM配置寄存器数据的代码一般长这样XSPI_CommandTypeDef cmd {0}; // 写入配置寄存器0 cmd.AddressMode XSPI_ADDRESS_24_BITS; cmd.Address 0x000000; // 配置寄存器0的CA地址 cmd.DataMode XSPI_DATA_16_BIT; cmd.DataSize 1; cmd.DQSMode XSPI_DQS_ENABLE; cmd.SIOOMode XSPI_SIOO_DDR; uint16_t cr0_value 0x00DF; // 刷新周期配置 HAL_XSPI_Transmit(hxspi1, cmd, (uint8_t *)cr0_value, HAL_MAX_DELAY);有朋友问过为什么地址是0x000000HyperRAM的CA序列不是把寄存器地址编码在地址位里吗确实如此但XSPI控制器会帮你把寄存器访问映射到某个地址颗粒数据手册里提供了寄存器映射表偏移地址0x000000对应的就是CR0。实际使用时要根据你选择的颗粒手册核对别照抄我的。写CR0之后还需要写CR1然后做一次把CR0的bit7从0置1的操作来激活配置。这段初始化逻辑不算复杂但它必须发生在任何普通读写之前。如果跳过这步直接往0x90000000地址写数据大概率写不进去或者读出来是FF。3.3 读写封装函数与直接访问地址内存映射初始化完成后我一般写一组基础读写函数。这组函数用间接模式适合验证时序和做小批量读写也方便加日志调试。uint8_t HyperRAM_ReadByte(uint32_t addr) { XSPI_CommandTypeDef cmd {0}; uint8_t data 0; cmd.AddressMode XSPI_ADDRESS_24_BITS; cmd.Address addr; cmd.DataMode XSPI_DATA_8_BIT; cmd.DataSize 1; cmd.DQSMode XSPI_DQS_ENABLE; cmd.SIOOMode XSPI_SIOO_DDR; HAL_XSPI_Receive(hxspi1, cmd, data, HAL_MAX_DELAY); return data; } void HyperRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { XSPI_CommandTypeDef cmd {0}; cmd.AddressMode XSPI_ADDRESS_24_BITS; cmd.Address addr; cmd.DataMode XSPI_DATA_8_BIT; cmd.DataSize len; cmd.DQSMode XSPI_DQS_ENABLE; cmd.SIOOMode XSPI_SIOO_DDR; HAL_XSPI_Transmit(hxspi1, cmd, buf, HAL_MAX_DELAY); }这种写法有个坑HyperRAM的最小访问粒度有时是16字节burst你用逐字节命令去读底层还是会被XSPI转换成固定长度的burst传输。效率不高但验证阶段完全够用。真正要提速还得用内存映射模式。初始化完成后把XSPI1映射到地址0x90000000空间然后直接操作指针volatile uint8_t *hyperram_base (volatile uint8_t *)0x90000000; hyperram_base[0x1000] 0xAA; uint8_t val hyperram_base[0x1000];这就像操作普通内部SRAM一样。但要注意地址映射模式下CPU访问外部存储是通过AXI总线走的如果开启了D-Cache这里的读写会先经过Cache一致性管理不处理好就等着踩坑。最粗暴的方法是把这段外部RAM空间配置成非Cacheable或者每次读写后做Clean和Invalidate。给一个MPU配置片段把0x90000000所在区域设为Normal、Non-CacheableMPU_Region_InitTypeDef MPU_InitStruct {0}; MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x90000000; MPU_InitStruct.Size MPU_REGION_SIZE_16MB; MPU_InitStruct.AccessPermission MPU_REGION_FULL_ACCESS; MPU_InitStruct.TypeExtField MPU_TEX_LEVEL_1; MPU_InitStruct.IsCacheable MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsBufferable MPU_ACCESS_BUFFERABLE; MPU_InitStruct.Number MPU_REGION_NUMBER0; HAL_MPU_ConfigRegion(MPU_InitStruct);开了非Cacheable之后读写速度会下降一些但代码逻辑简单可靠。如果你的应用对速度敏感可以把区域设成Cacheable然后读之前Invalidate写之后Clean每次操作多几条指令换来更高的DMA效率。驱动稳定之后再去折腾这部分优化前期千万别给自己加戏。3.4 DMA搬运把数据从外设搬进HyperRAM很多实际应用不会用CPU逐字节搬运而是让DMA把数据从外设FIFO直接写到HyperRAM。XSPI外设的DMA请求一般挂到DMAMUX上你可以选一个DMA通道源地址是外设寄存器目的地址是XSPI的内存映射空间。用DMA和内存映射地址配合时最典型的错误是忘记配置Cache一致性。DMA写数据到0x90000000后如果CPU之前已经通过Cache读过或写过同一段地址那么DMA写入的数据对CPU不可见读回来是旧的。这时候就需要先Clean数据Cache等DMA完成后Invalidate数据Cache。纯粹用非Cacheable区域可以规避这个问题。DMA的触发源也值得单独确认。XSPI的DMA请求不是所有DMA通道都能连接具体要查STM32H7的DMAMUX请求表。如果DMA配置没问题但就是搬不动八成是MUX请求号配错了此时打开调试器看DMA中断标志比盯着示波器猜快得多。4. 性能实测DDR模式到底能跑多快4.1 用DWT测读写带宽的方法验证性能时我习惯用DWT循环计数器精度高而且不用额外占用定时器。DWT是Cortex-M内核自带的调试组件在H7上直接可用不需要初始化太多东西DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; DWT-CYCCNT 0; uint32_t start DWT-CYCCNT; // 循环读写HyperRAM uint32_t stop DWT-CYCCNT; float cycles (float)(stop - start); float time_ms cycles / (SystemCoreClock / 1000.0f);读写8MB数据时间一除就能得到带宽。我把这段测试代码放在XSPI初始化后先跑一遍内存映射模式的纯写再跑纯读。测试数据有个讲究写测试用递增序列读测试比对增量是否一致如果只写全0再读全0很容易掩盖数据线和地址线短路的错误。4.2 实测结果到底能到多少我这边配置是STM32H7B3XSPI kernel clock 200MHz分频2得到100MHz CKDDR模式HyperRAM是S27KL0641内存映射模式直接读写。非Cacheable的情况下CPU纯写8MB大概在170MB/s左右纯读大概在180MB/s左右。听起来离理论带宽200MB/s还有点距离主要是因为非Cacheable模式下CPU每次读写外部总线都要等待XSPI的状态机周转指令流水线也会有停顿。把MPU改成Cacheable并保证一致性后读性能会提升明显因为CPU可以连续从Cache命中只有Cache miss时才会访问XSPI。实测读速度能到190MB/s甚至200MB/s写速度略低一点。如果是通过DMA搬运则更接近XSPI控制器的实际瓶颈大概能达到195MB/s。如果我把分频改成1也就是200MHz下直接跑DDR400很多HyperRAM颗粒不支持这个速率握手可能会失败。即使颗粒规格书里写了支持DDR200也不建议为了一点性能硬超因为PCB布线质量不够好的话在DDR200下已经要求RWDS采样点非常准确了再提频纯粹给自己制造稳定性隐患。4.3 性能瓶颈在哪里实测下来发现XSPI的带宽不是简单的“总线频率x数据位宽”。XSPI在内存映射模式下每发起一次访问内部要先做命令/地址阶段再做数据阶段两者之间有固定延迟。如果读操作是离散的随机地址控制器无法用burst把成本摊薄带宽会暴跌到几十MB/s。所以要想跑满速必须让XSPI处于连续burst状态。HyperRAM本身是DRAM它也有行激活、列访问的延迟。好在XSPI控制器会尽量把连续的地址访问映射成顺序burst把行激活的开销摊薄。我们实际做GUI显存时只要保证绘制缓冲是一整块连续地址空间带宽表现就很理想如果是随机小尺寸图形混合性能会肉眼可见下降。明白了这一层在设计数据布局时就要尽可能让高频访问区域线性化。5. 踩坑实录HyperRAM的几个典型问题5.1 第一次读永远读到垃圾数据这个坑几乎每个接HyperRAM的人都会遇到。现象是代码烧进去读HyperRAM某地址第一次读出来的值跟初始化写入的数据完全不同但继续读第二次、第三次就正常了。原因在于HyperRAM是DRAM架构读操作会触发内部行激活。第一个读命令发出时行激活还没有完成此时主机按照预设的Initial Latency去采样采到的数据自然不对而这一拍数据正好是“废数据”。要解决这个问题常见做法是在初始化完成后先做一次dummy read把第一次行激活的延迟消耗掉之后所有读取都正常。很多HyperRAM数据手册会在应用笔记里专门提这个行为Cypress的文档直言第一个读周期的数据不可预期。如果你用HAL库的HAL_XSPI_Receive建议在初始化后执行一次volatile uint8_t dummy 0; dummy HyperRAM_ReadByte(0x000000); dummy HyperRAM_ReadByte(0x000000);如果不行就多读几次然后正常使用。这个坑我在第一版驱动上整整排查了半天一度以为是CS#时序有问题后来把Initial Latency从4改到8情况有变化但没根治最后还是靠dummy read解决。后来翻H7手册的XSPI章节发现ST也没有明确说明HyperRAM第一次读会有废数据这个只能靠现场经验积累。5.2 写数据之后立刻读读出来是旧值这个情况的直觉嫌疑是写时序有问题但其实和HyperRAM的写恢复时间tRWR有关。HyperRAM的写操作完成之后CS#已经拉高但内部还需要一段时间完成数据写入这个时间大概在40ns左右。如果在tRWR尚未结束时就发起下一次访问控制器可能读到的仍然是写入前的旧值。解决方法是记住这个时序约束。如果是用HAL库的Transmit函数它内部有可能不负责自动插入tRWR你需要自己确认两次操作间隔。最简单粗暴的办法是每次写完后加个微秒级延时或者用DMA异步写然后轮询XSPI状态寄存器的忙标志等事务真正完成后再发下一次命令。更高效的做法是利用XSPI控制器自带的自动等待功能看手册里有没有tRWR相关配置位。H7系列较新的XSPI已经把外设时序参数化有些版本允许你设置Write Hold Time、Data Hold Time这些时间隐含地补足了写恢复周期。如果发现写后读不稳优先检查这两个参数而不是盲目加延时。5.3 CS#低电平时间不能太长很多朋友以为HyperRAM是RAM设备随便拉低CS#多长时间都行。实际上HyperRAM的刷新机制有个特点刷新只能发生在CS#为高电平期间。如果一次事务把CS#低电平拉得太久比如配置了超长的burst或者DMA持续占用总线颗粒就无法完成内部刷新时间一长数据就可能丢失。数据手册会给出一个最大CS#低电平时间tCSM常见值在几个微秒量级。正常用XSPI内存映射模式做连续burst时一次访问的数据量不会大到让CS#低这么长时间所以日常使用基本碰不到这个限制。但如果你开启了DMA循环传输让HyperRAM一直处于被访问状态就要小心了。做法是每次DMA传输之间预留一小段空闲窗口让CS#恢复为高颗粒完成内部刷新。我遇到过一种情况DMA循环搬运摄像头数据到HyperRAM跑几分钟后画面出现零星花点就是tCSM被突破导致的。后来我把DMA传输的总长度拆成小块块与块之间加入微小的延时现象就消失了。这种问题用示波器抓CS#波形最容易发现——如果CS#一直是低平的直线大概率就是出事了。5.4 读回全FF或者全00读回全FF第一个怀疑对象是时钟或者采样点第二个是引脚配置第三个是HyperRAM压根没被正确初始化。先看引脚XSPI的MCU引脚必须在CubeMX里配置成正确的AF模式不能只设置输出模式。如果你用手搭的板子飞线接触不良也容易读回全FF。再查时钟DDR模式下CK必须正常翻转可以用逻辑分析仪到CK引脚上看波形没有波形就去查PLL2Q是否锁定、XSPI使能位是否设置。读回全00则多半是片选极性、数据线短路、CS#没拉低这几个问题。在H7手册的XSPI章节里专门有一节叫“Debug mode”可以在外设空闲时把CK和DQ引脚释放出来从调试器里直接观察信号。这个功能排查这种问题时非常有用相当于把XSPI变成了串口示波器。具体操作是设置XSPI_CR的DBG位置位然后单步执行看每个引脚的电平是否和预期一致。不过记得调试完要把DBG位清掉否则会影响正常运行。5.5 Cache一致性和MPU配置这个不算是HyperRAM独有问题但在内存映射模式下特别容易踩。H7的CPU和DMA都走AXI总线而XSPI映射区域挂在另一个AXI端口上CPU访问时如果启用了D-Cache数据可能被暂存在Cache里而没有立即写到XSPI。反过来DMA把数据搬运到映射区域后CPU读到的可能是Cache旧值。最直接的规避方案是把0x90000000这段区域通过MPU配置成Non-Cacheable。代价是性能略低但换来了极高的可预期性。如果你做了Cacheable就一定要在每次写后执行SCB_CleanDCache每次读前执行SCB_InvalidateDCache。用DMA时尤其要小心DMA写完后必须Invalidate否则在Cache命中的情况下读到的还是旧数据。我在写过一段音频采集代码时犯过这个错误DMA持续往HyperRAM里写PCM数据CPU循环读取做处理结果播放出来的声音断断续续还有爆音。查了很久才发现是Cache命中了旧数据CPU以为DMA没写入。把MPU改成Non-Cacheable后声音立刻正常。从那以后凡是用XSPI外挂RAM的工程MPU配置永远排在驱动初始化的前面。5.6 初始化顺序与Timeout设置如果HyperRAM驱动初始化失败大多数情况下问题出在时序上但也有可能是HAL_XSPI_Receive的Timeout设置不合理。HAL库的收发函数是轮询等待如果你把Timeout设得太小在调试器单步运行或主频较低的时候函数会提前返回超时错误。初始化阶段宁可用HAL_MAX_DELAY先把功能跑通再根据实际时间优化。另外一个容易被忽略的点是XSPI外设自身的软件复位。CubeMX生成的初始化代码在调用HAL_XSPI_Init之前会先执行XSPI外设的复位。如果你在应用运行中临时改变了XSPI的kernel clock频率最好也顺便执行一次XSPI_DeInit不然有些内部状态不会自动刷新。这个我在切换低功耗模式后踩过HyperRAM无法唤醒只能完整复位外设和颗粒才能恢复。6. 几个后续可以扩展的方向HyperRAM接上之后能做的事情很多。最常见的是把它用作GUI的帧缓冲配合TouchGFX或者LVGL可以将分辨率从320x240一下提升到800x480甚至更高。另一个典型用途是给音频编解码器做大缓冲比如处理WAV文件或者做降噪算法时数据缓冲放在HyperRAM里不占内部SRAM。如果是做摄像头应用帧缓冲放在HyperRAM里也很顺手。单帧VGA图像大概需要300KB8MB空间足够放十几帧可以做环形缓冲。注意摄影场景下DMA写入和CPU读取的带宽竞争如果同时还在刷屏XSPI会成为瓶颈这时需要把XSPI时钟尽量拉高同时考虑把GUI放在LTDC直接读取避免额外的内存拷贝。我目前这个HyperRAM项目还在继续扩展准备把XSPI2也挂一颗HyperRAM组成多块内存池一块做GUI帧缓冲一块做摄像头环形缓冲另一块做协议栈缓冲。需要先确认STM32H7的两个XSPI外设可以独立配置和运行而且映射的基地址不冲突。我计划下一期记录详细讲讲双XSPI并行访问的性能表现。不过在实际设计里还要留个心眼很多STM32H7型号的XSPI1不支持所有引脚同时布线两个XSPI外设如果都用作Octal模式引脚总数会非常多可能要牺牲一部分其他外设。在做板子之前一定要把H7对应型号的引脚复用表完整过一遍避免画到一半发现引脚不够。
返回列表