FSMC协议详解
FSMC(Flexible Static Memory Controller,灵活静态存储控制器)是STM32微控制器中用于连接外部存储设备(如SRAM、NOR Flash、LCD等)的重要外设。它通过将外部存储设备映射到处理器的内存地址空间,简化了访问外部存储器的操作。以下是FSMC的详细解析:
一、FSMC核心功能
-
支持多种存储器类型:
- SRAM/PSRAM:静态随机存储器。
- NOR Flash/ROM:支持异步读写的Flash或ROM。
- LCD接口:通过模拟8080接口驱动TFT-LCD或OLED屏。
- NAND Flash(部分型号支持):需要额外配置ECC。
-
多存储区域划分:
- FSMC将地址空间分为多个Bank(如Bank1~Bank4),每个Bank可独立配置为不同存储类型。
- 例如:
- Bank1:通常用于NOR/SRAM,分为4个子区域(NE1~NE4)。
- Bank2/3/4:部分型号支持NAND或PC卡。
-
灵活时序配置:
- 可设置地址建立时间、数据保持时间、总线恢复时间等参数,适配不同速度的存储设备。
二、FSMC工作原理
-
地址映射:
- 外部存储器的物理地址被映射到STM32的固定内存地址(如Bank1起始地址为
0x60000000
)。 - 通过指针直接访问这些地址,即可操作外部设备。
- 外部存储器的物理地址被映射到STM32的固定内存地址(如Bank1起始地址为
-
主要控制信号:
- 片选信号(NE):选择对应的存储区域(如NE1对应Bank1的子区域1)。
- 写使能(NWE):低电平时写入数据。
- 读使能(NOE):低电平时读取数据。
- 地址线(A0~A25):用于寻址。
- 数据线(D0~D15/D31):传输数据(宽度可配置为8/16/32位)。
-
数据宽度与地址对齐:
- 若外部设备为16位(如多数SRAM和LCD),需将STM32的地址线右移1位(
Address << 1
),因为每个16位数据占两个字节地址。
- 若外部设备为16位(如多数SRAM和LCD),需将STM32的地址线右移1位(
三、FSMC时序讲解
-
FSMC读操作时序图
-
FSMC写操作时序图
四、FSMC在MCU和FPGA系统中的应用
FSMC通讯在MCU+FPGA系统中,主要负责芯片间的通讯工作,主要流程就是MCU接收到上位机数据,然后解析数据包之后,对FPGA的某个子模块的某个寄存器进行配置或者读取操作,如果是写操作不需要返回数据,如果是读操作,FPGA会通过FSMC返回数据给MCU,MCU接收到返回数据后将相关信息打包发给上位机;
五、Verilog代码示例
//-----------------------------------------------------------------------------------//-----------------------------------------------------------------------------------// stm32程序2字节对齐(字对齐),fsmc接口每次读写2两字节;//-----------------------------------------------------------------------------------//-----------------------------------------------------------------------------------module fsmc_test ( input wire clk ,//系统时钟 input wire rst ,//复位信号,高电平有效 //fsmc input wire fsmc_ren ,//FSMC_NOE input wire fsmc_wen ,//FSMC_NWE input wire [ 15: 0] fsmc_add ,//double bytes address inout wire [ 15: 0] fsmc_data ,//FSMC_D input wire fsmc_csn ,//FSMC_NEx output reg fsmc_waitn ,//FSMC_NWAIT output reg [ 15: 0] fsmc_fpga_add ,//操作的寄存器地址 output reg [ 15: 0] fsmc_fpga_wen ,//对应外部16路模块写使能 output reg [ 15: 0] fsmc_fpga_ren ,//对应外部16路模块读使能 input wire [ 15: 0] fsmc_fpga_rdata ,//读使能对应模块返回数据 output reg [ 15: 0] fsmc_fpga_wdata //写对应地址数据); reg csn_d1 ; reg csn_d2 ; reg csn_d3 ; reg csn_d4 ; reg csn_d5 ; reg csn_d6 ; reg csn_d7 ; reg csn_d8 ; reg csn_d9 ; reg csn_d10 ; reg csn_d11 ; reg [ 15: 0] fsmc_rdata ; reg fsmc_ren_d1 ; reg fsmc_ren_d2 ; //对CS信号进行同时钟域处理,同时进行延时打拍,为后面操作提供参考时间标准 always @(posedge clk or posedge rst) begin if(rst) begin csn_d1 <= 1\'b1; csn_d2 <= 1\'b1; csn_d3 <= 1\'b1; csn_d4 <= 1\'b1; csn_d5 <= 1\'b1; csn_d6 <= 1\'b1; csn_d7 <= 1\'b1; csn_d8 <= 1\'b1; csn_d9 <= 1\'b1; csn_d10 <= 1\'b1; csn_d11 <= 1\'b1; end else begin csn_d1 <= fsmc_csn; csn_d2 <= csn_d1; csn_d3 <= csn_d2; csn_d4 <= csn_d3; csn_d5 <= csn_d4; csn_d6 <= csn_d5; csn_d7 <= csn_d6; csn_d8 <= csn_d7; csn_d9 <= csn_d8; csn_d10 <= csn_d9; csn_d11 <= csn_d10; end end //地址同时钟域处理 always @(posedge clk or posedge rst) begin if(rst) fsmc_fpga_add <=\'b0; else if(~csn_d2 && csn_d3) fsmc_fpga_add <= fsmc_add; else fsmc_fpga_add <= fsmc_fpga_add; end //读使能同时钟域处理 always @(posedge clk or posedge rst) begin if(rst) begin fsmc_ren_d1 <= 1\'b0; fsmc_ren_d2 <= 1\'b0; end else begin fsmc_ren_d1 <= fsmc_ren; fsmc_ren_d2 <= fsmc_ren_d1; end end //wait信号处理 always @(posedge clk or posedge rst) begin if(rst) fsmc_waitn <= 1\'b1; else if(fsmc_waitn == 1\'b1) if(~csn_d1 && csn_d2) fsmc_waitn <= 1\'b0; else fsmc_waitn <= 1\'b1; else if(~csn_d9 && csn_d10) fsmc_waitn <= 1\'b1; else fsmc_waitn <= 1\'b0; end //根据基地址,将读写使能分配到对应的外部子模块 genvar i; generate for (i = 0; i < 16; i = i + 1) begin : gen_fsmc_ctr_block always @(posedge clk or posedge rst) begin if(rst) fsmc_fpga_wen[i] <= 1\'b0; else if(~csn_d4 && csn_d5 && fsmc_ren_d2 && (fsmc_fpga_add[14:11] == i)) fsmc_fpga_wen[i] <= 1\'b1; else fsmc_fpga_wen[i] <= 1\'b0; end always @(posedge clk or posedge rst) begin if(rst) fsmc_fpga_ren[i] <= 1\'b0; else if(~csn_d4 && csn_d5 && ~fsmc_ren_d2 && (fsmc_fpga_add[14:11] == i)) fsmc_fpga_ren[i] <= 1\'b1; else fsmc_fpga_ren[i] <= 1\'b0; end end endgenerate //数据同时钟域处理 always @(posedge clk or posedge rst) begin if(rst) fsmc_fpga_wdata <= \'b0; else fsmc_fpga_wdata <= fsmc_data; end //读取操作,返回读取值 always @(posedge clk or posedge rst) begin if(rst) fsmc_rdata <= 16\'d0; else if(~csn_d7 && csn_d8) fsmc_rdata <= fsmc_fpga_rdata; else fsmc_rdata <= fsmc_rdata; end assign fsmc_data = (fsmc_ren_d1) ? {16{1\'bz}} : fsmc_rdata;endmodule