STM32H743实现16位SDRAM与PSSI并口共存:自刷新+引脚复用方案解析

发布时间:2026/8/31 22:38:44
STM32H743实现16位SDRAM与PSSI并口共存:自刷新+引脚复用方案解析 我们组最近接了一个视觉引导的活儿主控是STM32H743外围挂了一片16bit SDRAM做大容量帧缓冲前面还要用PSSI接一个16bit并口输出的CMOS sensor。原理图评审的时候硬件工程师直接抛过来一个问题FMC的D0~D15和PSSI的D0~D15明明是按在同一组引脚上的这两个外设到底怎么同时用这个问题就是开头那个标题里“Simultaneous 16-bit SDRAM and 16-bit PSSI Support”要解决的东西。我在这套设计上折腾了两个多星期把FMC的SDRAM时序、PSSI的DMA搬运、GPIO引脚复用切换和自刷新进入退出整个链路都趟了一遍中间踩了不少坑也翻了不少参考手册。今天就把这套实现方法和排错过程完整盘一遍给正在做类似项目的朋友一个可以照着抄的方案。这套方案适合谁呢主要是用STM32H7系列做机器视觉、高速并行数据采集、或者需要外挂大容量SDRAM同时又想用PSSI接外部并口设备的朋友。以下内容偏实操我会从原理讲到配置再把代码和排查经验都亮出来保证你看完能直接拿去用。1. 项目背景与核心痛点1.1 一个引脚两个外设谁用STM32H7系列的PSSI外设全称是Parallel Synchronous Slave Interface并行同步从接口。它的核心作用是从外部设备接收并行数据流典型应用是接CMOS图像传感器、高速ADC、或者FPGA输出的并行数据。PSSI支持8bit和16bit两种数据宽度16bit模式下会占用D0~D15这16根数据线再加上一个外部时钟输入和一个同步信号输入。问题就出在这16根数据线上。在STM32H7的引脚复用表里FMC外设的16bit SDRAM数据总线D0~D15和PSSI的16bit数据总线D0~D15映射到了同一组GPIO上。比如H743这颗芯片上PE0到PE15这组引脚既可以是FMC_D0~FMC_D15AF12也可以是PSSI_D0~PSSI_D15AF13。这意味着什么呢就是硬件电平层面你不可能同时接两个外部器件到同一根引脚上然后把两路信号都正确读出来。就像家里墙上只有一个插座你不可能同时插冰箱和微波炉物理上就不成立。所以这个“Simultaneous Support”绝不是字面上的“同一时刻同时读写两个外设”而是在系统层面让这两个外设可以轮流使用同一组引脚各自完成工作而且不破坏彼此的数据。1.2 SDRAM的刷新比想象中脆弱如果只是引脚冲突那问题其实不难GPIO复用功能切换一下就行。真正麻烦的是SDRAM这种存储介质的特性。SDRAM内部是电容存储电荷电荷会自然泄漏所以它必须不断地靠刷新操作来维持数据。FMC外设里有一个刷新定时器会定时向SDRAM发自动刷新命令通常是每行15.625us刷新一次整个芯片64ms内把所有行刷一遍。只要刷新停止存储电容上的电荷就会逐渐丢失数据也就没了而且这个时间窗口很短不是几分钟而是微秒级就能观察到数据错误。那么问题就来了PSSI要占用数据线SDRAM就得让位可SDRAM又需要持续刷新才能保住数据。你如果不做任何处理直接把引脚复用切到PSSISDRAM这边刷新被切断等PSSI工作完切回来SDRAM里的数据早就是一片乱码了。上一帧图像、运行日志、算法参数全没了。这是整个方案里最核心的矛盾。所以我才说这套设计要解决的不是引脚切换而是“在SDRAM不能访问的时间段里怎么保住SDRAM里的数据”。1.3 “同时支持”的正确打开方式ST官方参考设计里给的思路其实就是两个字休眠。在切到PSSI之前先把SDRAM弄进自刷新模式SDRAM进入自刷新后会靠内部电路自动维持数据不需要外部FMC持续发刷新命令。然后我们把GPIO复用切到PSSI让PSSI采集外部数据采完后再切回来发一个命令让SDRAM退出自刷新恢复工作。整个流程就像让SDRAM睡一觉睡之前把门锁好醒来之后数据都还在。PSSI工作的这段时间就是SDRAM的“午休时间”。这个思路听起来简单但实际落地时涉及时序参数、外设优先级、DMA衔接、状态判断等多个环节任何一个细节没处理好都会出奇奇怪怪的问题。接下来的内容我会按照“为什么这么做”和“具体怎么做”两个维度把整个技术方案讲透。2. 核心方案设计自刷新 引脚复用切换2.1 方案对比纯软件切换 vs 外部总线开关拿到这个需求后我们团队其实讨论过两个方案。第一个方案就是纯软件切换。在MCU内部把GPIO从FMC的AF12切换到PSSI的AF13同时配合SDRAM自刷新。这个方案的优点是硬件零改动、成本最低、切换速度也快GPIO复用切换本身只要几个GPIO寄存器操作微秒级别就完成了。缺点是SDRAM在PSSI工作期间不能访问所有采集的数据必须先落到内部SRAM里等到切回来之后再搬运到SDRAM。第二个方案是在PCB上加外部总线开关比如SN74CB3Q16244这类芯片。用MCU的GPIO控制总线开关的使能脚把FMC的数据总线和PSSI的数据总线在物理上分开。这样做的好处是两个外设可以在同一时刻独立工作SDRAM不用进自刷新PSSI也能持续采集。但代价也很明显增加了BOM成本、PCB走线难度还多了一个控制逻辑要维护。我们在实际项目里选了第一个方案纯软件切换。原因很直接我们这路PSSI是接CMOS sensor的采集一帧图像用不了多少时间SDRAM在自刷新模式下可以舒舒服服地睡几毫秒完全不影响系统整体吞吐。而且纯软件方案不需要改PCB后续迭代也灵活。如果你遇到的是非常大数据量的连续采集业务SDRAM几乎不能休眠那就得认真评估外部总线开关方案了。2.2 SDRAM自刷新的工作原理与用法SDRAM自刷新英文叫Self Refresh Mode。正常工作状态下SDRAM由外部FMC控制器定时发Refresh命令刷新一整行然后行地址自动递增这样逐步把所有行都刷一遍。一旦进入自刷新模式SDRAM内部自己产生刷新命令不再依赖外部控制器外部时钟甚至都可以停掉只要供电还在数据就不会丢。在STM32H7的FMC控制器上进入自刷新的过程是这样的先向FMC的SDRAM命令寄存器写一条自刷新进入命令FMC会把这个命令发送到SDRAM芯片上。SDRAM收到命令后内部开始自刷新CKE信号拉低外部时钟可以停掉。这时候FMC控制器就不再频繁访问SDRAM了我们利用这个窗口把GPIO切给PSSI。退出自刷新也很简单向FMC命令寄存器写一条自刷新退出命令然后等待SDRAM从自刷新状态恢复。注意SDRAM从自刷新退出到可以正常访问需要一个恢复时间在SDRAM数据手册里叫tXSR通常70ns左右。FMC控制器会自动处理这个等待但如果你手动去读SDRAM数据最好等状态寄存器表明退出完成后再访问。这里有一个非常重要的细节进入自刷新前一定要确保SDRAM没有正在进行的读写操作。你需要等FMC_SDSR状态寄存器里的BUSY位拉低或者等HAL库里HAL_SDRAM_SendCommand返回成功后再切GPIO。否则可能出现命令卡住、SDRAM不知道自己在干嘛的尴尬局面。2.3 数据路径PSSI → DMA → SRAM → SDRAM明确了让SDRAM休眠这套思路后数据流设计就顺理成章了。PSSI工作期间SDRAM不可用所以PSSI采集的数据只能先进内部SRAM。具体路径是这样的外部设备比如CMOS sensor在PCLK时钟驱动下把并行16bit数据打到PSSI引脚上。PSSI内部FIFO接收数据攒到一定字节数后触发DMA请求。DMA把数据从PSSI的数据寄存器搬到内部SRAM这个过程不需要CPU参与DMA控制器直接完成。等PSSI完成一帧采集DMA会触发中断CPU在中断里知道数据已经准备好了。然后我们把SDRAM从自刷新唤醒再用memcpy或者DMA2D把数据从内部SRAM拷贝到SDRAM的地址空间里。这条链路里隐藏着一个容量约束很重要PSSI采集期间所有数据只能暂存在内部SRAM所以单次采集的数据总量不能超过可用SRAM大小。比如H743内部AXI SRAM有512KB但去掉系统堆栈、RTOS任务栈、中间变量之后实际能用来做采集缓冲的可能也就300KB左右。如果300KB装不下你的一帧图像要么降低分辨率要么拆成小块分多次采集每次采一小块就切回SDRAM写入再切回来继续采。我们现在用的sensor是640x480分辨率16bit灰度图一帧约600KB内部SRAM一次装不下。所以我们把一帧拆成上下两个半帧每次采完半个帧就切换一次代价是切换频率翻倍但数据完整性就保住了。这个取舍在工程上很常见性能和容量永远在博弈。3. 实操配置CubeMX HAL一步步来3.1 FMC SDRAM配置与初始化校验先说FMC这一侧。我用的是STM32CubeMX生成初始化代码再手动补自刷新和引脚切换逻辑。CubeMX里配置SDRAM时关键参数有以下几个数据宽度选16bit行列地址位宽根据你实际SDRAM芯片决定。我们用的SDRAM是4bank、13行、9列所以Rows填13Columns填9。CAS延迟CL根据SDRAM频率选择100MHz下多数SDRAM配置成3个时钟周期。SDCLK分频系数也在这时候定HCLK跑400MHz的话分频4得到100MHz SDRAM时钟对大部分SDRAM芯片都很安全。时序参数我直接按照SDRAM数据手册来。下面是我们这颗SDRAM在100MHz时钟下的配置示例CubeMX里以ns为单位填写时序项填写的值换算成SDRAM周期TRCD行地址到列地址延迟20ns2 cyclesTRP预充电到激活20ns2 cyclesTRAS激活到预充电42ns5 cyclesTRC激活到激活/刷新63ns7 cyclesTWR写恢复2 cycles2 cyclesTXSR自刷新退出到下一次有效命令70ns7 cyclesTMRD模式寄存器到激活2 cycles2 cycles注意CubeMX内部会自动把这些ns值转换成SDRAM时钟周期并且向上取整。如果你发现生成代码里的周期数和你预期的有一点点偏差不用慌只要不小于datasheet要求就行。刷新周期COUNT是另一个重点。公式是COUNT (SDRAM刷新周期 × SDRAM频率) / SDRAM行数 - 20按我们这颗SDRAM刷新周期64ms行数4096SDRAM频率100MHzCOUNT (0.064 × 100000000) / 4096 - 20 1542这个值填到FMC刷新定时器寄存器里FMC会自动以这个间隔向SDRAM发自动刷新命令。这里要提醒一句COUNT计算公式里的频率是SDRAM时钟频率不是HCLK不要搞混了。CubeMX生成初始化代码后通常会调用HAL_SDRAM_Init然后还需要手动发送初始化命令序列时钟使能、预充电、至少8次自动刷新、配置模式寄存器。HAL库把这部分封装成了HAL_SDRAM_SendCommand按照参考手册的顺序依次发送就行。如果这一步没走对SDRAM初始化会卡在状态寄存器BUSY上也读不出正确数据。3.2 PSSI与DMA配置PSSI的配置相对简单。CubeMX里选好PSSI后主要配置这几项数据宽度16bit外部时钟极性根据sensor的PCLK极性选择sensor数据在上升沿稳定我就选了上升沿采样同步信号极性这个看sensor的同步信号是高有效还是低有效FIFO阈值我配了4字节意思是FIFO里攒够4字节就触发一次DMA请求减少DMA请求频率DMA配置是PSSI这侧的命门。PSSI的DMA请求要接到DMA控制器上模式选择外设到内存外设地址就是PSSI_DATA寄存器地址内存地址是内部SRAM缓冲数据宽度配置成半字16bit与PSSI的16bit模式对齐。DMA模式我选择正常模式而不是循环模式因为PSSI是连续数据流循环模式会不断覆盖缓冲区。这里有一个特别容易踩的坑DMA传输方向里的数据宽度如果配成了字节宽度而PSSI工作在16bit模式搬运出来的数据顺序会乱掉高低字节交错图像看起来就是花屏加错位。我做调试时在这上面浪费了大半天时间后来是拿示波器看PSSI引脚上的字节顺序才定位到问题是DMA宽度没对齐。3.3 引脚复用切换与自刷新控制代码整个方案的核心操作都在切换函数里。我写了一个简单的状态机两个核心函数一个切到PSSI一个切回FMC。先看进入PSSI的切换函数逻辑是先让SDRAM进入自刷新再关闭FMC时钟最后把GPIO复用从AF12切到AF13。void System_Enter_PSSI_Mode(void) { FMC_SDRAM_CommandTypeDef cmd; // 1. 进入自刷新 cmd.CommandMode FMC_SDRAM_CMD_SELF_REFRESH_MODE; cmd.CommandTarget FMC_SDRAM_CMD_TARGET_BANK