1. 项目概述
FSMC(Flexible Static Memory Controller,灵活静态存储器控制器)是STM32系列MCU中一个强大的外部总线接口模块,在部分型号中也称为EXMC(External Memory Controller)。这个项目探讨的是如何利用FSMC接口实现MCU与FPGA之间的高速数据交互,这在工业控制、图像处理、通信协议转换等场景中具有广泛的应用价值。
我在多个嵌入式项目中都使用过FSMC接口,特别是在需要与FPGA协同工作的场合。相比SPI、I2C等传统接口,FSMC提供了更高的带宽(可达16位或32位并行传输)和更灵活的控制时序,非常适合大数据量传输或实时性要求高的场景。比如在一个工业相机项目中,我们就是用FSMC将STM32采集的图像数据实时传输给FPGA做预处理,传输速率比SPI快了近20倍。
2. 硬件设计要点
2.1 接口信号定义
FSMC接口包含三类关键信号:
- 地址线(A[25:0]):用于寻址外部设备
- 数据线(D[15:0]或D[31:0]):双向数据传输
- 控制信号:
- NEx(片选,x=0..4)
- NOE(输出使能)
- NWE(写使能)
- NWAIT(等待信号)
在与FPGA对接时,我通常会这样分配:
- 使用Bank1的NE1片选(对应FSMC_NE1引脚)
- 16位数据总线(D0-D15)
- 地址线根据需求连接,通常A0-A15足够
- 必须加上10K上拉电阻防止浮空
重要提示:FSMC的时序参数对信号完整性非常敏感,建议使用阻抗匹配的PCB走线,长度尽量等长,特别是高速应用时。
2.2 FPGA端接口设计
FPGA需要实现一个兼容FSMC时序的从设备接口。以Verilog为例,核心部分包括:
verilog复制module fsmc_slave(
input NE1, // 片选
input NOE, // 读使能
input NWE, // 写使能
input [15:0] A, // 地址线
inout [15:0] D // 数据线
);
reg [15:0] mem[0:255]; // 256x16位存储空间
reg [15:0] data_out;
// 三态控制
assign D = (!NE1 && !NOE) ? data_out : 16'hZZZZ;
// 写操作
always @(negedge NWE or negedge NE1) begin
if(!NE1 && !NWE)
mem[A] <= D;
end
// 读操作
always @(negedge NOE or negedge NE1) begin
if(!NE1 && !NOE)
data_out <= mem[A];
end
endmodule
这个基础实现已经可以支持MCU通过FSMC读写FPGA内部的存储空间。在实际项目中,我会根据需求扩展:
- 添加状态寄存器(如中断标志位)
- 实现FIFO接口用于流式数据传输
- 加入DMA支持
3. STM32软件配置
3.1 CubeMX设置
使用STM32CubeMX配置FSMC的步骤:
- 在"Pinout & Configuration"选项卡中启用FSMC控制器
- 选择"NOR Flash/PSRAM"模式(虽然我们接的是FPGA)
- 配置存储器类型为"SRAM"
- 设置数据宽度(16位或8位)
- 配置时序参数:
- AddressSetupTime:通常2-3个HCLK周期
- DataSetupTime:根据FPGA响应速度设置
- BusTurnAroundTime:防止总线冲突
3.2 软件访问示例
配置完成后,可以直接通过指针访问FPGA内存:
c复制#define FPGA_BASE_ADDR 0x60000000
#define FPGA_REG_CTRL *(volatile uint16_t *)(FPGA_BASE_ADDR + 0x00)
#define FPGA_REG_DATA *(volatile uint16_t *)(FPGA_BASE_ADDR + 0x02)
void fpga_write_data(uint16_t value) {
FPGA_REG_CTRL = 0x01; // 写入控制寄存器
FPGA_REG_DATA = value; // 写入数据寄存器
}
uint16_t fpga_read_data(void) {
return FPGA_REG_DATA;
}
在实际项目中,我通常会封装更完善的API,包括:
- 突发传输函数(利用FSMC的连续访问特性)
- 错误检测和重试机制
- 中断驱动的异步接口
4. 时序调试技巧
FSMC与FPGA的协同工作,时序匹配是关键。以下是我总结的调试方法:
4.1 逻辑分析仪抓取
使用Saleae Logic或DSView等工具捕获关键信号:
- 片选(NE)有效期间的读写时序
- 数据建立和保持时间是否满足FPGA要求
- 信号完整性检查(过冲、振铃等)
4.2 时序参数调整
如果发现通信不稳定,可以逐步调整:
- 增加地址建立时间(AddressSetupTime)
- 延长数据建立时间(DataSetupTime)
- 启用扩展模式(ExtendedMode)
- 降低FSMC时钟频率(通过AHB分频)
经验之谈:FPGA内部的输入寄存器(如IOB寄存器)能显著提高时序余量,建议在FPGA代码中为所有FSMC输入信号添加寄存器级。
5. 性能优化方案
5.1 DMA传输
对于大数据量传输,启用DMA可以释放CPU资源:
c复制// 配置DMA从内存到FSMC
hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
HAL_DMA_Init(&hdma_memtomem_dma2_stream0);
// 启动传输
HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)src, (uint32_t)FPGA_BASE_ADDR, length);
5.2 双缓冲机制
在FPGA端实现双缓冲可以进一步提高吞吐量:
- MCU通过FSMC写入Buffer A
- 写完后触发FPGA处理Buffer A
- 同时MCU开始写Buffer B
- 通过中断或轮询状态寄存器实现同步
6. 常见问题排查
6.1 数据读写异常
现象:读取的数据与写入不一致
排查步骤:
- 检查FSMC和FPGA的数据宽度设置是否匹配(8/16位)
- 确认地址线连接正确(特别注意A0是否接对)
- 测量电源电压是否稳定(特别是FPGA的IO电压)
- 检查PCB走线是否有短路或虚焊
6.2 通信不稳定
现象:偶尔出现数据错误
解决方案:
- 在FSMC数据线上添加22-33Ω串联电阻
- 在FPGA代码中对输入信号进行同步处理(打两拍)
- 降低FSMC时钟频率测试
- 检查地平面是否完整
7. 进阶应用实例
7.1 图像数据传输
在一个机器视觉项目中,我们使用FSMC将OV7670摄像头的数据通过STM32中转传输到FPGA:
- STM32通过DCMI接口接收图像数据
- 存入内部SRAM缓冲区
- 通过FSMC DMA传输到FPGA
- FPGA实现Sobel边缘检测算法
这种架构实现了720p@30fps的实时处理,FSMC的带宽完全满足需求。
7.2 多设备共享总线
FSMC支持分时复用多个外部设备。例如同时连接:
- FPGA(使用NE1)
- SRAM芯片(使用NE2)
- LCD屏(使用NE3)
关键是要合理规划地址空间和时序参数,我通常会为每个设备创建独立的初始化函数。
8. 替代方案比较
当FSMC资源紧张时,可以考虑:
- SPI接口:
- 优点:引脚少
- 缺点:速度慢(通常<50Mbps)
- 并行GPIO模拟:
- 优点:灵活
- 缺点:软件开销大
- USB/FMC:
- 优点:带宽高
- 缺点:实现复杂
经过实测,在16位宽度、60MHz HCLK下,FSMC的理论带宽可达120MB/s,实际持续传输约80MB/s,远高于SPI接口。
