1. 为什么需要外部SRAM扩展

在嵌入式开发中,我们经常会遇到STM32内部内存不够用的情况。尤其是处理图像、音频或者复杂算法时,那点可怜的片上SRAM根本不够塞牙缝的。我记得有一次做图像处理项目,刚把一幅320x240的RGB图像加载到内存,系统就直接卡死了——内存爆了!

这时候外部SRAM就派上用场了。通过FSMC(Flexible Static Memory Controller)接口,我们可以轻松扩展外部存储空间。FSMC就像是STM32的"内存管家",帮我们管理外部存储器,让CPU能够像访问内部内存一样直接访问外部SRAM。

我比较喜欢用IS62WV51216这款SRAM芯片,容量1MB,16位数据宽度,速度也够快。关键是价格便宜,十来块钱就能买到,比买大容量的STM32芯片划算多了。不过要注意,不同型号的SRAM时序参数可能略有差异,一定要先看 datasheet 再配置。

2. FSMC基础与SRAM连接

2.1 FSMC内存映射与Bank划分

FSMC把外部存储器分成4个Bank,每个Bank有256MB的地址空间。对于SRAM,我们通常使用Bank1,这个Bank又分为4个子Bank,每个子Bank有64MB地址空间。实际使用时,我们只需要连接一个SRAM芯片,所以用Bank1的第一个子Bank(NE1)就够了。

硬件连接时要注意地址线的对应关系。比如我用的是16位宽度的SRAM,那么FSMC_A0应该接SRAM的A0,FSMC_A1接A1,依此类推。数据线也是类似,FSMC_D0-D15接SRAM的DQ0-DQ15。别忘了控制信号线:片选NE1、写使能NWE、读使能NOE。

这是我常用的连接方式:

  • 地址线:FSMC_A[0:18] → SRAM_A[0:18]
  • 数据线:FSMC_D[0:15] → SRAM_DQ[0:15]
  • 控制线:FSMC_NE1 → SRAM_CS
  • FSMC_NOE → SRAM_OE
  • FSMC_NWE → SRAM_WE

2.2 硬件布局注意事项

布线时要特别注意信号完整性。地址线和数据线要等长走线,尽量减少stub长度。我在第一个项目里就吃过亏,因为布线太随意,导致SRAM读写不稳定,时不时就出现数据错误。

电源去耦也很重要。每个SRAM芯片的VCC引脚都要加一个0.1uF的陶瓷电容,尽量靠近芯片引脚。如果板子空间允许,还可以加一个10uF的钽电容作为 bulk 电容。

3. HAL库时序结构体详解

3.1 时序参数实战意义

FSMC_NORSRAMTimingInitTypeDef这个结构体是配置时序的关键,里面每个参数都直接影响读写稳定性。我先说说最重要的三个参数:

AddressSetupTime(ADDSET)是地址建立时间,表示地址信号稳定后要等多久才能发出读/写使能。设得太短,地址还没稳定就操作,肯定读写出错;设得太长,又会影响性能。我一般从4个HCLK周期开始调试。

DataSetupTime(DATAST)是数据建立时间,这个参数尤其重要。读操作时,它表示读使能信号发出后要等多久才能采样数据;写操作时,表示写使能信号要保持多久才能确保数据被可靠写入。

AddressHoldTime是地址保持时间,表示操作完成后地址信号还要保持多久。这个参数相对不那么敏感,我通常设为1个HCLK周期就够了。

3.2 时序调试经验分享

调试时序时我有个小技巧:先用保守的参数确保基本读写功能,再逐步优化。比如先设ADDSET=7,DATAST=9,这样虽然慢但肯定能工作。然后用逻辑分析仪抓波形,看实际需要多少时间,再慢慢减小参数。

记得有一次调IS62WV51216,datasheet上写地址建立时间最小15ns,我的HCLK是84MHz(约11.9ns周期),所以ADDSET至少需要2个周期。但实际测试发现设成3个周期才稳定,这就是留了点余量。

4. 初始化结构体全面解析

4.1 关键配置参数

FSMC_NORSRAM_InitTypeDef这个结构体包含了FSMC的所有控制参数。MemoryType一定要选FSMC_MEMORY_TYPE_SRAM,不然时序对不上。MemoryDataWidth根据你的SRAM来选,16位的SRAM就选FSMC_NORSRAM_MEM_BUS_WIDTH_16。

DataAddressMux通常设为禁用(FSMC_DATA_ADDRESS_MUX_DISABLE),因为SRAM不需要地址数据复用。BurstAccessMode也建议禁用,除非你用同步SRAM并且真的需要突发传输。

ExtendedMode是个很有用的功能。如果启用,读时序和写时序可以分开配置,这样能分别优化读写性能。我一般都启用这个功能,因为SRAM的读时序和写时序要求往往不同。

4.2 模式选择与性能权衡

AccessMode有A、B、C、D四种模式,对于SRAM我们通常用Mode A。每种模式的时序略有不同,Mode A是最通用的模式,适合大多数SRAM芯片。

WriteOperation一定要启用,不然只能读不能写——这个坑我踩过!当时调试了半天,为什么写数据没反应,最后发现是这个参数设成了DISABLE。

WaitSignal相关参数通常都禁用,除非你的SRAM支持等待信号功能。现在大多数SRAM都不需要这个功能。

5. 完整配置代码实战

5.1 工程设置与引脚配置

首先在CubeMX中启用FSMC功能,选择NOR/PSRAM控制器。根据你的硬件连接,选择正确的Bank(通常是Bank1),数据宽度(16位或8位),地址线数量(根据SRAM容量决定)。

引脚会自动配置,但最好手动检查一下。确保所有用到的地址线、数据线、控制线都正确映射。特别是片选信号NE1/NE2/NE3/NE4,一定要和你硬件上连接的片选线对应。

记得在Clock Configuration里确认HCLK频率,因为FSMC的时序参数都是基于HCLK周期计算的。我建议先把HCLK设在一个合理的频率,比如80MHz或100MHz,调试完成后再考虑是否超频。

5.2 时序参数配置代码

SRAM_HandleTypeDef hsram1;
FSMC_NORSRAM_TimingTypeDef Timing = {0};

void SRAM_Init(void)
{
    // 时序参数配置
    Timing.AddressSetupTime = 3;        // 3个HCLK周期
    Timing.AddressHoldTime = 1;         // 1个HCLK周期
    Timing.DataSetupTime = 6;           // 6个HCLK周期
    Timing.BusTurnAroundDuration = 0;   // 不需要总线翻转
    Timing.CLKDivision = 0;             // 异步模式无效
    Timing.DataLatency = 0;             // 异步模式无效
    Timing.AccessMode = FSMC_ACCESS_MODE_A; // 模式A

    // FSMC初始化
    hsram1.Instance = FSMC_NORSRAM_DEVICE;
    hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
    hsram1.Init.NSBank = FSMC_NORSRAM_BANK1;
    hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
    hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
    hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
    hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
    hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
    hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
    hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
    hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
    hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
    hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE; // 启用扩展模式
    hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
    hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
    hsram1.Init.ContinuousClock = FSMC_CONTINUOUS_CLOCK_DISABLE;
    hsram1.Init.WriteFifo = FSMC_WRITE_FIFO_ENABLE;
    hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE;

    // 读写时序都使用相同的配置
    hsram1.Init.ReadWriteTimingStruct = &Timing;
    hsram1.Init.WriteTimingStruct = &Timing;

    if (HAL_SRAM_Init(&hsram1, &Timing, &Timing) != HAL_OK)
    {
        Error_Handler();
    }
}

这段代码配置了一个比较保守的时序参数,适合大多数16位SRAM芯片。在实际项目中,你可能需要根据具体芯片的datasheet调整这些参数。

5.3 读写测试与验证

配置完成后一定要做全面的读写测试。我通常这样做:先测试单个地址的读写,再测试地址边界,最后做全内存测试。

// 简单的读写测试函数
uint32_t SRAM_Test(void)
{
    volatile uint16_t *sram_addr = (uint16_t*)0x60000000; // Bank1的起始地址
    uint32_t errors = 0;
    
    // 测试模式:写然后读回验证
    for(uint32_t i = 0; i < 0x10000; i++) {
        sram_addr[i] = (uint16_t)(i & 0xFFFF);
        if(sram_addr[i] != (uint16_t)(i & 0xFFFF)) {
            errors++;
        }
    }
    
    return errors;
}

如果测试发现数据错误,可能是时序参数不合适。这时候就需要用逻辑分析仪或者示波器抓波形,看看到底是哪个环节出了问题。

6. 性能优化技巧

6.1 时序参数优化

优化时序参数时要在稳定性和性能之间找平衡。我通常先用保守参数确保功能正常,然后逐步减小ADDSET和DATAST,直到出现错误,再稍微回退一点。

如果启用扩展模式,可以分别优化读时序和写时序。一般来说,SRAM的读操作比写操作更敏感,所以读时序参数要相对保守一些,写时序可以更激进。

BusTurnAroundDuration这个参数在SRAM中通常设为0,但在某些特殊情况下,如果读写切换时出现冲突,可以适当增加这个值。

6.2 内存访问模式优化

虽然FSMC支持突发传输,但对于异步SRAM来说,线性访问通常效率更高。因为突发传输需要SRAM支持,而大多数异步SRAM并不支持这个功能。

如果确实需要提高访问效率,可以考虑使用DMA来传输数据。FSMC可以和DMA配合使用,实现内存到内存的数据传输,这样能减轻CPU负担。

WriteFifo功能建议启用,它能提高写操作的效率,特别是在连续写操作时。这个功能会在FSMC内部建立一个写缓冲区,优化写时序。

7. 常见问题与解决方案

7.1 硬件相关问题

最常见的问题是信号完整性问题。如果读写不稳定,首先检查PCB布局:地址和数据线是否等长?是否有足够的去耦电容?信号线是否有过长的stub?

电源质量也很重要。FSMC操作时电流变化较大,如果电源纹波太大,会影响SRAM的工作稳定性。建议用示波器检查SRAM的VCC引脚,看看在读写操作时是否有明显的电压跌落。

7.2 软件配置问题

时序参数配置不当是最常见的软件问题。一定要仔细阅读SRAM芯片的datasheet,了解各项时序要求,然后根据HCLK频率计算需要的时钟周期数。

地址映射错误也是常见问题。记得FSMC的地址是映射到STM32的内存空间的,Bank1的起始地址是0x60000000。如果你连接的是NE1,那么访问0x60000000就是访问SRAM的第一个地址。

数据宽度不匹配会导致奇怪的问题。如果你配置的是16位数据宽度,但试图按8位方式访问,会出现数据错位。一定要保证访问方式与配置一致。

7.3 调试技巧

没有逻辑分析仪的话,可以用GPIO翻转来粗略测量时序。在读写操作前后翻转GPIO,然后用示波器观察,就能大概知道操作耗时。

如果怀疑是时序问题,可以尝试增加时序参数,如果问题消失,就说明原参数确实太小了。然后再逐步减小参数,找到最优值。

HAL库提供了错误回调函数,可以重写SRAM_ErrorCallback函数来捕获错误。虽然信息有限,但至少能知道是否发生了错误。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐