27. FSMC—扩展外部SRAM

本章参考资料:《STM32F4XX-参考手册》FSMC章节。

关于SRAM存储器,请参考“常用存储器介绍”章节,实验中FLASH芯片的具体参数,请参考其规格书《IS62WV51216》来了解。

27.1. SRAM控制原理

STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但当程序较大,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了。

扩展内存时一般使用SRAM和SDRAM存储器,但STM32F407系列的芯片不支持扩展SDRAM(STM32F429系列支持),它仅支持使用FSMC外设扩展SRAM, 我们以 SRAM为例讲解如何为STM32扩展内存。

给STM32芯片扩展内存与给PC扩展内存的原理是一样的,只是PC上一般以内存条的形式扩展, 内存条实质是由多个内存颗粒(即SRAM芯片)组成的通用标准模块,而STM32直接与SRAM芯片连接。 见图 一种SRAM芯片的内部结构框图 , 这是我们实验板上使用的型号为IS62WV51216的SRAM芯片内部结构框图,以它为模型进行学习。

SRAM芯片外观 一种SRAM芯片的内部结构框图

27.1.1. SRAM信号线

一种SRAM芯片的内部结构框图 中左侧引出的是SRAM芯片的控制引脚, 其说明见表 SRAM控制引脚说明

SRAM控制引脚说明

SRAM的控制比较简单,只要控制信号线使能了访问,从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。

27.1.2. 存储器矩阵

框图中标号处表示的是存储器矩阵,这个SRAM芯片的空间大小为512Kx16(bits), 见图 SRAM存储阵列模型

SRAM存储阵列模型

SRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址和列地址,就可以精确地找到目标单元格, 这是SRAM芯片寻址的基本原理。这样的每个单元格被称为存储单元,而这样的表则被称为存储矩阵。

27.1.3. 地址译码器、列I/O及I/O数据电路

地址译码器把N根地址线转换成2N根信号线,每根信号线对应一行或一列存储单元,通过地址线找到具体的存储单元,实现寻址。 如果存储阵列比较大,地址线会分成行和列地址,或者行、列分时复用同一地址总线,访问数据寻址时先用地址线传输行地址再传输列地址。 本实例中的SRAM比较小,没有列地址线,它的数据宽度为16位,即一个行地址对应2字节空间,框图中左侧的A0-A18是行址信号, 18根地址线一共可以表示218=28x1024=512K行存储单元, 所以它一共能访问512Kx16bits大小的空间。访问时, 使用UB#或LB#线控制数据宽度,例如,当要访问宽度为16位的数据时,使用行地址线指出地址,然后把UB#和LB#线都设置为低电平, 那么I/O0-I/O15线都有效,它们一起输出该地址的16位数据(或者接收16位数据到该地址);当要访问宽度为8位的数据时,使用行地址线指出地址, 然后把UB#或LB#其中一个设置为低电平,I/O会对应输出该地址的高8位和低8位数据,因此它们被称为数据掩码信号。

27.1.4. 控制电路

控制电路主要包含了片选、读写使能以及上面提到的宽度控制信号UB#和LB#。利用CS2或CS1#片选信号,可以把多个SRAM芯片组成一个大容量的内存条。OE#和WE#可以控制读写使能,防止误操作。

27.1.5. SRAM的读写流程

对SRAM进行读写数据时,它各个信号线的时序流程见图 SRAM的读时序 及图 SRAM的写时序

SRAM的读时序 SRAM的写时序

读写时序的流程很类似,下面我们统一解说:

(1) 主机使用地址信号线发出要访问的存储器目标地址;

(2) 控制片选信号CS1#及CS2#使能存储器芯片;

(3) 若是要进行读操作,则控制读使能信号OE#表示要读数据,若进行写操作则控制写使能信号WE#表示要写数据;

(4) 使用掩码信号LB#与UB#指示要访问目标地址的高、低字节部分;

(5) 若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。

在读写时序中,有几个比较重要的时间参数,在使用STM32 控制的时候需要参考, 它们的介绍见表 IS62WV51216BLL-55ns型号SRAM的时间参数

IS62WV51216BLL-55ns型号SRAM的时间参数

27.2. FSMC简介

STM32F407系列芯片使用FSMC外设来管理扩展的存储器, FSMC是Flexible Static Memory Controller的缩写,译为灵活的静态存储控制器。 它可以用于驱动包括SRAM、NOR FLASH以及NANDFLSAH类型的存储器, 不能驱动如SDRAM这种动态的存储器而在STM32F429系列的控制器中, 它具有FMC外设,支持控制SDRAM存储器。

27.3. FSMC框图剖析

STM32的FSMC外设内部结构见图 FSMC控制器框图

FSMC控制器框图

27.3.1. 通讯引脚

在框图的右侧是FSMC外设相关的控制引脚,由于控制不同类型存储器的时候会有一些不同的引脚,看起来有非常多,其中地址线FSMC_A和数据线FSMC_D是所有控制器都共用的。 这些FSMC引脚具体对应的GPIO端口及引脚号可在《STM32F4xx规格书》中搜索查找到,不在此列出。针对本示例中的SRAM控制 器,我们整理出以下的FSMC与SRAM引脚对照表 FSMC中的SRAM控制信号线

FSMC中的SRAM控制信号线

其中比较特殊的FSMC_NE是用于控制SRAM芯片的片选控制信号线,STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。 例如,当STM32访问0x6C000000-0x6FFFFFFF地址空间时,FSMC_NE3引脚会自动设置为低电平,由于它连接到SRAM的CE#引脚, 所以SRAM的片选被使能,而访问0x60000000-0x63FFFFFF地址时,FSMC_NE1会输出低电平。当使用不同的FSMC_NE引脚连接外部存储器时, STM32访问SRAM的地址不一样,从而达到控制多块SRAM芯片的目的。各引脚对应的地址会在后面“FSMC的地址映射”小节讲解。

27.3.2. 存储器控制器

上面不同类型的引脚是连接到FSMC内部对应的存储控制器中的。NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器,不同的控制器有专用的寄存器用于配置其工作模式。

控制SRAM的有FSMC_BCR1/2/3/4控制寄存器、FSMC_BTR1/2/3/4片选时序寄存器以及FSMC_BWTR1/2/3/4写时序寄存器。每种寄存器都有4个,分别对应于4个不同的存储区域,各种寄存器介绍如下:

  • FSMC_BCR控制寄存器可配置要控制的存储器类型、数据线宽度以及信号有效极性能参数。

  • FMC_BTR时序寄存器用于配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。

  • FMC_BWTR写时序寄存器与FMC_BTR寄存器控制的参数类似,它专门用于控制写时序的时间参数。

27.3.3. 时钟控制逻辑

FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认168MHz),控制器的同步时钟输出就是由它分频得到。例如,NOR控制器的FSMC_CLK引脚输出的时钟,它可用于与同步类型的SRAM芯片进行同步通讯,它的时钟频率可通过FSMC_BTR寄存器的CLKDIV位配置,可以配置为HCLK的1/2或 1/3,也就是说,若它与同步类型的SRAM通讯时,同步时钟最高频率为84MHz。本示例中的SRAM为异步类型的存储器,不使用同步时钟信号,所以时钟分频配置不起作用。

27.4. FSMC的地址映射

FSMC连接好外部的存储器并初始化后,就可以直接通过访问地址来读写数据,这种地址访问与I2C EEPROM、SPI FLASH的不一样, 后两种方式都需要控制I2C或SPI总线给存储器发送地址,然后获取数据;在程序里,这个地址和数据都需要分开使用不同的变量存储, 并且访问时还需要使用代码控制发送读写命令。而使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里, 定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程, 读写命令之类的操作不需要程序控制。FSMC的地址映射见图 FSMC的地址映射

FSMC的地址映射

图中左侧的是Cortex-M4内核的存储空间分配,右侧是STM32 FSMC外设的地址映射。 可以看到FSMC的NOR/PSRAM/SRAM/NAND FLASH以及PC卡的地址都在ExternalRAM地址空间内。 正是因为存在这样的地址映射,使得访问FSMC控制的存储器时,就跟访问STM32的片上外设寄存器一样(片上外设的地址映射即图中左侧的“Peripheral”区域)。

FSMC把整个External RAM存储区域分成了4个Bank区域,并分配了地址范围及适用的存储器类型, 如NOR及SRAM存储器只能使用Bank1的地址。在每个Bank的内部又分成了4个小块, 每个小块有相应的控制引脚用于连接片选信号,如FSMC_NE[4:1]信号线可用于选择BANK1内部的4小块地址区域, 见图 Bank1内部的小块地址分配 , 当STM32访问0x6C000000-0x6FFFFFFF地址空间时,会访问到Bank1的第4小块区域, 相应的FSMC_NE3信号线会输出控制信号。

Bank1内部的小块地址分配

27.5. FSMC控制SRAM的时序

FSMC外设支持输出多种不同的时序以便于控制不同的存储器,它具有ABCD四种模式, 下面我们仅针对控制SRAM使用的模式A进行讲解,见图 FSMC模式A的读时序 及图 FSMC模式A的写时序

FSMC模式A的读时序 FSMC模式A的写时序

当内核发出访问某个指向外部存储器地址时,FSMC外设会根据配置控制信号线产生时序访问存储器,上图中的是访问外部SRAM时FSMC外设的读写时序。

以读时序为例,该图表示一个存储器操作周期由地址建立周期(ADDSET)、数据建立周期(DATAST)以及2个HCLK周期组成。在地址建立周期中,地址线发出要访问的地址, 数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号, 接着存储器通过数据信号线把目标数据传输给FSMC,FSMC把它交给内核。

写时序类似,区别是它的一个存储器操作周期仅由地址建立周期(ADDSET)和数据建立周期(DATAST)组成, 且在数据建立周期期间写使能信号线发出写信号,接着FSMC把数据通过数据线传输到存储器中。

27.6. SRAM时序结构体

控制FSMC使用SRAM存储器时主要是配置时序寄存器以及控制寄存器,利用ST标准库的SRAM时序结构体以及初始化结构体可以很方便地写入参数。

SRAM时序结构体的成员见 代码清单:FSMC-1

代码清单:FSMC-1 SRAM时序结构体FSMC_NORSRAMTimingInitTypeDef
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef struct
{
uint32_t FSMC_AddressSetupTime;       /*地址建立时间,0-0xF个HCLK周期*/
uint32_t FSMC_AddressHoldTime;        /*地址保持时间,0-0xF个HCLK周期*/
uint32_t FSMC_DataSetupTime;           /*地址建立时间,0-0xF个HCLK周期*/
uint32_t FSMC_BusTurnAroundDuration;/*总线转换周期,0-0xF个HCLK周期,在NOR FLASH */
uint32_t FSMC_CLKDivision;/*时钟分频因子,1-0xF,若控制异步存储器,本参数无效 */
uint32_t FSMC_DataLatency;    /*数据延迟时间,若控制异步存储器,本参数无效 */
uint32_t FSMC_AccessMode;             /*设置访问模式 */
}FSMC_NORSRAMTimingInitTypeDef;

这个结构体成员定义的都是SRAM读写时序中的各项时间参数,这些成员的的参数都与FSMC_BRT及FSMC_BWTR寄存器配置对应,各个成员介绍如下:

(1) FSMC_AddressSetupTime

本成员设置地址建立时间,即FSMC读写时序图 FSMC模式A的读时序 中的ADDSET值, 它可以被设置为0-0xF个HCLK周期数,按STM32标准库的默认配置,HCLK的时钟频率为168MHz,即一个HCLK周期为1/168微秒。

(2) FSMC_AddressHoldTime

本成员设置地址保持时间,它可以被设置为0-0xF个HCLK周期数。

(3) FSMC_DataSetupTime

本成员设置数据建立时间,即FSMC读写时序图 FSMC模式A的写时序 中的DATAST值,它可以被设置为0-0xF个HCLK周期数。

(4) FSMC_BusTurnAroundDuration

本成员设置总线转换周期,在NOR FLASH存储器中,地址线与数据线可以分时复用, 总线转换周期就是指总线在这两种状态间切换需要的延时,防止冲突。控制其它存储器时这个参数无效,配置为0即可。

(5) FSMC_CLKDivision

本成员用于设置时钟分频,它以HCLK时钟作为输入,经过FSMC_CLKDivision分频后输出到FSMC_CLK引脚作为通讯使用的同步时钟。 控制其它异步通讯的存储器时这个参数无效,配置为0即可。

(6) FSMC_DataLatency

本成员设置数据保持时间,它表示在读取第一个数据之前要等待的周期数,该周期指同步时钟的周期, 本参数仅用于同步NOR FLASH类型的存储器,控制其它类型的存储器时,本参数无效。

(7) FSMC_AccessMode

本成员设置存储器访问模式,不同的模式下FSMC访问存储器地址时引脚输出的时序不一样,可选FSMC_AccessMode_A/B/C/D模式。一般来说控制SRAM时使用A模式。

这个FSMC_NORSRAMTimingInitTypeDef时序结构体配置的延时参数,将作为下一节的FSMC SRAM初始化结构体的一个成员。

27.7. SRAM初始化结构体

FSMC的SRAM初始化结构体见 代码清单:FSMC-2

代码清单:FSMC-2 SRAM初始化结构体FSMC_NORSRAMInitTypeDef
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @brief  FSMC NOR/SRAM Init structure definition
*/
typedef struct
{
uint32_t FSMC_Bank;                /*设置要控制的Bank区域 */
uint32_t FSMC_DataAddressMux;      /*设置地址总线与数据总线是否复用 */
uint32_t FSMC_MemoryType;          /*设置存储器的类型 */
uint32_t FSMC_MemoryDataWidth;     /*设置存储器的数据宽度*/
uint32_t FSMC_BurstAccessMode; /*设置是否支持突发访问模式,只支持同步类型的存储器 */
uint32_t FSMC_AsynchronousWait;     /*设置是否使能在同步传输时的等待信号,*/
uint32_t FSMC_WaitSignalPolarity;  /*设置等待信号的极性*/
uint32_t FSMC_WrapMode;            /*设置是否支持对齐的突发模式 */
uint32_t FSMC_WaitSignalActive; /*配置等待信号在等待前有效还是等待期间有效 */
uint32_t FSMC_WriteOperation;      /*设置是否写使能 */
uint32_t FSMC_WaitSignal;          /*设置是否使能等待状态插入 */
uint32_t FSMC_ExtendedMode;        /*设置是否使能扩展模式 */
uint32_t FSMC_WriteBurst;          /*设置是否使能写突发操作*/
/*当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序*/
FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
/*当使用扩展模式时,本参数用于配置写时序*/
FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;

这个结构体,除最后两个成员是上一小节讲解的时序配置外,其它结构体成员的配置都对应到FSMC_BCR中的寄存器位。 各个成员意义介绍如下,括号中的是STM32标准库定义的宏:

(1) FSMC_Bank

本成员用于选择FSMC映射的存储区域, 它的可选参数以及相应的内核地址映射范围见表 可以选择的存储器区域及区域对应的地址范围

可以选择的存储器区域及区域对应的地址范围

(2) FSMC_DataAddressMux

本成员用于设置地址总线与数据总线是否复用(FSMC_DataAddressMux_Enable /Disable), 在控制NOR FLASH时,可以地址总线与数据总线可以分时复用,以减少使用STM32信号线的数量。

(3) FSMC_MemoryType

本成员用于设置要控制的存储器类型,它支持控制的存储器类型为SRAM、 PSRAM以及NOR FLASH(FSMC_MemoryType_SRAM/PSRAM/NOR)。

(4) FSMC_MemoryDataWidth

本成员用于设置要控制的存储器的数据宽度,可选择设置成8或16位(FSMC_MemoryDataWidth_8b /16b)。

(5) FSMC_BurstAccessMode

本成员用于设置是否使用突发访问模式(FSMC_BurstAccessMode_Enable/Disable),突发访问模式是指发送一个地址后连续访问多个数据,非突发模式下每访问一个数据都需要输入一个地址,仅在控制同步类型的存储器时才能使用突发模式。

(6) FSMC_AsynchronousWait

本成员用于设置是否使能在同步传输时使用的等待信号(FSMC_AsynchronousWait_Enable/Disable),在控制同步类型的NOR或PSRAM时,存储器可以使用FSMC_NWAIT引脚通知STM32需要等待。

(7) FSMC_WaitSignalPolarity

本成员用于设置等待信号的有效极性,即要求等待时,使用高电平还是低电平(FSMC_WaitSignalPolarity_High/Low)。

(8) FSMC_WrapMode

本成员用于设置是否支持把非对齐的AHB突发操作分割成2次线性操作(FSMC_WrapMode_Enable/Disable),该配置仅在突发模式下有效。

(9) FSMC_WaitSignalActive

本成员用于配置在突发传输模式时,决定存储器是在等待状态之前的一个数据周期有效还是在等待状态期间有效(FSMC_WaitSignalActive_BeforeWaitState/DuringWaitState)。

(10) FSMC_WriteOperation

这个成员用于设置是否写使能(FSMC_WriteOperation_ Enable /Disable), 禁止写使能的话FSMC只能从存储器中读取数据,不能写入。

(11) FSMC_WaitSignal

本成员用于设置当存储器处于突发传输模式时,是否允许通过NWAIT信号插入等待状态(FSMC_WaitSignal_Enable/Disable)。

(12) FSMC_ExtendedMode

本成员用于设置是否使用扩展模式(FSMC_ExtendedMode_Enable/Disable),在非扩展模式下,对存储器读写的时序都只使用FSMC_BCR寄存器中的配置, 即下面的FSMC_ReadWriteTimingStruct结构体成员;在扩展模式下,对存储器的读写时序可以分开配置,读时序使用FSMC_BCR寄存器, 写时序使用FSMC_BWTR寄存器的配置,即下面的FSMC_WriteTimingStruct结构体。

(13) FSMC_ReadWriteTimingStruct

本成员是一个指针,赋值时使用上一小节中讲解的时序结构体FSMC_NORSRAMInitTypeDef设置,当不使用扩展模式时,读写时序都使用本成员的参数配置。

(14) FSMC_WriteTimingStruct

同样地,本成员也是一个时序结构体的指针,只有当使用扩展模式时,本配置才有效,它是写操作使用的时序。

对本结构体赋值完成后,调用FSMC_NORSRAMInit库函数即可把配置参数写入到FSMC_BCR及FSMC_BTR/BWTR寄存器中。

27.8. FSMC—扩展外部SRAM实验

本小节以型号为“IS62WV51216”的SRAM芯片为STM32扩展内存。它的地址线宽度为19位,数据线宽度为16位,容量大小为1MB。

学习本小节内容时,请打开配套的“FSMC—外部SRAM”工程配合阅读。本实验仅讲解基本的外部SRAM驱动, 不涉及内存管理的内容,在本书的《MDK编译过程及文件类型全解》章节将会讲解使用更简单的方法从外部SRAM中分配变量, 以及使用C语言标准库的malloc函数来分配外部SRAM的空间。

27.8.1. 硬件设计

V1

外部SRAM硬件连接图

V2

外部SRAM硬件连接图

外部SRAM芯片与STM32相连的引脚非常多,主要是地址线和数据线,这些具有特定FSMC功能的GPIO引脚可查询《STM32F4xx规格书》中的说明来了解。

关于该SRAM芯片的更多信息,请参考其规格书《IS62WV51216》了解。若您使用的实验板SRAM的型号或控制引脚不一样,可在我们工程的基础上修改,程序的控制原理相同。

根据本硬件设计,SRAM芯片的使能信号与FSMC_NE4连接,所以它会被映射到STM32中的BANK1 NOR/SRAM 4区域, 该区域的地址范围为0x6C000000-0x6FFFFFFF,因此,当内核访问从基地址0x6C000000开始的1MB空间时, FSMC外设会自动控制原理图中的引脚产生访问时序,访问这个外部SRAM存储器。

27.8.2. 软件设计

为了使工程更加有条理,我们把SRAM初始化相关的代码独立分开存储,方便以后移植。在“工程模板”之上新建“bsp_sram.c”及“bsp_sram.h”文件,这些文件也可根据您的喜好命名, 它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。

27.8.2.1. 编程要点

(1) 初始化通讯使用的目标引脚及端口时钟;

(2) 使能FSMC外设的时钟;

(3) 配置FSMC SRAM的时序、工作模式;

(4) 建立机制访问外部SRAM存储器;

(5) 编写测试程序,对读写数据进行校验。

27.8.2.2. 代码分析

FSMC硬件相关宏定义

我们把FSMC SRAM硬件相关的配置都以宏的形式定义到 “bsp_sram.h”文件中,见 代码清单:FSMC-3

代码清单:FSMC-3 SRAM硬件配置相关的宏(省略了大部分数据线,bsp_sram.h文件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#define FSMC_GPIO_AF             GPIO_AF_FSMC

/*A地址信号线*/
#define FSMC_A0_GPIO_PORT        GPIOF
#define FSMC_A0_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A0_GPIO_PIN         GPIO_Pin_0
#define FSMC_A0_GPIO_PinSource   GPIO_PinSource0
/*……省略A1-A18地址线*/

/*D 数据信号线*/
#define FSMC_D0_GPIO_PORT        GPIOD
#define FSMC_D0_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D0_GPIO_PIN         GPIO_Pin_14
#define FSMC_D0_GPIO_PinSource   GPIO_PinSource14
/*……省略D1-D15地址线*/

/*控制信号线*/
/*CS片选*/
/*NE4 ,对应的基地址0x6C000000*/
#define FSMC_CS_GPIO_PORT        GPIOG
#define FSMC_CS_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_CS_GPIO_PIN         GPIO_Pin_12
#define FSMC_CS_GPIO_PinSource   GPIO_PinSource12

/*WE写使能*/
#define FSMC_WE_GPIO_PORT        GPIOD
#define FSMC_WE_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_WE_GPIO_PIN         GPIO_Pin_5
#define FSMC_WE_GPIO_PinSource   GPIO_PinSource5

/*OE读使能*/
#define FSMC_OE_GPIO_PORT        GPIOD
#define FSMC_OE_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_OE_GPIO_PIN         GPIO_Pin_4
#define FSMC_OE_GPIO_PinSource   GPIO_PinSource4


/*UB数据掩码*/
#define FSMC_UDQM_GPIO_PORT        GPIOE
#define FSMC_UDQM_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_UDQM_GPIO_PIN         GPIO_Pin_1
#define FSMC_UDQM_GPIO_PinSource   GPIO_PinSource1

/*LB数据掩码*/
#define FSMC_LDQM_GPIO_PORT        GPIOE
#define FSMC_LDQM_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_LDQM_GPIO_PIN         GPIO_Pin_0
#define FSMC_LDQM_GPIO_PinSource   GPIO_PinSource0

以上代码根据硬件的连接,把与SRAM通讯使用的引脚端口、引脚号以及时钟都以宏封装起来。其中FSMC_CS作为片选引脚对应的是FSMC_NE4, 所以后面我们对SDRAM的寻址空间也是要指向存储区域BANK1 NOR/SRAM 4的。

初始化FSMC的 GPIO

利用上面的宏,编写FSMC的GPIO引脚初始化函数,见 代码清单:FSMC-4

代码清单:FSMC-4 FSMC的GPIO初始化函数(省略了大部分数据线,bsp_sram.c文件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* @brief  初始化控制SRAM的IO
* @param  无
* @retval 无
*/
static void SRAM_GPIO_Config(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;

    /* 使能SRAM相关的GPIO时钟 */

    /*地址信号线,部分省略*/
    RCC_AHB1PeriphClockCmd(FSMC_A0_GPIO_CLK |
                        /*数据信号线,部分省略*/
                        FSMC_D0_GPIO_CLK |
                        /*控制信号线*/
        FSMC_CS_GPIO_CLK  | FSMC_WE_GPIO_CLK | FSMC_OE_GPIO_CLK |
            FSMC_UDQM_GPIO_CLK|FSMC_LDQM_GPIO_CLK, ENABLE);

    /*-- GPIO 配置 -----------------------------------------------------*/

    /* 通用 GPIO 配置 */
    /*--所有GPIO的配置都相同,此处省略大量引脚初始化,具体请查看工程中的代码*/
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

    /*A地址信号线 引脚配置*/
    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);
    /*...*/

    /*DQ数据信号线 引脚配置*/
    GPIO_InitStructure.GPIO_Pin = FSMC_D0_GPIO_PIN;
    GPIO_Init(FSMC_D0_GPIO_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(FSMC_D0_GPIO_PORT,FSMC_D0_GPIO_PinSource,FSMC_GPIO_AF);
    /*...*/

    /*控制信号线*/
    GPIO_InitStructure.GPIO_Pin = FSMC_CS_GPIO_PIN;
    GPIO_Init(FSMC_CS_GPIO_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(FSMC_CS_GPIO_PORT,FSMC_CS_GPIO_PinSource,FSMC_GPIO_AF);
    /*...*/
}

与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,以上代码把FSMC SRAM的所有信号线全都初始化为复用功能,所有引脚配置都是一样的。

配置FSMC的模式

接下来需要配置FSMC SRAM的工作模式,这个函数的主体是根据硬件连接的SRAM特性,对时序结构体以及初始化结构体进行赋值。 见 代码清单:FSMC-5

代码清单:FSMC-5 配置FSMC的模式(bsp_sram.c文件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* @brief  初始化FSMC外设
* @param  None.
* @retval None.
*/
void FSMC_SRAM_Init(void)
{
    FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
    FSMC_NORSRAMTimingInitTypeDef  readWriteTiming;

    /*初始化SRAM相关的GPIO*/
    SRAM_GPIO_Config();

    /*使能FSMC外设时钟*/
    RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);
    /*********************时序**************************/
    //地址建立时间(ADDSET)为1个HCLK
    readWriteTiming.FSMC_AddressSetupTime = 0x00;

    //地址保持时间(ADDHLD)模式A未用到
    readWriteTiming.FSMC_AddressHoldTime = 0x00;

    //数据保持时间(DATAST)+ 1个HCLK = 9/168M=54ns(对EM的SRAM芯片)
    readWriteTiming.FSMC_DataSetupTime = 0x08;

    //设置总线转换周期,仅用于复用模式的NOR操作
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;

    //设置时钟分频,仅用于同步类型的存储器
    readWriteTiming.FSMC_CLKDivision = 0x00;

    //数据保持时间,仅用于同步型的NOR
    readWriteTiming.FSMC_DataLatency = 0x00;

    //选择匹配SRAM的模式
    readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;

    /**************************控制************************************/
    // 选择FSMC映射的存储区域: Bank1 sram4
    FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;

    //设置地址总线与数据总线是否复用,仅用于NOR
    FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;

    //设置要控制的存储器类型:SRAM类型
    FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;

    //存储器数据宽度:16位
    FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;

    //设置是否使用突发访问模式,仅用于同步类型的存储器
    FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;

    //设置是否使能等待信号,仅用于同步类型的存储器
    FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;

    //设置等待信号的有效极性,仅用于同步类型的存储器
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;

    //设置是否支持把非对齐的突发操作,仅用于同步类型的存储器
    FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;

    //设置等待信号插入的时间,仅用于同步类型的存储器
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;

    //存储器写使能
    FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;

    //不使用等待信号
    FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;

    // 不使用扩展模式,读写使用相同的时序
    FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;

    //突发写操作
    FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;

    //读写时序配置
    FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;

    //读写同样时序,使用扩展模式时这个配置才有效
    FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;

    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK

}

这个函数的执行流程如下:

(1) 初始化GPIO引脚以及FSMC时钟

函数开头调用了前面定义的SRAM_GPIO_Config函数对FSMC用到的GPIO进行初始化,并且使用库函数RCC_AHBPeriphClockCmd使能FSMC外设的时钟。

(2) 时序结构体赋值

接下来对时序结构体FSMC_NORSRAMTimingInitTypeDef赋值。在这个时序结构体配置中,由于我们要控制的是SRAM,所以选择FSMC为模式A, 在该模式下配置FSMC的控制时序结构体中,实际上只有地址建立时间FSMC_AddressSetupTime(即ADDSET的值)以及数据建立时间FSMC_DataSetupTime(即DATAST的值)成员的配置值是有效的, 其它SRAM没使用到的成员值全配置为0即可。而且,这些成员值使用的单位为:1个HCLK的时钟周期,而HCLK的时钟频率为168MHz,对应每个时钟周期为1/168微秒。

由图 FSMC时序配置与SRAM读时序参数要求对比 的FSMC时序和SRAM时序对比及SRAM时间参数要求可总结出 表 SRAM的读操作参数 最右侧的计算表达式。

FSMC时序配置与SRAM读时序参数要求对比 SRAM的读操作参数

根据FSMC配置表达式的配置要求把时间单位1/168微秒(即1000/168纳秒)代入,可求得ADDSET = 0,DATAST=1时即可符合要求。如:

tRC=ADDSET+1+DATAST+1+2 =( 0+1+8+1+2 )*1000/168 = 72.4 ns > 55 ns

tDOE=DATAST+1 = (8+1)*1000/168= 53.5 > 25 ns

可看出本实验中的配置有充足的裕量,裕量较大,可确保访问正确,但会导致访问速度变慢, 可根据实际需要进行测试调整,保证访问正确的前提下可提高访问速度。 不过还需要注意本实验的读时序配置与写时序是一致的,修改时还要确保写时序正常, 下面再来列出写时序的计算过程:

由图 FSMC时序配置与SRAM写时序参数要求对比 的FSMC时序和SRAM时序对比及SRAM时间参数要求可总结出 表 SRAM的写操作参数 最右侧的计算表达式。

FSMC时序配置与SRAM时写序参数要求对比 SRAM的写操作参数

根据FSMC配置表达式的配置要求把时间单位1/168微秒(即1000/168纳秒)代入,可求得ADDSET = 0,DATAST=8时即可符合要求。如:

tWC = ADDSET+1+DATAST+1 =( 0+1+8+1 )*1000/168 = 59.5 ns > 55 ns

tPWB = DATAST+1 = (8+1) *1000/168 = 53.5 > 40 ns

把计算得的参数赋值到时序结构体中的FSMC_AddressSetupTime(即ADDSET的值)及FSMC_DataSetupTime(即DATAST的值)中, 然后再把时序结构体作为指针赋值到下面的FSMC初始化结构体中,作为读写的时序参数,最后再调用FSMC_NORSRAMInit函数即可把参数写入到相应的寄存器中。

(3) 配置FSMC初始化结构体

函数接下来对FSMC SRAM的初始化结构体赋值。主要包括存储映射区域、存储器类型以及数据线宽度等,这些是根据外接的SRAM的电路设置的。

  • 设置存储区域FSMC_Bank

FSMC_Bank成员设置FSMC的SRAM存储区域映射选择为FSMC_Bank1_NORSRAM4,这是由于我们的SRAM硬件连接到FSMC_NE4和NOR/PSRAM相关引脚, 所以对应到存储区域Bank1 SRAM4,对应的基地址为0x6C000000;

  • 存储器类型FSMC_MemoryType

由于我们控制的是SRAM类型存储器,所以FSMC_MemoryType成员要选择相应的FSMC_MemoryType_SRAM;

  • 数据线宽度FSMC_MemoryDataWidth

根据硬件的数据线连接,数据线宽度被配置为16位宽FSMC_MemoryDataWidth_16b;

  • 写使能FSMC_WriteOperation

FSMC_WriteOperation用于设置写使能,只有使能了才能正常使用FSMC向外部存储器写入数据;

  • 扩展模式以及读写时序

在FSMC_ExtendedMode成员中可以配置是否使用扩展模式,当设置扩展模式时,读时序使用FSMC_ReadWriteTimingStruct中的配置, 写时序使用FSMC_WriteTimingStruct中的配置,两种配置互相独立,可以赋值为不同的读写时序结构体。在本实例中不使用扩展模式, 即读写时序使用相同的配置,都是赋值为前面的readWriteTiming结构体;

  • 其它

配置FSMC还涉及到其它的结构体成员,但这些结构体成员与SRAM控制不相关,都被设置为Disable了;

赋值完成后调用库函数FSMC_NORSRAMInit把初始化结构体配置的各种参数写入到FSMC_BCR控制寄存器及FSMC_BTR时序寄存器中。 最后调用FSMC_NORSRAMCmd函数使能要控制的存储区域FSMC_Bank1_NORSRAM4。

使用指针的方式访问SRAM存储器

完成初始化SRAM后,我们就可以利用它存储数据了,由于SRAM的存储空间是被映射到内核的寻址区域的,我们可以通过映射的地址直接访问SRAM,访问这些地址时, FSMC外设自动读写SRAM,程序上无需额外操作。

通过地址访问内存,最直接的就是使用C语言的指针方式,见 代码清单:FSMC-6

代码清单:FSMC-6 使用指针的方式访问SRAM
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/*SRAM起始地址 存储空间4的起始地址*/
#define Bank1_SRAM4_ADDR     ((uint32_t)0x6C000000)
/*SRAM大小,1M字节*/
#define IS62WV51216_SIZE 0x100000

uint32_t temp;

/*向SRAM写入8位数据*/
*( uint8_t*) (Bank1_SRAM4_ADDR ) = (uint8_t)0xAA;
/*从SRAM读取数据*/
temp =  *( uint8_t*) (Bank1_SRAM4_ADDR );

/*写/读 16位数据*/
*( uint16_t*) (Bank1_ SRAM4_ADDR +10 ) = (uint16_t)0xBBBB;
temp =  *( uint16_t*) (Bank1_SRAM4_ADDR+10 );

/*写/读 32位数据*/
*( uint32_t*) (Bank1_SRAM4_ADDR +20 ) = (uint32_t)0xCCCCCCCC;
temp =  *( uint32_t*) (Bank1_SRAM4_ADDR+20 );

为方便使用,代码中首先定义了宏Bank1_SRAM4_ADDR表示SRAM的起始地址, 该地址即FSMC映射的存储区域Bank SRAM4的首地址;宏IS62WV51216_SIZE表示SRAM的大小, 所以从地址(Bank1_SRAM4_ADDR)到(Bank1_SRAM3_ADDR+ IS62WV51216_SIZE)都表示在SRAM的存储空间, 访问这些地址,直接就能访问SRAM。

配合这些宏,使用指针的强制转换以及取指针操作即可读写SRAM的数据,使用上跟普通的变量无异。

直接指定变量存储到SRAM空间

每次存取数据都使用指针来访问太麻烦了,为了简化操作,可以直接指定变量存储到SRAM空间, 见 代码清单:FSMC-7

代码清单:FSMC-7 直接指定变量地址的方式访问SRAM
1
2
3
4
5
/*SRAM起始地址 存储空间2的起始地址*/
#define Bank1_SRAM4_ADDR     ((uint32_t)0x6C000000)
/*绝对定位方式访问SRAM,这种方式必须定义成全局变量*/
uint8_t testValue __attribute__((at(SDRAM_BANK_ADDR)));
testValue = 0xDD;

这种方式使用关键字“__attribute__((at()))”来指定变量的地址,代码中指定testValue存储到SRAM的起始地址,从而实现把变量存储到SRAM上。要注意使用这种方法定义变量时, 必须在函数外把它定义成全局变量,才可以存储到指定地址上。

更常见的是利用这种方法定义一个很大的数组,整个数组都指定到SRAM地址上,然后就像使用malloc函数一样,用户自定义一些内存管理函数,动态地使用SRAM的内存, 我们在使用emWin写GUI应用的时候就是这样做的。参考我们配套的“FSMC—外部SRAM(内存管理)”实验可以了解如何自行管理内存。。

然而,我们更推荐另一种方法,在本书的《MDK编译过程及文件类型全解》章节将会讲解使用更简单的方法从SRAM中分配变量,以及使用C语言标准库的malloc函数来分配SRAM的空间, 更有效地进行内存管理。

27.8.2.3. main函数

最后我们来编写main函数,进行SRAM芯片读写校验,见 代码清单:FSMC-8

代码清单:FSMC-8 main函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*绝对定位方式访问SRAM,这种方式必须定义成全局变量*/
uint8_t testValue __attribute__((at(Bank1_SRAM4_ADDR)));
/*
* 函数名:main
* 描述  :主函数
* 输入  :无
* 输出  :无
*/
int main(void)
{
    LED_GPIO_Config();
    LED_BLUE;

    /* 配置串口1为:115200 8-N-1 */
    Debug_USART_Config();

    //初始化外部SRAM
    FSMC_SRAM_Init();

    printf ( "\r\n野火外部 SRAM 测试\r\n" );

    /*蓝灯亮,表示正在读写SRAM测试*/
    LED_BLUE;

    /*对SRAM进行读写测试,检测SRAM是否正常*/
    if (SRAM_Test()==1) {
        //测试正常 绿灯亮
        LED_GREEN;
    } else {
        //测试失败 红灯亮
        LED_RED;
    }

    /*指针方式访问SRAM*/
    {
        uint32_t temp;

        printf("\r\n指针方式访问SRAM\r\n");
        /*向SRAM写入8位数据*/
        *( uint8_t*) (Bank1_SRAM4_ADDR ) = (uint8_t)0xAA;
        printf("\r\n指针访问SRAM,写入数据0xAA \r\n");

        /*从SRAM读取数据*/
        temp =  *( uint8_t*) (Bank1_SRAM4_ADDR );
        printf("读取数据:0x%X \r\n",temp);

        /*写/读 16位数据*/
        *( uint16_t*) (Bank1_SRAM4_ADDR+10 ) = (uint16_t)0xBBBB;
        printf("指针访问SRAM,写入数据0xBBBB \r\n");

        temp =  *( uint16_t*) (Bank1_SRAM4_ADDR+10 );
        printf("读取数据:0x%X \r\n",temp);


        /*写/读 32位数据*/
        *( uint32_t*) (Bank1_SRAM4_ADDR+20 ) = (uint32_t)0xCCCCCCCC;
        printf("指针访问SRAM,写入数据0xCCCCCCCC \r\n");
        temp =  *( uint32_t*) (Bank1_SRAM4_ADDR+20 );
        printf("读取数据:0x%X \r\n",temp);

    }

    /*绝对定位方式访问SRAM,这种方式必须定义成全局变量*/
    {
        testValue = 0xDD;
    printf("\r\n绝对定位访问SRAM,写入数据0xDD,读出数据0x%X,变量地址为%X\r\n",testValue,
            (uint32_t )&testValue);
    }
    while (1) {

    }
}

函数中初始化了LED、串口,接着调用前面定义好的SRAM_Init函数初始化FSMC及SRAM,然后调用自定义的测试函数SRAM_Test尝试使用SRAM存取8、16及32位数据, 并进行读写校验,它就是使用指针的方式存取数据并校验而已,此处不展开。

注意对SRAM存储空间的数据操作都要在FSMC_SRAM_Init初始化FSMC之后,否则数据是无法正常存储的。

27.8.3. 下载验证

用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到SRAM测试的调试信息。

27.9. 补充说明

因为芯片供应等原因,霸天虎V2底板实际使用SRAM芯片请以资料中的原理图和实物为准,目前使用过的芯片在程序上都完全兼容。