一、FSMC 简介与工作原理
FSMC(Flexible Static Memory Controller)是 STM32 微控制器中用于与外部静态存储器(如 SRAM、PSRAM、NOR Flash、LCD 等)进行通信的一个外设模块。
1、支持的设备类型:
-
SRAM / PSRAM
-
NOR Flash
-
NAND Flash
-
PC 卡
-
扩展 I/O 接口设备(如 TFT-LCD 控制器)
2、 工作原理:
FSMC 通过地址线、数据线和控制信号(如 nWE、nOE、nCS、ALE、CLE 等)对外扩展静态设备。其本质是将外设的访问映射到 MCU 的外部存储地址空间,实现类似访问内存的方式来读写外设。
二、典型应用场景
应用方向 | 说明 |
---|---|
外接SRAM | 作为内存扩展 |
外接LCD模块 | 接带并口的TFT-LCD模块(例如8080协议) |
外接Flash | 用于代码/数据的扩展存储 |
FPGA通信 | FSMC也常用于与FPGA的数据交互 |
三、开发步骤(基于 HAL 库)
1、引脚配置
FSMC 依赖 GPIO,需要设置对应的引脚模式为 AF(Alternate Function)。常见管脚如:
-
数据线
D0~D15
-
地址线
A0~Axx
-
控制线
NE1~NE4
、NOE
、NWE
、NADV
2、时钟使能
cpp
__HAL_RCC_FSMC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
// 其他GPIO时钟
3、 FSMC 初始化结构体配置
使用 FMC_NORSRAM_TimingTypeDef
和 SRAM_HandleTypeDef
。
cpp
SRAM_HandleTypeDef hsram;
FMC_NORSRAM_TimingTypeDef Timing = {0};
hsram.Instance = FSMC_NORSRAM_DEVICE;
hsram.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
hsram.Init.NSBank = FSMC_NORSRAM_BANK1;
hsram.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
Timing.AddressSetupTime = 2;
Timing.AddressHoldTime = 1;
Timing.DataSetupTime = 5;
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 2;
Timing.DataLatency = 2;
Timing.AccessMode = FSMC_ACCESS_MODE_A;
if (HAL_SRAM_Init(&hsram, &Timing, NULL) != HAL_OK)
{
Error_Handler();
}
4、FSMC 地址映射
根据 FSMC 的 BANK地址映射,其起始地址为固定值,例如:
BANK | 地址起始 | 控制引脚 |
---|---|---|
BANK1 | 0x60000000 | NE1 |
BANK2 | 0x64000000 | NE2 |
BANK3 | 0x68000000 | NE3 |
BANK4 | 0x6C000000 | NE4 |
你可以通过访问 *(volatile uint16_t*)0x60000000
来读写连接的外设。
5、代码示例(操作LCD模块)
cpp
#define LCD_BASE_ADDR ((uint32_t)(0x60000000)) // NE1 映射
static void LCD_WriteReg(uint16_t reg)
{
*(volatile uint16_t*)(LCD_BASE_ADDR) = reg;
}
static void LCD_WriteData(uint16_t data)
{
*(volatile uint16_t*)(LCD_BASE_ADDR | (1 << 16)) = data; // A16=1 表示数据
}
void LCD_Init(void)
{
LCD_WriteReg(0x0001); // 假设这是LCD的初始化寄存器
LCD_WriteData(0x1234);
}
四、示例
下面是一个 基于 STM32 + FSMC + DMA 访问外部 SRAM(以 IS61LV25616 为例) 的完整示例。此例程使用 HAL 库实现了对外部 SRAM 的初始化、DMA 读写操作。
1、示例目标
-
通过 FSMC 接口扩展外部 SRAM
-
使用 DMA 进行高效数据传输(写入和读取)
-
SRAM 芯片示例:
IS61LV25616
,256K x 16bit = 512KB
2、硬件前提(假设如下):
信号 | 接口说明 |
---|---|
数据线 | PD14PD15 + PE7PE15(D0~D15) |
地址线 | 地址线 A0~A17 |
控制信号 | NOE , NWE , NE1 |
总线宽度 | 16-bit |
SRAM映射地址 | 0x60000000(BANK1) |
3、工程结构
-
main.c
: 主函数 -
sram.c/h
: FSMC+DMA 初始化 & 操作函数 -
使用 STM32CubeMX 自动生成 HAL 框架,添加 SRAM 相关内容
Step 1: 配置 STM32CubeMX
-
开启 FSMC 外设(有些型号叫 FMC)
-
配置为:
-
SRAM
-
Bank1
-
数据总线宽度:16bit
-
地址/数据复用:关闭
-
写操作:启用
-
-
开启 DMA 通道(如 DMA2_Stream0,MemoryToMemory,优先级 High)
-
启用对应的 GPIO(PD, PE, PF)
生成代码后在 sram.c/h
中编写以下逻辑。
cpp
#ifndef __SRAM_H
#define __SRAM_H
#include "stm32f4xx_hal.h"
#define SRAM_BANK_ADDR ((uint32_t)0x60000000) // Bank1 -> NE1
#define SRAM_SIZE (512 * 1024) // 512KB SRAM
extern SRAM_HandleTypeDef hsram1;
void SRAM_Init(void);
HAL_StatusTypeDef SRAM_DMA_Write(uint32_t offset, uint8_t *src, uint32_t size);
HAL_StatusTypeDef SRAM_DMA_Read(uint32_t offset, uint8_t *dst, uint32_t size);
#endif
sram.c代码
cpp
#include "sram.h"
SRAM_HandleTypeDef hsram1;
DMA_HandleTypeDef hdma_memtomem;
// 初始化 FSMC SRAM + DMA
void SRAM_Init(void)
{
FMC_NORSRAM_TimingTypeDef Timing = {0};
/*** FSMC Configuration ***/
hsram1.Instance = FMC_NORSRAM_DEVICE;
hsram1.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
hsram1.Init.NSBank = FMC_NORSRAM_BANK1;
hsram1.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
hsram1.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram1.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
hsram1.Init.WrapMode = FMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
hsram1.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
Timing.AddressSetupTime = 2;
Timing.AddressHoldTime = 1;
Timing.DataSetupTime = 5;
Timing.BusTurnAroundDuration = 1;
Timing.CLKDivision = 2;
Timing.DataLatency = 2;
Timing.AccessMode = FMC_ACCESS_MODE_A;
if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
{
Error_Handler();
}
/*** DMA Configuration ***/
__HAL_RCC_DMA2_CLK_ENABLE();
hdma_memtomem.Instance = DMA2_Stream0;
hdma_memtomem.Init.Channel = DMA_CHANNEL_0;
hdma_memtomem.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem.Init.Mode = DMA_NORMAL;
hdma_memtomem.Init.Priority = DMA_PRIORITY_HIGH;
hdma_memtomem.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_memtomem) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(&hsram1, hdma, hdma_memtomem);
}
// 使用 DMA 写入外部 SRAM
HAL_StatusTypeDef SRAM_DMA_Write(uint32_t offset, uint8_t *src, uint32_t size)
{
if ((offset + size) > SRAM_SIZE) return HAL_ERROR;
uint8_t *dest = (uint8_t *)(SRAM_BANK_ADDR + offset);
return HAL_DMA_Start(&hdma_memtomem, (uint32_t)src, (uint32_t)dest, size);
}
// 使用 DMA 从外部 SRAM 读取
HAL_StatusTypeDef SRAM_DMA_Read(uint32_t offset, uint8_t *dst, uint32_t size)
{
if ((offset + size) > SRAM_SIZE) return HAL_ERROR;
uint8_t *src = (uint8_t *)(SRAM_BANK_ADDR + offset);
return HAL_DMA_Start(&hdma_memtomem, (uint32_t)src, (uint32_t)dst, size);
}
main.c代码
cpp
#include "main.h"
#include "sram.h"
#include <string.h>
#include <stdio.h>
uint8_t txBuffer[64];
uint8_t rxBuffer[64];
int main(void)
{
HAL_Init();
SystemClock_Config();
SRAM_Init();
// 初始化数据
for (int i = 0; i < 64; i++) {
txBuffer[i] = i;
}
// 写入SRAM
if (SRAM_DMA_Write(0x0000, txBuffer, sizeof(txBuffer)) != HAL_OK) {
printf("SRAM DMA Write Failed!\n");
}
HAL_Delay(10);
// 读取SRAM
if (SRAM_DMA_Read(0x0000, rxBuffer, sizeof(rxBuffer)) != HAL_OK) {
printf("SRAM DMA Read Failed!\n");
}
HAL_Delay(10);
// 校验
if (memcmp(txBuffer, rxBuffer, sizeof(txBuffer)) == 0) {
printf("SRAM Read/Write OK.\n");
} else {
printf("SRAM Data Mismatch!\n");
}
while (1);
}
五、开发注意事项
项目 | 内容 |
---|---|
地址线对齐 | LCD常用 A16 作为数据命令选择信号 |
时序调整 | DataSetupTime、AddressSetupTime 需根据实际器件手册配置 |
数据总线宽度 | 若使用 8 位数据总线,需调整 FSMC_NORSRAM_MEM_BUS_WIDTH_8 |
DMA 支持 | FSMC 支持 DMA 访问,加快数据刷新效率 |
多设备冲突 | 使用多个BANK时注意各设备地址映射不可重叠 |
六、调试与排查问题技巧
1. 总线不响应
-
检查 GPIO 是否配置为复用模式
-
检查 FSMC 时钟是否开启
2. 数据错误或乱码
-
检查数据总线宽度是否正确(8/16 位)
-
检查读写时序设置(尤其是
DataSetupTime
) -
检查地址线是否配置正确(A16 通常用于寄存器/数据切换)
3. 无法访问外设
-
使用示波器抓取
NE
,NOE
,NWE
,是否跳变 -
使用
volatile
强制访问,避免优化器优化掉访问行为