首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

静态存储控制器FSMC

MCU自带的FLASH和SRAM资源是十分有限的,比如说探索者开发板选用的STM32F407,最多就只有1M的Flash和192K的SRAM, 相比于PC机的存储空间而言要小的可怜。当然对于嵌入式应用来说这点存储空间一般也就够用了,但避免不了一些大量消耗内存的应用, 比如说图像处理。对于这类对内存要求较高的应用,我们往往需要扩展一个FLASH或者SRAM。STM32提供的FSMC就是用来完成这项功能的。

FSMC全称Flexible static memory controller,官方手册上的翻译是灵活的静态存储控制器。 它能够接同步的(synchronous)和异步的(asynchronous)存储器以及16位的PC卡。一些常见的存储器件,如SRAM、NAND Flash、Nor Flash等都可以接上去。 完成的工作就是,把AHB总线上的数据通信事物转换为对应外设的通信协议,控制外设的访问时序。以至于我们可以直接在程序中寻址访问。

在本文中,我们以驱动探索者开发板上的SRAM芯片IS62WV51216为例介绍FSMC。实际上FSMC除了用于扩展存储外,还可以拿来驱动液晶屏, 我们会在以后的文章中予以介绍。

1. FSMC简介

图1(a)是FSMC的结构框图。它由AHB总线接口(包括FSMC的配置寄存器)、NOR闪存/SRAM控制器、NAND闪存/PC卡控制器、外设接口四个部分构成。

AHB总线接口是CPU、DMA等AHB总线主设备访问FSMC的通道,它负责把AHB总线事务转换成为外设通信的协议。 AHB总线事务的请求可以是8、16或者32位的,但外设器件的数据线位宽是恒定的。如果两者宽度相同就不存在什么问题, 如果总线事务的位宽大于外设的位宽,那么总线接口将把总线事务拆分为多个连续的8位或16位形式访问外设。 我们应当尽量避免总线事务宽度小于外设宽度的情况出现,因为这将可能导致数据的不一致,具体与外设类型有关系。

配置寄存器则描述了扩展外设的具体形式、通信协议和接口形式。用于总线接口将AHB总线事务转换位外设通信协议, 驱动NOR闪存/SRAM控制器和NAND闪存/PC卡控制器,进而控制外设。

NOR闪存/PSRAM控制器用于生成适当的时序,以驱动8位、16位、32位的异步SRAM和ROM、异步或者突发模式的PSRAM和NOR闪存。 我们通过配置寄存器描述外设的特征和时序后,控制器就可以为我们生成对应的驱动时序。 类似的,NAND/PC卡控制器用于驱动8位或者16位的NAND闪存以及16位的PC卡兼容设备。

各种不同的外设都共用一组地址数据总线、读写使能和输入等待控制线,如下图1(a)中的Shared signals标记的引脚。 它们基本上覆盖了各种扩展存储的读写事务所需的所有信息,通过地址总线寻址,通过读写使能和等待控制线控制数据线的数据传输方向, 并标识者数据的有效时刻以同步数据。除此之外,STM32位每个外设都提供了一个专门的片选信号,比如NOR/PSRAM中的NE信号、NAND中的NCE信号。 在每次访问外设时都会切换片选信号,实现对总线的分时复用。至于其它的外设接口信号,与实际的外设驱动方式有很大的关联, 暂不予介绍,详细可以参考手册中相关说明。

图1(a) FSMC结构框图 图1(b) FSMC存储区域划分

FSMC将外设映射在0x60000000——0x9FFFFFFF这一段存储空间中,它的存在使得我们可以直接以读写内存的形式访问存储设备。 图1(b)描述的是FSMC的存储区域划分情况。从图中可以看出STM32将FSMC划分成了4个bank,每个bank对应256M字节的空间。 其中Bank1用于NOR/PSRAM,可以链接多达4个NOR闪存或者PSRAM存储器。该Bank又被细分为4个64M字节的区域,每个区域都有唯一的一个片选信号NE[4:1], 和专用的配置寄存器。Bank2和Bank3可以分别接入两个NAND闪存,Bank4则用于扩展PC卡兼容设备。 在STM32中为每一个Bank都提供了一组配置寄存器,我们可以将它们看作是相互独立的外设。

2. NOR/PSRAM内存控制器

FSMC内部有两个独立的控制器分别用于驱动NOR/PSRAM内存和NAND/PC卡。由于本文所涉及的例子是驱动一个SRAM, 所以这里专门讲述一下NOR/PSRAM控制器。NAND/PC卡控制器将在以后的文章中予以介绍。

2.1 NOR/PSRAM内存控制器支持设备

NOR/PSRAM内存控制器支持各种同步和异步的内存。 所谓同步内存就是在读写内存的时候需要一个同步时钟来指导数据的发送和接收, 与我们在串口通信(1)一文中提到的同步/异步通信是一个道理。 对于同步内存,FSMC只会在读写操作的时候产生驱动时钟,而且其频率是系统总线时钟HCLK的分频。 下表1列出了NOR/PSRAM内存控制器支持的器件种类,其中不支持的器件类型用粉底色标记出来了:

表 1 NOR/PSRAM内存控制器支持的器件类型及传输方式

设备 模式 读/写 AHB数据宽度 内存数据宽度 是否支持 注释
NOR Flash(muxed I/Os and nonmuxed I/Os) 异步 R 8 16 支持
W 8 16 不支持
异步 R 16 16 支持
W 16 16 支持
异步 R 32 16 支持 拆分为两个FSMC访问
W 32 16 支持 拆分为两个FSMC访问
异步
分页
R - 16 不支持 不支持这种模式
同步 R 8 16 不支持
R 16 16 支持
R 32 16 支持
PSRAM(multiplexed I/Os and nonmultiplexed I/Os) 异步 R 8 16 支持
W 8 16 支持 使用NBL[1:0]
异步 R 16 16 支持
W 16 16 支持
异步 R 32 16 支持 拆分为两个FSMC访问
W 32 16 支持 拆分为两个FSMC访问
异步
分页
R - 16 不支持 不支持这种模式
同步 R 8 16 不支持
R 16 16 支持
R 32 16 支持
W 8 16 支持 使用NBL[1:0]
W 16/32 16 支持
SRAM和ROM 异步 R 8/16 16 支持
W 8/16 16 支持 使用NBL[1:0]
异步 R 32 16 支持 拆分为两个FSMC访问
W 32 16 支持 拆分为两个FSMC访问
使用NBL[1:0]

2.2 NOR/PSRAM内存控制器外部信号接口

下表2总结了NOR Flash, SRAM和PSRAM的外部信号接口,其中信号名称的前缀'N'表示该信号为低电平有效。 根据地址总线是否与数据总线复用,接口形式分为了multiplexed和nonmultiplexed两类。而PSRAM/SRAM由于支持单个字节的访问, 所以相比于NOR Flash存储器而言,多了NBL信号用于控制访问高位字节或低位字节。

表 2 NOR/PSRAM内存控制器外部信号

FSMC信号名称 I/O Nonmultiplexed Multiplexed
NOR Flash PSRAM/SRAM NOR Flash PSRAM/SRAM
CLK O 时钟信号(用于同步存储器)
A[25:0] O 地址总线 ——
A[25:16] O —— 地址总线
D[15:0] I/O 双向通信的数据总线 ——
AD[15:0] I/O —— 16位地址/数据混用总线
NE[x] O 片选信号, x=1..4,每个片选信号控制64M字节大小的内存空间
NOE O 输出使能(或者说是读使能)
NWE O 写使能
NL(NADV) O 地址锁存(Latch enable),或者说是地址有效信号(address valid)
NWAIT I 外设等待到FSMC的输入信号(peripheral wait input signal to the FSMC)
NBL[1] O —— 高位字节使能(Upper byte enable) —— 高位字节使能(Upper byte enable)
NBL[0] O —— 低位字节使能(Lowed byte enable) —— 低位字节使能(Lowed byte enable)

2.3 NOR/PSRAM内存控制器信号逻辑

根据存储器的种类不同,控制器还有同步/异步,Multiplexed/Nonmultiplexed等多种的工作模式。结合本文的例程, 这里只讲述Nonmultiplexed的异步工作方式。 其余模式可以参见参考手册

在FSMC中,控制器的所有输出信号都是在内部总线时钟HCLK的上升沿改变。 对于同步模式下的设备无论分频系数是多少,NOEL、NWEL、NEL、NADVL、NADVH、NBLL、Address valid这些输出信号都是在FSMC_CLK时钟的下降沿产生。 而NOEH、NWEH、NEH、NEH、NOEH、NBLH、Address invalid输出信号则在FSMC_CLK的上升沿产生。 对于异步的存储器,FSMC通过内部时钟信号HCLK来驱动各个接口信号,但是HCLK是不会提供给存储器的。 FSMC通过信号NOE控制存储器保持数据信号,并在NOE信号释放之前从数据总线上采样数据。它还通过NWE通知存储器从数据总线上获取数据。

一般情况下,控制器有Mode1和Mode2两种工作模式,其中Mode1是控制SRAM/PSRAM的默认模式,Mode2是控制NOR Flash的默认模式。 下图2(a)(b)分别描述了Mode1的读操作和写操作的时序逻辑。

图2(a) Mode1读操作时序 图2(b) Mode1写操作时序

开始读操作时,片选信号NEx、读使能信号NOE在一个HCLK的上升沿置低,同时FSMC控制地址总线和高低位控制信号寻址。 经过ADDSET个HCLK周期,保证地址信号已经完全建立后,FSMC从数据总线上采样数据,此时数据总线由存储器驱动,保持DATAST个HCLK周期。 整个过程中,写使能信号NWE始终保持高电平。

在写操作过程中,FSMC则始终保持NOE为高电平,并在开始的HCLK的上升沿将NEx拉低。NWE并不是在一开始的时候就被拉低了, 而是在经过了ADDSET个HCLK周期,地址信号完全建立后才拉低的。与此同时,FSMC驱动数据总线写数据到存储器中,这将持续DATAST+1个HCLK周期。 在DATAST个HCLK周期的时候,NWE信号就已经被置高了,此后仍然保持一个HCLK周期才释放片选信号和地址总线。

下图3(a)(b)分别描述了Mode2的读写操作时序逻辑。其时序逻辑与Mode1大体相同,只是这里没有高低字节控制信号NBL, 多了地址锁存信号NADV信号用来标记地址是否可用。

图3(a) Mode2读操作时序 图3(b) Mode2写操作时序

此外,控制器还有一种扩展模式,通过寄存器FSMC_BCRx的EXTMOD位开启。在开启扩展模式的情况下,控制器有A、B、C、D四种工作模式。 我们可以混用这四种模式,比如说可以在读操作时使用模式A,在写操作时使用模式B。

下图4中的八张图分别描述了Mode A/B/C/D的读写时序逻辑。其中Mode A用于驱动SRAM/PSRAM,其写时序与与Mode1一致, 读时序相比于Mode1而言,只是NOE信号在地址建立后反转,而不是一开始就拉低。 Mode B/C/D都是用于驱动NOR闪存的,与Mode2的时序逻辑基本一致,只是在NWE和NOE信号上略有差异。

图4(a) ModeA读操作时序 图4(b) ModeB读操作时序 图4(c) ModeC读操作时序 图4(d) ModeD读操作时序
图4(e) ModeA写操作时序 图4(f) ModeB写操作时序 图4(g) ModeC写操作时序 图4(h) ModeD写操作时序

3. SRAM IS62WV51216简介

在探索者开发板上,集成了一个IS62WV51216的CMOS静态内存芯片。 它是一颗16位宽8Mbit的SRAM,三态输出,支持高/低字节控制。 下图2描述了其内部结构和引脚定义,从图中我们可以看到,它没有时钟信号,因而可以断定这是一种异步的SRAM。

图5 IS62WV51216内部结构框图和引脚定义
下表则描述了SRAM的片选规则。它有两个片选信号CS1和CS2, 只有在两者分别为低电平和高电平时才有片选意义。此外读写使能信号WEOE控制了数据总线的方向, WE为低电平时, 无论OE的电平逻辑是什么,都是控制MCU向SRAM写数据。 WE为高电平OE为低电平时, 控制MCU从SRAM中读数据。若两者都为高电平则与没有片选的效果是一样的。

图6 IS62WV51216的片选规则

此外,该SRAM虽然是16位的线宽,但它可以受控制分别输出/写入高8位或者低8位的数据。 UBLB分别控制了高低字节的访问。

如右图所示,是探索者开发板上集成SRAM的原理图。正点原子他们选择的是一块TSOP44封装的芯片。 其中,UB,LB,OE,WE,CE都是低电平有效的信号,一般情况下都会有一个上划线标记,这里在画原理图库的时候偷懒给省了。 虽然做出的电路没有什么问题,但是分析的时候可能会搞混,注意一下就是了。

原理图中SRAM的A0到A18分别与STM32F407的FSMC_A0~FSMC_A18相连,但是他们的连接是乱序的。这对于正常使用SRAM而言没有什么影响, 因为每个地址唯一的标识了一个16位的存储空间。这样一来对于画电路的人来说就有了比较大的自由,可以根据实际需要调整线序。

这里的数据线的线序则是一一对应的,理论上它们的线序也是可以乱序的,因为读写数据的是一对互逆的操作, 它只决定了物理上数据在SRAM中如何存储,而对于逻辑没有任何影响。我们只需要保证数据线和地址线不要搞混了就可以。

在原理图中我们只看到了一个与片选相关的引脚CE接到了FSMC_NE3上。经过仔细查看SRAM的数据手册才发现, TSOP44封装的芯片只有一个片选信号CS1,也就是这里CE标记的引脚它接到了FSMC_NE3。 这意味着SRAM挂在了bank1的第三个区域,起始地址为0x68000000。

SRAM的读写使能信号OEWE, 则直接与FSMC的NOE和NWE信号连接。 高低字节控制信号OEWE, 则接到FSMC的NOR/SRAM控制器的NBL[1:0]信号上。

4. SRAM初始化过程

与其它片上外设类似,我们想要通过FSMC来驱动一个SRAM,首先需要完成对FSMC的各种配置。包括FSMC的时钟和相关引脚的配置、 挂接SRAM的FSMC BANK以及对应区域的配置。最后使能片上外设FSMC。

4.1 FSMC的时钟和相关引脚的配置

通过分析原理图,我们知道驱动一个IS62WV51216需要用到至少40个引脚,其中地址总线需要19个,数据总线需要16个,控制信号需要5个。 涉及到D、E、F、G四个GPIO端口。我们需要开启这四个GPIO端口的驱动时钟,以及FSMC的驱动时钟,如下:

    void fsmc_sram_init_gpio(void) {
        RCC->AHB1ENR.bits.gpiod = 1;
        RCC->AHB1ENR.bits.gpioe = 1;
        RCC->AHB1ENR.bits.gpiof = 1;
        RCC->AHB1ENR.bits.gpiog = 1;
        
        RCC->AHB3ENR.bits.fsmc = 1;

此外,还需要配置相关引脚的功能映射为FSMC, 通过查询数据手册,知道FSMC对应的功能号为12。 下面示例代码中象征性的列举了两个引脚的功能映射,详细参考源代码。 最后,配置各个引脚工作在推挽复用模式下,并开启了上拉电阻。

        GPIOG->AFR.bits.pin10 = 12; // PG10 -> FSMC_NE3,CE, 片选信号
        // 此处省略各个引脚的功能映射,详细查看源代码
        GPIOD->AFR.bits.pin13 = 12; // PD13 -> FSMC_A18
        
        struct gpio_pin_conf pincof;
        pincof.mode = GPIO_Mode_Af;
        pincof.otype = GPIO_OType_PP;    
        pincof.pull = GPIO_Pull_Up;
        pincof.speed = GPIO_OSpeed_Very_High;
        
        gpio_init(GPIOD, (3<<0)|(3<<4)|(0XFF<<8), &pincof);  // PD0,1,4,5,8~15
        gpio_init(GPIOE, (3<<0)|(0X1FF<<7), &pincof);        // PE0,1,7~15 
        gpio_init(GPIOF, (0X3F<<0)|(0XF<<12), &pincof);      // PF0~5,12~15
        gpio_init(GPIOG, (0X3F<<0) | GPIO_Pin_10, &pincof);  // PG0~5,10
    }

4.2 FSMC的工作模式配置

由于我们的SRAM挂载在Bank1的第三个区域,所以我们这里只配置BCR3、BTR3和BWTR3。

        void fsmc_sram_init(void) {
            fsmc_sram_init_gpio();
            // 寄存器清零
            FSMC_Bank1->bcr3.all = 0;
            FSMC_Bank1->btr3.all = 0;
            FSMC_Bank1E->bwtr3.all = 0;
            // 使用异步模式A(读写共用一个时序寄存器)
            FSMC_Bank1->bcr3.bits.MWID = FSMC_BCR_MWID_16b;
            FSMC_Bank1->bcr3.bits.WREN = 1;
            FSMC_Bank1->btr3.bits.DATAST = 8;
            FSMC_Bank1->btr3.bits.ADDHLD = 0;
            FSMC_Bank1->btr3.bits.ADDSET = 0;
            
            FSMC_Bank1E->bwtr3.all = 0x0FFFFFFF;
            FSMC_Bank1->bcr3.bits.MBKEN = 1;
        }

4.3 访问SRAM

为了验证我们已经成功配置了FSMC并可以驱动SRAM,我们定义一个全局的数组testbuf。这里使用了一个__attribute__的修饰, 用来描述该数组的起始地址是从0x68000000开始的,也就是我们的SRAM的首地址。另外,我们还定义了一个指针指向这个testbuf。

    uint32 testbuf[100] __attribute__((at(0X68000000)));
    uint32 *testptr = testbuf;

在main()函数中,我们首先调用函数fsmc_sram_init()完成初始化,然后使用一个for循环通过testbuf写入一些初值。 接着在另一个for循环中依次检查是否正确写入数据,并通过串口和LED显示出来。

    fsmc_sram_init();

    for (int i = 0; i < 100; i++)
        testbuf[i] = i;

    for (int i = 0; i < 100; i++) {
        if (i != testptr[i]) {
            uart_send_byte(USART1, 'E');
            LED_0 = LED_OFF;
        } else {
            uart_send_byte(USART1, 'Y');
        }
    }

做出这样的修改后,编译烧录到单片机上并复位,如果没有任何意外的话,我们就可以通过串口接收到一串‘Y'。

5. 总结

FSMC是STM32提供的扩展存储的一种方式,它将外设直接映射到了一段地址空间中,我们可以直接通过寻址操作实现对存储器的读写访问。 FSMC将这段地址空间划分为了4个Bank,每个Bank有256M字节的空间。其中Bank1通常用来驱动PSRAM/SRAM或者NOR闪存, 它又被细分为四个区域,并未每个区域提供了一套独立的配置寄存器。Bank2和Bank3用来驱动NAND Flash,Bank4用来驱动PC卡兼容设备。

由于FSMC可以直接访问内存空间,为编写程序提供了很多便利,一些外设往往也抽象成为类似内存的形式来工作。 比如说这里介绍的驱动液晶屏的方法,就是一种模拟内存的工作形式。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1