最近在做一个嵌入式项目时遇到了内存不足的问题,STM32F407的内部SRAM只有192KB,在处理大量数据时明显不够用。于是决定通过FSMC接口扩展外部SRAM,选择了IS62WV51216这款1MB容量的SRAM芯片。这个方案不仅解决了内存瓶颈,还让我深入理解了STM32的内存映射机制和FSMC控制器的工作原理。
IS62WV51216是一款高速异步静态RAM,关键参数如下:
硬件连接上需要注意几个要点:
地址线连接:
数据线连接:
控制信号:
硬件设计时特别注意:FSMC的NE1-NE4对应不同的存储区域,我们使用NE4,对应的地址范围是0x6C000000-0x6FFFFFFF。这个基地址会在软件中频繁使用。
在设计原理图时,有几个关键点需要特别注意:
电源去耦:
信号完整性:
未用引脚处理:
FSMC的配置是整个过程的核心,主要分为几个步骤:
c复制static void SRAM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能所有用到的GPIO时钟
RCC_AHB1PeriphClockCmd(FSMC_A0_GPIO_CLK | FSMC_A1_GPIO_CLK | ... , ENABLE);
// 配置GPIO为复用功能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
// 逐个配置每个引脚
GPIO_InitStructure.GPIO_Pin = FSMC_A0_GPIO_PIN;
GPIO_Init(FSMC_A0_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(FSMC_A0_GPIO_PORT, FSMC_A0_GPIO_PinSource, FSMC_GPIO_AF);
// 重复上述过程配置所有地址、数据和控制引脚
}
c复制void FSMC_SRAM_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;
// 配置时序参数
readWriteTiming.FSMC_AddressSetupTime = 0x00; // 地址建立时间
readWriteTiming.FSMC_AddressHoldTime = 0x00; // 地址保持时间
readWriteTiming.FSMC_DataSetupTime = 0x08; // 数据建立时间
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
readWriteTiming.FSMC_CLKDivision = 0x00;
readWriteTiming.FSMC_DataLatency = 0x00;
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A; // 模式A
// 配置FSMC参数
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
// 其他参数配置...
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
}
提供了两种访问SRAM的方式:
c复制#define Bank1_SRAM4_ADDR ((uint32_t)0x6C000000)
// 写入8位数据
*(uint8_t*)(Bank1_SRAM4_ADDR + offset) = data;
// 读取8位数据
data = *(uint8_t*)(Bank1_SRAM4_ADDR + offset);
c复制void SRAM_WriteBuffer(uint32_t* pBuffer, uint32_t WriteAddr, uint32_t Size)
{
for(; Size != 0; Size--) {
*(uint32_t*)(Bank1_SRAM4_ADDR + WriteAddr) = *pBuffer++;
WriteAddr += 4;
}
}
void SRAM_ReadBuffer(uint32_t* pBuffer, uint32_t ReadAddr, uint32_t Size)
{
for(; Size != 0; Size--) {
*pBuffer++ = *(uint32_t*)(Bank1_SRAM4_ADDR + ReadAddr);
ReadAddr += 4;
}
}
为确保SRAM工作正常,实现了全面的测试函数:
c复制uint8_t SRAM_Test(void)
{
uint32_t counter;
uint8_t write8, read8;
uint16_t write16, read16;
// 8位测试
for(counter = 0; counter < IS62WV51216_SIZE; counter++) {
write8 = (uint8_t)counter;
*(uint8_t*)(Bank1_SRAM4_ADDR + counter) = write8;
read8 = *(uint8_t*)(Bank1_SRAM4_ADDR + counter);
if(read8 != write8) return 0;
}
// 16位测试
for(counter = 0; counter < IS62WV51216_SIZE/2; counter++) {
write16 = (uint16_t)counter;
*(uint16_t*)(Bank1_SRAM4_ADDR + 2*counter) = write16;
read16 = *(uint16_t*)(Bank1_SRAM4_ADDR + 2*counter);
if(read16 != write16) return 0;
}
return 1;
}
通过串口输出测试信息,可以直观看到测试过程:
code复制野火外部 SRAM 测试
正在检测SRAM,以8位、16位的方式读写sram...
SRAM读写测试正常!
指针方式访问SRAM
指针访问SRAM,写入数据0xAA
读取数据:0xAA
指针访问SRAM,写入数据0xBBBB
读取数据:0xBBBB
指针访问SRAM,写入数据0xCCCCCCCC
读取数据:0xCCCCCCCC
绝对定位访问SRAM,写入数据0xDD,读出数据0xDD,变量地址为6C000000
通过GCC的扩展属性,可以直接将变量分配到SRAM中:
c复制uint8_t sram_buffer[1024] __attribute__((at(Bank1_SRAM4_ADDR)));
这种方法特别适合需要大块连续内存的应用,如图形缓冲区。
基于外部SRAM实现简单的内存管理:
c复制typedef struct {
uint32_t start_addr;
uint32_t size;
uint32_t used;
} sram_pool;
void sram_init(sram_pool* pool, uint32_t start, uint32_t size) {
pool->start_addr = start;
pool->size = size;
pool->used = 0;
}
void* sram_alloc(sram_pool* pool, uint32_t size) {
if(pool->used + size > pool->size) return NULL;
void* ptr = (void*)(pool->start_addr + pool->used);
pool->used += size;
return ptr;
}
void sram_free(sram_pool* pool) {
pool->used = 0;
}
通过分散加载文件(Scatter File),可以指定特定段使用外部SRAM:
code复制LR_IROM1 0x08000000 0x00100000 { ; 加载区域
ER_IROM1 0x08000000 0x00100000 { ; 代码区
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00030000 { ; 内部SRAM
.ANY (+RW +ZI)
}
RW_IRAM2 0x6C000000 0x00100000 { ; 外部SRAM
*.o (EXTSRAM)
}
}
现象:偶尔读取的数据与写入的不一致
可能原因:
解决方案:
现象:低地址访问正常,高地址失败
可能原因:
解决方案:
现象:访问速度比理论值慢很多
可能原因:
解决方案:
我在实际项目中,通过合理使用外部SRAM,将图像处理算法的性能提升了3倍以上。关键是将频繁访问的大数据缓冲区放在外部SRAM中,而将小型的、频繁访问的变量保留在内部SRAM。