Bootstrap

FSMC—扩展外部 SRAM

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

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

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

 SRAM

这是它的外观图,可以看到,该芯片有44个引脚


下表是该SRAM芯片的信号线说明。

信号线类型说明
A0~A18I地址输入
I/O0~I/O7I/O数据输入输出信号,低字节
I/O8~I/O15I/O数据输入输出信号,高字节
CS2 和CS1#I片选信号,CS2高电平有效,CS1#低电平有效,部分芯片只有其中一个引脚
OE#I输出使能信号,低电平有效
WE#I写入使能,低电平有效
UB#I数据掩码信号Upper Byte,高位字节允许访问,低电平有效
LB#I数据掩码信号Lower Byte,低位字节允许访问,低电平有效

注意:

  • 该表是站在SARM芯片的角度描述的
  • 类型中,I表示输入,O表示输出
  • 带#号表示低电平有效

SRAM的控制比较简单,只要控制信号线(片选信号CS1#,输出使能信号OE#,输入使能信号WE#)进行了使能,再从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。

这是它的内部功能框图。


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

  • 地址译码器、列I/O及I/O数据电路
  • 地址译码器把N根地址线转换成2N根信号线,每根信号线对应一行或一列存储单元,通过地址线找到具体的存储单元,实现寻址。如果存储阵列比较大,地址线会分成行和列地址,或者行、 列分时复用同一地址总线,访问数据寻址时先用地址线传输行地址再传输列地址。
  • 本次使用的SRAM比较小,没有列地址线,它的数据宽度为16位,即一个行地址对应2字节空间,框图中左侧的A0-A18是行址信号,19根地址线一共可以表示219=29x1024=512K行存储单元,所以它一共能访问512Kx16bits大小的空间。
  • 访问时,使用UB#或LB#线控制数据宽度。UB#控制访问的是高字节,也就是D8 ~ D15,而LB#控制访问低字节,也就是D0 ~D7。当要访 问宽度为 16 位的数据时,使用行地址线指出地址,然后把 UB# 和 LB# 线都设置为低电平,那 么 I/O0-I/O15 线都有效,它们一起输出该地址的 16 位数据 (或者接收 16 位数据到该地址);当要 访问宽度为 8 位的数据时,使用行地址线指出地址,然后把 UB# 或 LB# 其中一个设置为低电平,I/O 会对应输出该地址的高 8 位和低 8 位数据,因此它们被称为数据掩码信号。
  • 控制电路主要包含了片选CS2或CS1#、读写使能OE#和WE#以及上面提到的宽度控制信号UB#和LB#。
  • 利用CS2或CS1#片选信号(CS2高电平有效,CS1低电平有效),可以把多个SRAM芯片组成一个大容量的内存条。
  • OE#和WE#可以控制读写使能,防止误操作。

存储器矩阵

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

SRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。

和表格查找一样,指定一个行地址,再指定该行的高字节还是低字节,就可以精确地找到目标单元格,这便是使用的SRAM芯片寻址的基本原理。


 SRAM的读写流程

读写时序的流程很类似,过程如下:

  • 主机使用地址信号线发出要访问的存储器目标地址;
  • 控制片选信号CS1#或CS2#使能存储器芯片;
  • 若是要进行读操作,则控制读使能信号OE#表示要读数据,若进行写操,则控制写使能信号WE#表示要写数据;
  •  使用掩码信号LB#与UB#指示要访问目标地址的高、低字节部分;
  •  若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。

下面分别看一下读和写的时序图。

对SRAM进行读数据时的时序流程

这是进行读的时候的时序图,注意图中画红色框的三个时间,这三个是我们后面需要配置的重点。

这是时序图中发生信号变化的限制时间,这里本喵使用的SRAM芯片是中速访问的,只需要看这个表中红色框中的那一列即可。

过程:

  • 在SRAM收到MCU给的地址信号后,ADDRESS信号发生变化,tRC开始计时,对照时间表得出,tRC是表示的是整个读取数据过程所用的时间,并且必须大于55ns。
  • 在OE#信号由高电平变为低电平的同时开始计时,经过时间tDOE后,数据线上出现了数据,时间tDOE表示输出通道时间,对照表中得出,时间tDOE的最小时间没有限制,但是最大不能超过25ns。
  • 在ADDRESS发生变化后到数据线上出现数据的这段时间tAA称为地址通道时间,对照表中得出,时间tAA最大不能过55ns。

其他时间所代表的意义以及取值范围,大家可以对照时序图以及时间表得出,因为在扩展SRAM时并不涉及到其他的时间,所以就不介绍别的时间了。


对SRAM进行写数据时的时序流程

这是进行写的时候的时序图,注意图中画红色框的三个时间,这三个是我们后面需要配置的重点。

这是时序图中发生信号变化的限制时间,这里本喵使用的SRAM芯片是中速访问的,只需要看这个表中红色框中的那一列即可。

过程:

  • 在SRAM收到地址信号后,ADDRESS信号发生变化,并且tWC开始计时,对照时间表得出,tWC表示的是整个写数据过程所用的时间,并且必须大于55ns。
  • 在WE#信号由高电平变化为低电平的同时开始计时,经过时间tPWB后,数据线上出现了数据,时间tPWE表示写信号的时间宽度,对照表中得出的最小时间是45ns。
  • 在ADDRESS信号发生变化后到WE#信号发生变化所经过的时间tSA,对照表中得出,时间tSA表示地址建立时间,它没有时间限制,在ADDRESS信号发生变化后或者同时产生变化就可以。

其他时间所代表的意义以及取值范围,大家可以对照时序图以及时间表得出,因为在扩展SRAM时并不涉及到其他的时间,所以就不介绍别的时间了。

将上面所用到的时间总结为下表:


片内FSMC介绍

通过上面对SRAM的介绍,我们可以看到,在对SRAM进行的每一次操作,都必须按照SRAM的时序来进行操作。

我们可以通过程序控制各个引脚的电平来实现这个时序,从而操作SRAM,但是这样不仅很复杂,而且灵敏度也会比较低 。

STM32F103ZET6就有一个片内的外设,可以实现SRAM的时序,我们只需要将该外设配置好,就能像操作内部的SRAM一样操作外部扩展的SRAM,这个外设就是FSMC。

STM32F407 系列芯片使用 FSMC 外设来管理扩展的存储器,

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


FSMC 框图剖析

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


通讯引脚

在框图的右侧是 FSMC 外设相关的控制引脚,由于控制不同类型存储器的时候会有一些不同的 引脚,看起来有非常多,其中地址线 FSMC_A 和数据线 FSMC_D 是所有控制器都共用的。

这些 FSMC 引脚具体对应的 GPIO 端口及引脚号可在《STM32F4xx 规格书》中搜索查找到,不在此列 出。针对本示例中的 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 的地 址映射”小节讲解。


存储器控制器

上面不同类型的引脚是连接到 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 寄存器控制的参数类似,它专门用于控制写时序 的时间参数。


时钟制逻辑

FSMC 外设挂载在 AHB 总线上,时钟信号来自于 HCLK(默认 168MHz),控制器的同步时钟输出 就是由它分频得到。

例如,NOR 控制器的 FSMC_CLK 引脚输出的时钟,它可用于与同步类型的 SRAM 芯片进行同步通讯,它的时钟频率可通过 FSMC_BTR 寄存器的 CLKDIV 位配置,可以配 置为 HCLK 的 1/2 或 1/3,也就是说,若它与同步类型的 SRAM 通讯时,同步时钟最高频率为 84MHz。

本示例中的 SRAM 为异步类型的存储器,不使用同步时钟信号,所以时钟分频配置不起作用。


FSMC 的地址映射

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


SRAM 时序结构体

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

SRAM 时序结构体的成员

这个结构体成员定义的都是 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 初始化结构体的一个成员。


SRAM 初始化结构体

FSMC 的 SRAM 初始化结构体

这个结构体,除最后两个成员是上一小节讲解的时序配置外,其它结构体成员的配置都对应到 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 寄存器中。


FSMC—扩展外部 SRAM 实验

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

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


硬件设计

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

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

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


编程要点

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

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

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

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

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


一堆宏定义

主要包括A和D的地址信号线(各18个),以及片选信号线,写使能读使能和两个事件掩码。

//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=10 
//对IS61LV25616/IS62WV25616,地址线范围为A0~A17 
//对IS61LV51216/IS62WV51216,地址线范围为A0~A18
#define Bank1_SRAM4_ADDR    ((uint32_t)(0x6C000000))		

#define IS62WV51216_SIZE 0x100000  //512*16/2bits = 0x100000  ,1M字节


#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

#define FSMC_A1_GPIO_PORT        GPIOF
#define FSMC_A1_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A1_GPIO_PIN         GPIO_Pin_1
#define FSMC_A1_GPIO_PinSource   GPIO_PinSource1

#define FSMC_A2_GPIO_PORT        GPIOF
#define FSMC_A2_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A2_GPIO_PIN         GPIO_Pin_2
#define FSMC_A2_GPIO_PinSource   GPIO_PinSource2

#define FSMC_A3_GPIO_PORT        GPIOF
#define FSMC_A3_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A3_GPIO_PIN         GPIO_Pin_3
#define FSMC_A3_GPIO_PinSource   GPIO_PinSource3

#define FSMC_A4_GPIO_PORT        GPIOF
#define FSMC_A4_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A4_GPIO_PIN         GPIO_Pin_4
#define FSMC_A4_GPIO_PinSource   GPIO_PinSource4

#define FSMC_A5_GPIO_PORT        GPIOF
#define FSMC_A5_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A5_GPIO_PIN         GPIO_Pin_5
#define FSMC_A5_GPIO_PinSource   GPIO_PinSource5

#define FSMC_A6_GPIO_PORT        GPIOF
#define FSMC_A6_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A6_GPIO_PIN         GPIO_Pin_12
#define FSMC_A6_GPIO_PinSource   GPIO_PinSource12

#define FSMC_A7_GPIO_PORT        GPIOF
#define FSMC_A7_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A7_GPIO_PIN         GPIO_Pin_13
#define FSMC_A7_GPIO_PinSource   GPIO_PinSource13

#define FSMC_A8_GPIO_PORT        GPIOF
#define FSMC_A8_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A8_GPIO_PIN         GPIO_Pin_14
#define FSMC_A8_GPIO_PinSource   GPIO_PinSource14

#define FSMC_A9_GPIO_PORT        GPIOF
#define FSMC_A9_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FSMC_A9_GPIO_PIN         GPIO_Pin_15
#define FSMC_A9_GPIO_PinSource   GPIO_PinSource15

#define FSMC_A10_GPIO_PORT        GPIOG
#define FSMC_A10_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A10_GPIO_PIN         GPIO_Pin_0
#define FSMC_A10_GPIO_PinSource   GPIO_PinSource0

#define FSMC_A11_GPIO_PORT        GPIOG
#define FSMC_A11_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A11_GPIO_PIN         GPIO_Pin_1
#define FSMC_A11_GPIO_PinSource   GPIO_PinSource1

#define FSMC_A12_GPIO_PORT        GPIOG
#define FSMC_A12_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A12_GPIO_PIN         GPIO_Pin_2
#define FSMC_A12_GPIO_PinSource   GPIO_PinSource2

#define FSMC_A13_GPIO_PORT        GPIOG
#define FSMC_A13_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A13_GPIO_PIN         GPIO_Pin_3
#define FSMC_A13_GPIO_PinSource   GPIO_PinSource3

#define FSMC_A14_GPIO_PORT        GPIOG
#define FSMC_A14_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A14_GPIO_PIN         GPIO_Pin_4
#define FSMC_A14_GPIO_PinSource   GPIO_PinSource4

#define FSMC_A15_GPIO_PORT        GPIOG
#define FSMC_A15_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FSMC_A15_GPIO_PIN         GPIO_Pin_5
#define FSMC_A15_GPIO_PinSource   GPIO_PinSource5

#define FSMC_A16_GPIO_PORT        GPIOD
#define FSMC_A16_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_A16_GPIO_PIN         GPIO_Pin_11
#define FSMC_A16_GPIO_PinSource   GPIO_PinSource11

#define FSMC_A17_GPIO_PORT        GPIOD
#define FSMC_A17_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_A17_GPIO_PIN         GPIO_Pin_12
#define FSMC_A17_GPIO_PinSource   GPIO_PinSource12

#define FSMC_A18_GPIO_PORT        GPIOD
#define FSMC_A18_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_A18_GPIO_PIN         GPIO_Pin_13
#define FSMC_A18_GPIO_PinSource   GPIO_PinSource13

/*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

#define FSMC_D1_GPIO_PORT        GPIOD
#define FSMC_D1_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D1_GPIO_PIN         GPIO_Pin_15
#define FSMC_D1_GPIO_PinSource   GPIO_PinSource15

#define FSMC_D2_GPIO_PORT        GPIOD
#define FSMC_D2_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D2_GPIO_PIN         GPIO_Pin_0
#define FSMC_D2_GPIO_PinSource   GPIO_PinSource0

#define FSMC_D3_GPIO_PORT        GPIOD
#define FSMC_D3_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D3_GPIO_PIN         GPIO_Pin_1
#define FSMC_D3_GPIO_PinSource   GPIO_PinSource1

#define FSMC_D4_GPIO_PORT        GPIOE
#define FSMC_D4_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D4_GPIO_PIN         GPIO_Pin_7
#define FSMC_D4_GPIO_PinSource   GPIO_PinSource7

#define FSMC_D5_GPIO_PORT        GPIOE
#define FSMC_D5_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D5_GPIO_PIN         GPIO_Pin_8
#define FSMC_D5_GPIO_PinSource   GPIO_PinSource8

#define FSMC_D6_GPIO_PORT        GPIOE
#define FSMC_D6_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D6_GPIO_PIN         GPIO_Pin_9
#define FSMC_D6_GPIO_PinSource   GPIO_PinSource9

#define FSMC_D7_GPIO_PORT        GPIOE
#define FSMC_D7_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D7_GPIO_PIN         GPIO_Pin_10
#define FSMC_D7_GPIO_PinSource   GPIO_PinSource10

#define FSMC_D8_GPIO_PORT        GPIOE
#define FSMC_D8_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D8_GPIO_PIN         GPIO_Pin_11
#define FSMC_D8_GPIO_PinSource   GPIO_PinSource11

#define FSMC_D9_GPIO_PORT        GPIOE
#define FSMC_D9_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D9_GPIO_PIN         GPIO_Pin_12
#define FSMC_D9_GPIO_PinSource   GPIO_PinSource12

#define FSMC_D10_GPIO_PORT        GPIOE
#define FSMC_D10_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D10_GPIO_PIN         GPIO_Pin_13
#define FSMC_D10_GPIO_PinSource   GPIO_PinSource13

#define FSMC_D11_GPIO_PORT        GPIOE
#define FSMC_D11_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D11_GPIO_PIN         GPIO_Pin_14
#define FSMC_D11_GPIO_PinSource   GPIO_PinSource14

#define FSMC_D12_GPIO_PORT        GPIOE
#define FSMC_D12_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FSMC_D12_GPIO_PIN         GPIO_Pin_15
#define FSMC_D12_GPIO_PinSource   GPIO_PinSource15

#define FSMC_D13_GPIO_PORT        GPIOD
#define FSMC_D13_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D13_GPIO_PIN         GPIO_Pin_8
#define FSMC_D13_GPIO_PinSource   GPIO_PinSource8

#define FSMC_D14_GPIO_PORT        GPIOD
#define FSMC_D14_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D14_GPIO_PIN         GPIO_Pin_9
#define FSMC_D14_GPIO_PinSource   GPIO_PinSource9

#define FSMC_D15_GPIO_PORT        GPIOD
#define FSMC_D15_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FSMC_D15_GPIO_PIN         GPIO_Pin_10
#define FSMC_D15_GPIO_PinSource   GPIO_PinSource10

/*控制信号线*/  
/*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


/*信息输出*/
#define SRAM_DEBUG_ON         1

#define SRAM_INFO(fmt,arg...)           printf("<<-SRAM-INFO->> "fmt"\n",##arg)
#define SRAM_ERROR(fmt,arg...)          printf("<<-SRAM-ERROR->> "fmt"\n",##arg)
#define SRAM_DEBUG(fmt,arg...)          do{\
                                          if(SRAM_DEBUG_ON)\
                                          printf("<<-SRAM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)

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


初始化 FSMC 的 GPIO

利用上面的宏,编写 FSMC 的 GPIO 引脚初始化函数

/**
  * @brief  初始化控制SRAM的IO
  * @param  无
  * @retval 无
  */
static void SRAM_GPIO_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 
  /* 使能SRAM相关的GPIO时钟 */

                         /*地址信号线*/
  RCC_AHB1PeriphClockCmd(FSMC_A0_GPIO_CLK | FSMC_A1_GPIO_CLK | FSMC_A2_GPIO_CLK | 
                         FSMC_A3_GPIO_CLK | FSMC_A4_GPIO_CLK | FSMC_A5_GPIO_CLK |
                         FSMC_A6_GPIO_CLK | FSMC_A7_GPIO_CLK | FSMC_A8_GPIO_CLK |
                         FSMC_A9_GPIO_CLK | FSMC_A10_GPIO_CLK| FSMC_A11_GPIO_CLK| 
                         FSMC_A12_GPIO_CLK| FSMC_A13_GPIO_CLK|FSMC_A14_GPIO_CLK|
						 FSMC_A15_GPIO_CLK|FSMC_A16_GPIO_CLK|FSMC_A17_GPIO_CLK|FSMC_A18_GPIO_CLK|
                         /*数据信号线*/
                         FSMC_D0_GPIO_CLK | FSMC_D1_GPIO_CLK | FSMC_D2_GPIO_CLK | 
                         FSMC_D3_GPIO_CLK | FSMC_D4_GPIO_CLK | FSMC_D5_GPIO_CLK |
                         FSMC_D6_GPIO_CLK | FSMC_D7_GPIO_CLK | FSMC_D8_GPIO_CLK |
                         FSMC_D9_GPIO_CLK | FSMC_D10_GPIO_CLK| FSMC_D11_GPIO_CLK|
                         FSMC_D12_GPIO_CLK| FSMC_D13_GPIO_CLK| FSMC_D14_GPIO_CLK|
                         FSMC_D15_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_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);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A1_GPIO_PIN; 
  GPIO_Init(FSMC_A1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A1_GPIO_PORT,FSMC_A1_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A2_GPIO_PIN; 
  GPIO_Init(FSMC_A2_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A2_GPIO_PORT,FSMC_A2_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A3_GPIO_PIN; 
  GPIO_Init(FSMC_A3_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A3_GPIO_PORT,FSMC_A3_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A4_GPIO_PIN; 
  GPIO_Init(FSMC_A4_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A4_GPIO_PORT,FSMC_A4_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A5_GPIO_PIN; 
  GPIO_Init(FSMC_A5_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A5_GPIO_PORT,FSMC_A5_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A6_GPIO_PIN; 
  GPIO_Init(FSMC_A6_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A6_GPIO_PORT,FSMC_A6_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A7_GPIO_PIN; 
  GPIO_Init(FSMC_A7_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A7_GPIO_PORT,FSMC_A7_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A8_GPIO_PIN; 
  GPIO_Init(FSMC_A8_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A8_GPIO_PORT,FSMC_A8_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A9_GPIO_PIN; 
  GPIO_Init(FSMC_A9_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A9_GPIO_PORT,FSMC_A9_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A10_GPIO_PIN; 
  GPIO_Init(FSMC_A10_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A10_GPIO_PORT,FSMC_A10_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A11_GPIO_PIN; 
  GPIO_Init(FSMC_A11_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A11_GPIO_PORT,FSMC_A11_GPIO_PinSource,FSMC_GPIO_AF);
	
  GPIO_InitStructure.GPIO_Pin = FSMC_A12_GPIO_PIN; 
  GPIO_Init(FSMC_A12_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A12_GPIO_PORT,FSMC_A12_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A13_GPIO_PIN; 
  GPIO_Init(FSMC_A13_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A13_GPIO_PORT,FSMC_A13_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A14_GPIO_PIN; 
  GPIO_Init(FSMC_A14_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A14_GPIO_PORT,FSMC_A14_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A15_GPIO_PIN; 
  GPIO_Init(FSMC_A15_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A15_GPIO_PORT,FSMC_A15_GPIO_PinSource,FSMC_GPIO_AF);  
	
  GPIO_InitStructure.GPIO_Pin = FSMC_A16_GPIO_PIN; 
  GPIO_Init(FSMC_A16_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A16_GPIO_PORT,FSMC_A16_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A17_GPIO_PIN; 
  GPIO_Init(FSMC_A17_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A17_GPIO_PORT,FSMC_A17_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_A18_GPIO_PIN; 
  GPIO_Init(FSMC_A18_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_A18_GPIO_PORT,FSMC_A18_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_D1_GPIO_PIN; 
  GPIO_Init(FSMC_D1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D1_GPIO_PORT,FSMC_D1_GPIO_PinSource,FSMC_GPIO_AF);
    
  GPIO_InitStructure.GPIO_Pin = FSMC_D2_GPIO_PIN; 
  GPIO_Init(FSMC_D2_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D2_GPIO_PORT,FSMC_D2_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D3_GPIO_PIN; 
  GPIO_Init(FSMC_D3_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D3_GPIO_PORT,FSMC_D3_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D4_GPIO_PIN; 
  GPIO_Init(FSMC_D4_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D4_GPIO_PORT,FSMC_D4_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D5_GPIO_PIN; 
  GPIO_Init(FSMC_D5_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D5_GPIO_PORT,FSMC_D5_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D6_GPIO_PIN; 
  GPIO_Init(FSMC_D6_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D6_GPIO_PORT,FSMC_D6_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D7_GPIO_PIN; 
  GPIO_Init(FSMC_D7_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D7_GPIO_PORT,FSMC_D7_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D8_GPIO_PIN; 
  GPIO_Init(FSMC_D8_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D8_GPIO_PORT,FSMC_D8_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D9_GPIO_PIN; 
  GPIO_Init(FSMC_D9_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D9_GPIO_PORT,FSMC_D9_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D10_GPIO_PIN; 
  GPIO_Init(FSMC_D10_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D10_GPIO_PORT,FSMC_D10_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D11_GPIO_PIN; 
  GPIO_Init(FSMC_D11_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D11_GPIO_PORT,FSMC_D11_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D12_GPIO_PIN; 
  GPIO_Init(FSMC_D12_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D12_GPIO_PORT,FSMC_D12_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D13_GPIO_PIN; 
  GPIO_Init(FSMC_D13_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D13_GPIO_PORT,FSMC_D13_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D14_GPIO_PIN; 
  GPIO_Init(FSMC_D14_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D14_GPIO_PORT,FSMC_D14_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_D15_GPIO_PIN; 
  GPIO_Init(FSMC_D15_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_D15_GPIO_PORT,FSMC_D15_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_InitStructure.GPIO_Pin = FSMC_WE_GPIO_PIN; 
  GPIO_Init(FSMC_WE_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_WE_GPIO_PORT,FSMC_WE_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_OE_GPIO_PIN; 
  GPIO_Init(FSMC_OE_GPIO_PORT, &GPIO_InitStructure);   
  GPIO_PinAFConfig(FSMC_OE_GPIO_PORT,FSMC_OE_GPIO_PinSource,FSMC_GPIO_AF);  
  
  GPIO_InitStructure.GPIO_Pin = FSMC_UDQM_GPIO_PIN; 
  GPIO_Init(FSMC_UDQM_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_UDQM_GPIO_PORT,FSMC_UDQM_GPIO_PinSource,FSMC_GPIO_AF);
  
  GPIO_InitStructure.GPIO_Pin = FSMC_LDQM_GPIO_PIN; 
  GPIO_Init(FSMC_LDQM_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FSMC_LDQM_GPIO_PORT,FSMC_LDQM_GPIO_PinSource,FSMC_GPIO_AF); 
}				  			

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


配置 FSMC 的模式

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

/**
  * @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,1/168M = 6ns
	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 配置表达式的配置要求把时间单位 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 配置表达式的配置要求把时间单位 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,访问这些地址时,FSMC 外设自动读 写 SRAM,程序上无需额外操作。

通过地址访问内存,最直接的就是使用 C 语言的指针方式

为方便使用,代码中首先定义了宏 Bank1_SRAM4_ADDR 表示 SRAM 的起始地址,该地址即 FSMC 映射的存储区域 Bank SRAM4 的首地址;

宏 IS62WV51216_SIZE 表示 SRAM 的大小,所以 从地址 (Bank1_SRAM4_ADDR) 到 (Bank1_SRAM3_ADDR+ IS62WV51216_SIZE) 都表示在 SRAM 的存储空间,访问这些地址,直接就能访问 SRAM。

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


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

直接指定变量存储到 SRAM 空间 每次存取数据都使用指针来访问太麻烦了,为了简化操作,可以直接指定变量存储到 SRAM 空 间。

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

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

在GUIConf.c文件里,定义数组到外部SRAM中。

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

/**
  * @brief  以“字”为单位向sdram写入数据 
  * @param  pBuffer: 指向数据的指针 
  * @param  uwWriteAddress: 要写入的SRAM内部地址
  * @param  uwBufferSize: 要写入数据大小
  * @retval None.
  */
void SRAM_WriteBuffer(uint32_t* pBuffer, uint32_t uwWriteAddress, uint32_t uwBufferSize)
{
  __IO uint32_t write_pointer = (uint32_t)uwWriteAddress;
 

  /* 循环写入数据 */
  for (; uwBufferSize != 0; uwBufferSize--) 
  {
    /* 发送数据到SRAM */
    *(uint32_t *) (Bank1_SRAM4_ADDR + write_pointer) = *pBuffer++;

    /* 地址自增*/
    write_pointer += 4;
  }
    
}

/**
  * @brief  从SRAM中读取数据 
  * @param  pBuffer: 指向存储数据的buffer
  * @param  ReadAddress: 要读取数据的地十
  * @param  uwBufferSize: 要读取的数据大小
  * @retval None.
  */
void SRAM_ReadBuffer(uint32_t* pBuffer, uint32_t uwReadAddress, uint32_t uwBufferSize)
{
  __IO uint32_t write_pointer = (uint32_t)uwReadAddress;
  
  
  /*读取数据 */
  for(; uwBufferSize != 0x00; uwBufferSize--)
  {
   *pBuffer++ = *(__IO uint32_t *)(Bank1_SRAM4_ADDR + write_pointer );
    
   /* 地址自增*/
    write_pointer += 4;
  } 
}

功能测试主函数

#include "stm32f4xx.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"  
#include "./sram/bsp_sram.h"	  
 
void Disable_LCD_CS(void);

//1.使用指针对SRAM进行读写
//2.使用绝对地址的方式访问SRAM

/*绝对定位方式访问SRAM,这种方式必须定义成全局变量*/
/*at关键字后面跟随的Bank1_SRAM4_ADDR表示一个具体的内存地址,
这个属性通常用于将变量放置在特定的内存银行或区域,例如嵌入式系统中的外部SRAM。*/
//__attribute__((at(Bank1_SRAM4_ADDR)))用于指定变量或函数的存储位置
//使用这种属性时,您需要确保指定的地址位于可用的内存范围内,并且编译器配置允许使用硬编码的地址。
//此外,使用__attribute__((at()))定义的变量通常必须在全局作用域中声明,
//因为局部变量的存储位置通常由编译器在运行时决定,不适用于硬编码地址34.
//在STM32微控制器编程中,这种技术经常用于将变量或数组放置在外部SRAM中,以便利用比内部RAM更大的存储空间。
//例如,可以通过这种方式将大型缓冲区或数据集分配到外部SRAM,以优化程序性能和资源利用率

uint8_t testValue __attribute__((at(Bank1_SRAM4_ADDR)));

/*
 * 函数名:main
 * 描述  :主函数
 * 输入  :无
 * 输出  :无
 */
int main(void)
{ 	
	LED_GPIO_Config();
	LED_BLUE;
	
	Disable_LCD_CS();//F407板 当不使用屏幕时 失能接LCD_CS的脚 防止影响
	
	/* 配置串口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)
    {

    }
}



void Delay(__IO uint32_t nCount)    //简单的延时函数
{
  for(; nCount != 0; nCount--);
}

uint8_t testValue __attribute__((at(Bank1_SRAM4_ADDR)));   

__attribute__((at(Bank1_SRAM4_ADDR))); 是一个GNU编译器工具链(GCC)的扩展属性,用于指定变量或函数的存储位置。在这里,at关键字后面跟随的Bank1_SRAM4_ADDR表示一个具体的内存地址,这个属性通常用于将变量放置在特定的内存银行或区域,例如嵌入式系统中的外部SRAM。

使用这种属性时,您需要确保指定的地址位于可用的内存范围内,并且编译器配置允许使用硬编码的地址。此外,使用__attribute__((at()))定义的变量通常必须在全局作用域中声明,因为局部变量的存储位置通常由编译器在运行时决定,不适用于硬编码地址.

在STM32微控制器编程中,这种技术经常用于将变量或数组放置在外部SRAM中,以便利用比内部RAM更大的存储空间。

例如,可以通过这种方式将大型缓冲区或数据集分配到外部SRAM,以优化程序性能和资源利用率。

接下来讲解两个实验。


实验:自动分配变量到外部 SRAM 空间

由于内存管理对应用程序非常重要,若修改 sct 文件,不使用默认配置,对工程影响非常大,容 易导致出错,所以我们使用两个实验配置来讲解 sct 文件的应用细节,希望您学习后不仅知其然 而且知其所以然,清楚地了解修改后对应用程序的影响,还可以举一反三根据自己的需求进行各 种存储器定制。

在本书前面的外部 SRAM 实验中,当我们需要读写外部 SRAM 存储的内容时,需要使用指针或 者 __attribute__((at(具体地址))) 来指定变量的位置,当有多个这样的变量时,就需要手动计算地 址空间了,非常麻烦。

在本实验中我们将修改 sct 文件,让链接器自动分配全局变量到外部 SRAM 的地址并进行管理,使得利用外部 SRAM 的空间就跟内部 SRAM 一样简单。

说起来也很奇怪啊, 野火的例程报错,并且并没有把内存分配到外部SRAM空间,而我按照野火例程修改的就可以,也达到了实验现象。


打开sct文件的方法。

为方便讲解,本实验直接使用手动编写的 sct 文件,所以

第一步 在 MDK 的“Options for Target->Linker- >Use Memory Layout from Target Dialog”选项被取消勾选,

第二步  取消勾选后可直接点击“Edit”按钮编 辑工程的 sct 文件,也可到工程目录下打开编辑,

见图使用手动编写的 sct 文件。打开sct文件的方法。

取消了这个勾选后,在 MDK 的 Target 对话框及文件配置的存储器分布选项都会失效,仅以 sct 文件中的为准,更改对话框及文件配置选项都不会影响 sct 文件的内容。


编程要点

(1) 修改启动文件,在 __main 执行之前初始化外部 SRAM;

(2) 在 sct 文件中增加外部 SRAM 空间对应的执行域;

(3) 使用节区选择语句选择要分配到外部 SRAM 的内容;

(4) 编写测试程序,编译正常后,查看 map 文件的空间分配情况。


代码修改和分析

修改 startup_stm32f40xx.s 启动文件

在前面讲解 ELF 文件格式的小节中我们了解到,芯片启动后,会通过 __main 函数调用分散加载 代码 __scatterload,分散加载代码会把存储在 FLASH 中的 RW-data 复制到 RAM 中,然后在 RAM 区开辟一块 ZI-data 的空间,并将其初始化为 0 值。因此,为了保证在程序中定义到外部 SRAM 中的变量能被正常初始化,我们需要在系统执行分散加载代码之前使外部 SRAM 存储器正常运 转,使它能够正常保存数据。

在本来的“扩展外部 SRAM”工程中,我们使用 FSMC_SRAM_Init 函数初始化外部 SRAM,且 该函数在 main 函数里才被调用,所以在外部 SRAM 正常运转之前,分散加载过程复制到外部 SRAM 中的数据都丢失了,因而在初始化外部 SRAM 之后,需要重新给变量赋值才能正常使用 (即定义变量时的初值无效,在调用外部 SRAM_Init 函数之后的赋值才有效)。

为了解决这个问题,可修改工程的 startup_stm32f40xx.s 启动文件

在原来的启动文件中我们增加了上述加粗表示的代码,增加的代码中使用到汇编语法的 IMPOR 引入在 bsp_sram.c 文件中定义的 FSMC_SRAM_Init 函数,

接着使用 LDR 指令加载函数的代码地 址到寄存器 R0,

最后使用 BLX R0 指令跳转到 FSMC_SRAM_Init 的代码地址执行。

以上代码实现了 Reset_handler 在执行 __main 函数前先调用了我们自定义的 FSMC_SRAM_Init 函 数,从而为分散加载代码准备好正常的硬件工作环境。


修改sct文件

接下来修改 sct 文件,打开方法见上图在小魔术棒的linker相关操作中打开。控制使得在 C 源文件中定义的全局变量都自动由链接器分配到外部 SRAM 中

加粗部分是本例子中增加的代码,我们从后面开始,先分析比较简单的外部 SRAM 执行域部分。

• RW_ERAM1 0x6C000000 0x00100000{}

RW_ERAM1 是我们配置的外部 SRAM 执行域。该执行域的名字是可以随便取的,最 重要的是它的基地址及空间大小,这两个值与我们实验板配置的外部 SRAM 基地址 及空间大小一致,所以该执行域会被映射到外部 SRAM 的空间。在 RW_ERAM1 执 行域内部,它使用“.ANY(+RW +ZI)”语句,选择了所有的 RW/ZI 类型的数据都分配 到这个外部 SRAM 区域,所以我们在工程中的 C 文件定义全局变量时,它都会被分 配到外部 SRAM 区域。

• RW_IRAM1 执行域

RW_IRAM1 是 STM32 内部 SRAM 的执行域。我们在默认配置中增加了“*.o(STACK) 及 stm32f4xx_rcc.o(+RW)”语句。

本来上面配置外部 SRAM 执行域后已经达到使全局 变量分配的目的,为何还要修改原内部 SRAM 的执行域呢? 这是由于我们在 __main 之前调用的 FSMC_SRAM_Init 函数调用了很多库函数,且 这些函数内部定义了一些局部变量,而函数内的局部变量是需要分配到“栈”空间 (STACK),见图 FSMC_SRAM_Init 的调用说明 ,查看静态调用图文件“SRAM.htm”可 了解它使用了多少栈空间以及调用了哪些函数。

从文件中可了解到 FSMC_SRAM_Init 使用的 STACK 的深度为 168 字节,它 调用了 FMC_NORSRAMInit、FMC_NORSRAMCmd、RCC_AHB3PeriphClockCmd 及 SRAM_GPIO_Config 函数。由于它使用了栈空间,所以在 FSMC_SRAM_Init 函数执行 之前,栈空间必须要被准备好,然而在 FSMC_SRAM_Init 函数执行之前,外部 SRAM 芯片却并未正常工作,这样的矛盾导致栈空间不能被分配到外部 SRAM。

虽然内部 SRAM 的执行域 RW_IRAM1 及 SDRAM 执行域 RW_ERAM1 中都使用 “.ANY(+RW+ZI)”语句选择了所有 RW 及 ZI 属性的内容,但对于符合两个相同选择语 句的内容,链接器会优先选择使用空间较大的执行域,即这种情况下只有当 SDRAM 执行域的空间使用完了,RW/ZI 属性的内容才会被分配到内部 SRAM。

所以在大部分情况下,内部 SRAM 执行域中的“.ANY(+RW +ZI)”语句是不起作用 的,而栈节区 (STACK) 又属于 ZI-data 类,如果我们的内部 SRAM 执行域还是按原来 的默认配置的话,栈节区会被分配到外部 SRAM,导致出错。为了避免这个问题,我 们把栈节区使用“*.o(STACK)”语句分配到内部 SRAM 的执行域。

增 加 “stm32f4xx_rcc.o(+RW)” 语 句 是 因 为 FSMC_SRAM_Init 函 数 调 用 了 stm32f4xx_rcc.c 文件中的 RCC_AHB3PeriphClockCmd 函数,而查看 map 文件后 了解到 stm32f4xx_rcc.c 定义了一些 RW-data 类型的变量,见图 RW-data 使用统计信 息 。不管这些数据是否在 FSMC_SRAM_Initt 调用过程中使用到,保险起见,我们直 接把这部分内容也分配到内部 SRAM 的执行区。


变量分配测试及结果

//定义变量到外部SRAM
uint32_t testValue  =7 ;
//定义变量到外部SRAM
uint32_t testValue2  =0;

//定义数组到外部SRAM
uint8_t testGrup[100]  ={0};
//定义数组到外部SRAM
uint8_t testGrup2[100] ={1,2,3};

在main文件中定义变量和数组

之后通过print函数将地址打印出来

printf("\r\n使用“	uint32_t inerTestValue =10; ”语句定义的局部变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&inerTestValue,inerTestValue);
	
  printf("\r\n使用“uint32_t testValue  =7 ;”语句定义的全局变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&testValue,testValue);
	
  printf("\r\n使用“uint32_t testValue2  =0 ; ”语句定义的全局变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&testValue2,testValue2);
	
	
	printf("\r\n使用“uint8_t testGrup[100]  ={0};”语句定义的全局数组:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d,%d,%d\r\n",(uint32_t)&testGrup,testGrup[0],testGrup[1],testGrup[2]);
	
  printf("\r\n使用“uint8_t testGrup2[100] ={1,2,3};”语句定义的全局数组:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d,%d,%d\r\n",(uint32_t)&testGrup2,testGrup2[0],testGrup2[1],testGrup2[2]);

串口显示结果如下

可见定义变量的地址都被转移到了0x6c开头的外部SRAM空间。

在例程中使用动态分配内存

uint32_t * pointer = (uint32_t*)malloc(sizeof(uint32_t)*3); 
	
	if(pointer != NULL)
	{
		*(pointer)=1;
		*(++pointer)=2;
		*(++pointer)=3;	
		
		printf("\r\n使用“	uint32_t *pointer = (uint32_t*)malloc(sizeof(uint32_t)*3); ”动态分配的变量\r\n");
		printf("\r\n定义后的操作为:\r\n*(pointer++)=1;\r\n*(pointer++)=2;\r\n*pointer=3;\r\n\r\n");
		printf("结果:操作后它的地址为:0x%x,查看变量值操作:\r\n",(uint32_t)pointer); 
		printf("*(pointer--)=%d, \r\n",*(pointer--));
		printf("*(pointer--)=%d, \r\n",*(pointer--));
		printf("*(pointer)=%d, \r\n",*(pointer));
		
		free(pointer);
	}
	else
	{
		printf("\r\n使用malloc动态分配变量出错!!!\r\n");	
	}

结果如下

可见他的地址也在外部SRAM。


从 map 文件中,可看到 stm32f4xx_rcc 的 RW-data 及栈空间节区 (STACK) 都被分配到了 RW_IRAM1 区域,即 STM32 的内部 SRAM 空间中;

而 main 文件中定义的 RW-data、ZI-data 以及堆空间节区 (HEAP) 都被分配到了 RW_ERAM1 区域,即我们扩展的外部 SRAM 空间中,看 起来一切都与我们的 sct 文件配置一致了。

(堆空间属于 ZI-data,由于没有像控制栈节区那样指定 到内部 SRAM,所以它被默认分配到外部 SRAM 空间了;在 main 文件中我们定义了一个初值为 0 的全局变量 testValue2 及初值为 0 的数组 testGrup[100],它们本应占用的是 104 字节的 ZI-data 空 间,但在 map 文件中却查看到它仅使用了 100 字节的 RW-data 空间,这是因为链接器把 testValue2 分配为 RW-data 类型的变量了,这是链接器本身的特性,它对像 testGrup[100] 这样的数组才优化 作为 ZI-data 分配,这不是我们 sct 文件导致的空间分配错误。)


从调试信息中可发现,变量都定义到了正确的位置,如内部变量定义在内部 SRAM 的栈区域,全 局变量定义到了外部 SRAM 的区域。

经过野火大哥的测试,曾出现过堆空间分配错误的情况,该情况下即使在 sct 文件中使用“*.o(HEAP)” 语句指定堆区到内部 SRAM 或外部 SRAM 区域,都无法正常使用 malloc 分配空间。另外,由 于外部 SRAM 的读写速度比内部 SRAM 的速度慢,所以我们更希望默认定义的变量先使用内部 SRAM,当它的空间使用完毕后再把变量分配到外部 SRAM。


实验:优先使用内部 SRAM 并把堆区分配到外部 SRAM

本实验使用另一种方案配置 sct 文件,使得默认情况下优先使用内部 SRAM 空间,在需要的时候 使用一个关键字指定变量存储到外部 SRAM,另外,我们还把系统默认的堆空间 (HEAP) 映射到 外部 SRAM,从而可以使用 C 语言标准库的 malloc 函数动态从外部 SRAM 中分配空间,利用标 准库进行外部 SRAM 的空间内存管理。

软件设计

本工程只使 用手动编辑的 sct 文件配置,不使用 MDK 选项配置,在“Options for Target->linker”的选项见图 使用手动编写的 sct 文件。

取消了这个默认的“Use Memory Layout from Target Dialog”勾选后,在 MDK 的 Target 对话框及 文件配置的存储器分布选项都会失效,仅以 sct 文件中的为准,更改对话框及文件配置选项都不 会影响 sct 文件的内容。


编程要点

(1) 修改启动文件,在 __main 执行之前初始化外部 SRAM;

(2) 在 sct 文件中增加外部 SRAM 空间对应的执行域;

(3) 在外部 SRAM 中的执行域中选择一个自定义节区“EXRAM”;

(4) 使用 __attribute__ 关键字指定变量分配到节区“EXRAM”;

(5) 使用宏封装 __attribute__ 关键字,简化变量定义;

(6) 根据需要,把堆区分配到内部 SRAM 或外部 SRAM 中;

(7) 编写测试程序,编译正常后,查看 map 文件的空间分配情况。


代码修改和分析

修改 startup_stm32f40xx.s 启动文件

在 __main 之前初始化外部 SRAM 同样地,为了使定义到外部 SRAM 的变量能被正常初始化,需要修改工程 startup_stm32f40xx.s 启动文件中的 Reset_handler 函数,在 __main 函数之前调用 FSMC_SRAM_Init 函数使外部 SRAM 硬件正常运转,和上一个实验一样。

当芯片上电运行 Reset_handler 函数时,在执行 __main 函数前先调 用了我们自定义的 FSMC_SRAM_Init 函数,从而为分散加载代码准备好正常的硬件工作环境。


sct 文件配置

接下来分析本实验中的 sct 文件配置与上一小节有什么差异

本实验的 sct 文件中对内部 SRAM 的执行域保留了默认配置,没有作任何改动,新增了一个 外部 SRAM 的执行域,并且使用了“*.o(HEAP)”语句把堆区分配到了外部 SRAM 空间,使用 “.ANY(EXRAM)”语句把名为“EXRAM”的节区分配到外部 SRAM 空间。

这个“EXRAM”节区是由我们自定义的,在语法上就跟在 C 文件中定义全局变量类似,只要它 跟工程中的其它原有节区名不一样即可。有了这个节区选择配置,当我们需要定义变量到外部 SRAM 时,只需要指定该变量分配到该节区,它就会被分配到外部 SRAM 空间。

本实验中的 sct 配置就是这么简单,接下来直接使用就可以了。


指定变量分配到节区

当我们需要把变量分配到外部 SRAM 时,需要使用 __attribute__ 关键字指定节区,它的语法见代 码。

上述代码介绍了基本的指定节区语法:“变量定义 __attribute__ ((section (“节区名”))) = 变量 值;”,它的主体跟普通的 C 语言变量定义语法无异,在赋值“=”号前 (可以不赋初值),加了个 “__attribute__ ((section(“节区名”)))”描述它要分配到的节区。

本例中的节区名为“EXRAM”,即 我们在 sct 文件中选择分配到外部 SRAM 执行域的节区,所以该变量就被分配到外部 SRAM 中 了。

由于“__attribute__”关键字写起来比较繁琐,我们可以使用宏定义把它封装起来,简化代码。 本例中我们把指定到“EXRAM”的描述语句“__attribute__ ((section(“EXRAM”)))”封装成了 宏“__EXRAM”,应用时只需要使用宏的名字替换原来“__attribute__”关键字的位置即可,如 “uint32_t testValue __EXRAM =7 ;”。有 51 单片机使用经验的读者会发现,这种变量定义方法就跟使用 keil51 特有的关键字“xdata”定义变量到外部 RAM 空间差不多。

类似地,如果工程中还使用了其它存储器也可以用这样的方法实现变量分配,例如 STM32 的高 速内部 SRAM(CCM),可以在 sct 文件增加该高速 SRAM 的执行域,然后在执行域中选择一个自 定义节区,在工程源文件中使用“__attribute__”关键字指定变量到该节区,就可以可把变量分配 到高速内部 SRAM 了。

根据我们 sct 文件的配置,如果定义变量时没有指定节区,它会默认优先使用内部 SRAM,把变量 定义到内部 SRAM 空间,而且由于局部变量是属于栈节区 (STACK),它不能使用“__attribute__” 关键字指定节区。在本例中的栈节区被分配到内部 SRAM 空间。


变量分配测试及结果

//设置变量定义到“EXRAM”节区的宏
#define __EXRAM  __attribute__ ((section ("EXRAM")))


//定义变量到外部SRAM
uint32_t testValue __EXRAM =7 ;
//上述语句等效于:
//uint32_t testValue  __attribute__ ((section ("EXRAM"))) =7 ;

//定义变量到SRAM
uint32_t testValue2  =7 ;



//定义数组到外部SRAM
uint8_t testGrup[3] __EXRAM ={1,2,3};
//定义数组到SRAM
uint8_t testGrup2[3] ={1,2,3};

查看地址

	printf ( "\r\n野火外部 SRAM 优先使用内部 SRAM 并把堆区分配到外部 SRAM测试\r\n" );
	printf("\r\nSCT文件应用——自动分配变量到外部SRAM实验\r\n");
  
	printf("\r\n使用“	uint32_t inerTestValue =10; ”语句定义的局部变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&inerTestValue,inerTestValue);
	
  printf("\r\n使用“uint32_t testValue __EXRAM =7 ;”语句定义的全局变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&testValue,testValue);
	
  printf("\r\n使用“uint32_t testValue2  =7 ; ”语句定义的全局变量:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d\r\n",(uint32_t)&testValue2,testValue2);
	
	
	printf("\r\n使用“uint8_t testGrup[3] __EXRAM ={1,2,3};”语句定义的全局数组:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d,%d,%d\r\n",(uint32_t)&testGrup,testGrup[0],testGrup[1],testGrup[2]);
	
  printf("\r\n使用“uint8_t testGrup2[3] ={1,2,3};”语句定义的全局数组:\r\n");
	printf("结果:它的地址为:0x%x,变量值为:%d,%d,%d\r\n",(uint32_t)&testGrup2,testGrup2[0],testGrup2[1],testGrup2[2]);
	
	
	/*使用malloc从外部SRAM中分配空间*/
	uint32_t *pointer = (uint32_t*)malloc(sizeof(uint32_t)*3); 	

	if(pointer != NULL)
	{
		*(pointer)=1;
		*(++pointer)=2;
		*(++pointer)=3;	
		
		printf("\r\n使用“	uint32_t *pointer = (uint32_t*)malloc(sizeof(uint32_t)*3); ”动态分配的变量\r\n");
		printf("\r\n定义后的操作为:\r\n*(pointer++)=1;\r\n*(pointer++)=2;\r\n*pointer=3;\r\n\r\n");
		printf("结果:操作后它的地址为:0x%x,查看变量值操作:\r\n",(uint32_t)pointer); 
		printf("*(pointer--)=%d, \r\n",*(pointer--));
		printf("*(pointer--)=%d, \r\n",*(pointer--));
		printf("*(pointer)=%d, \r\n",*(pointer));
		
		free(pointer);
	}
	else
	{
		printf("\r\n使用malloc动态分配变量出错!!!\r\n");	
	}

串口助手打印结果

下面的多种形式访问SRAM是外部SRAM自带的。

从调试信息中可发现,实际运行结果也完全正常,本实验中的 sct 文件配置达到了优先分配变量 到内部 SRAM 的目的,而且堆区也能使用 malloc 函数正常分配空间。


代码中定义了普通变量、指定到 EXRAM 节区的变量并使用动态分配内存,还把它们的值和地址 通过串口打印到上位机,通过这些变量,我们可以检查变量是否能正常分配。

构建工程后,查看工程的 map 文件观察变量的分配情况,见图在 map 文件中查看工程的存储分 布。

从 map 文件中可看到普通变量及栈节区都被分配到了内部 SRAM 的地址区域,而指定到 EXRAM 节区的变量及堆空间都被分配到了外部 SRAM 的地址区域,与我们的要求一致。

再把程序下载到实验板进行测试,串口打印的调试信息如图空间分配实验实测结果。

本实验中的 sct 文件配置方案完全可以应用到您的实际工程项目中,下面再进一步强调其它应用 细节。


使用 malloc 和 free 管理外部 SRAM 的空间

外部 SRAM 的内存空间非常大,为了管理这些空间,一些工程师会自己定义内存分配函数来管 理外部 SRAM 空间,这些分配过程本质上就是从外部 SRAM 中动态分配内存。

从本实验中可了 解到我们完全可以直接使用 C 标准库的 malloc 从外部 SRAM 中分配空间,只要在前面配置的基 础上修改启动文件中的堆顶地址限制即可。

C 标准库的 malloc 函数是根据 __heap_base 及 __heap_limit 地址限制分配空间的,在以上的代码 定义中,堆基地址 __heap_base 的由链接器自动分配未使用的基地址,而堆顶地址 __heap_limit 则 被定义为外部 SRAM 的最高地址 0x6c000000+0x00100000(使用这种定义方式定义的 __heap_limit 值与 Heap_Size 定义的大小无关),经过这样配置之后,外部 SRAM 内除 EXRAM 节区外的空间 都被分配为堆区,所以 malloc 函数可以管理剩余的所有外部 SRAM 空间。修改后,它生成的 map 文件信息见图使用 malloc 管理剩余外部 SRAM 空间。

可看到 __heap_base 的地址紧跟在 EXRAM 之后,__heap_limit 指向了外部 SRAM 的最高地址, 因此 malloc 函数就能利用所有外部 SRAM 的剩余空间了。注意图中显示的 HEAP 节区大小为 0x00000200 字节,修改启动文件中的 Heap_Size 大小可以改变该值,它的大小是不会影响 malloc 函 数使用的,malloc 实际可用的就是 __heap_base 与 __heap_limit 之间的空间。至于如何使 Heap_Size 的值能自动根据 __heap_limit 与 __heap_base 的值自动生成,我还没找到方法,若您了解,请告知。


把堆区分配到内部 SRAM 空间

若您希望堆区 (HEAP) 按照默认配置,使它还是分配到内部 SRAM 空间,只要把“*.o(HEAP)”选 择语句从外部 SRAM 的执行域删除掉即可,堆节区就会默认分配到内部 SRAM,外部 SRAM 仅 选择 EXRAM 节区的内容进行分配,见代码清单 ,若您更改了启动文件中堆的默认配 置,主注意空间地址的匹配。


屏蔽链接过程的 warning(屏蔽警告的方法)

在我们的实验配置的 sct 文件中使用了“*.o(HEAP)”语句选择堆区,但有时我们的工程完全没有 使用堆 (如整个工程都没有使用 malloc),这时链接器会把堆占用的空间删除,构建工程后会输出 警告提示该语句仅匹配到无用节区,见图仅匹配到无用节区的 warning。

这并无什么大碍,但强迫症患者不希望看到 warning,可以在“Options for Target->Linker->disable Warnings”中输入 warning 号屏蔽它。warning 号可在提示信息中找到,如上图提示信息中“warning: L6329W”表示它的 warning 号为 6329,把它输入到图屏蔽链接过程的 warning 中的对话框中即 可。


如何把栈空间也分配到外部 SRAM

前 面 提 到 因 为 FSMC_SRAM_Init 初 始 化 函 数 本 身 使 用 了 栈 空 间 (STACK), 而 在 执 行 FSMC_SRAM_Init 函数之前外部 SRAM 并未正常工作,这样的矛盾导致无法把栈分配到外部 SRAM。其实换一个思路,只要我们的外部 SRAM 初始化过程不使用栈空间,外部 SRAM 正常 运行后栈才被分配到外部 SRAM 空间,这样就没有问题了。

由于原来的 FSMC_SRAM_Init 实现的外部 SRAM 初始化过程使用了 STM32 标准库函数,它不可 避免地使用了栈空间 (定义了局部变量),要完全不使用栈空间完成外部 SRAM 的初始化,只能 使用纯粹的寄存器方式配置。在 STM32 标准库的“system_stm32f4xx.c”文件已经给出了类似的 解决方案,SystemInit_ExtMemCtl 函数,

#elif defined (DATA_IN_ExtSDRAM)
/**
  * @brief  Setup the external memory controller.
  *         Called in startup_stm32f4xx.s before jump to main.
  *         This function configures the external SDRAM mounted on STM324x9I_EVAL board
  *         This SDRAM will be used as program data memory (including heap and stack).
  * @param  None
  * @retval None
  */
void SystemInit_ExtMemCtl(void)
{
  register uint32_t tmpreg = 0, timeout = 0xFFFF;
  register uint32_t index;

  /* Enable GPIOC, GPIOD, GPIOE, GPIOF, GPIOG, GPIOH and GPIOI interface 
      clock */
  RCC->AHB1ENR |= 0x000001FC;
  
  /* Connect PCx pins to FMC Alternate function */
  GPIOC->AFR[0]  = 0x0000000c;
  GPIOC->AFR[1]  = 0x00007700;
  /* Configure PCx pins in Alternate function mode */  
  GPIOC->MODER   = 0x00a00002;
  /* Configure PCx pins speed to 50 MHz */  
  GPIOC->OSPEEDR = 0x00a00002;
  /* Configure PCx pins Output type to push-pull */  
  GPIOC->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PCx pins */ 
  GPIOC->PUPDR   = 0x00500000;
  
  /* Connect PDx pins to FMC Alternate function */
  GPIOD->AFR[0]  = 0x000000CC;
  GPIOD->AFR[1]  = 0xCC000CCC;
  /* Configure PDx pins in Alternate function mode */  
  GPIOD->MODER   = 0xA02A000A;
  /* Configure PDx pins speed to 50 MHz */  
  GPIOD->OSPEEDR = 0xA02A000A;
  /* Configure PDx pins Output type to push-pull */  
  GPIOD->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PDx pins */ 
  GPIOD->PUPDR   = 0x00000000;

  /* Connect PEx pins to FMC Alternate function */
  GPIOE->AFR[0]  = 0xC00000CC;
  GPIOE->AFR[1]  = 0xCCCCCCCC;
  /* Configure PEx pins in Alternate function mode */ 
  GPIOE->MODER   = 0xAAAA800A;
  /* Configure PEx pins speed to 50 MHz */ 
  GPIOE->OSPEEDR = 0xAAAA800A;
  /* Configure PEx pins Output type to push-pull */  
  GPIOE->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PEx pins */ 
  GPIOE->PUPDR   = 0x00000000;

  /* Connect PFx pins to FMC Alternate function */
  GPIOF->AFR[0]  = 0xcccccccc;
  GPIOF->AFR[1]  = 0xcccccccc;
  /* Configure PFx pins in Alternate function mode */   
  GPIOF->MODER   = 0xAA800AAA;
  /* Configure PFx pins speed to 50 MHz */ 
  GPIOF->OSPEEDR = 0xAA800AAA;
  /* Configure PFx pins Output type to push-pull */  
  GPIOF->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PFx pins */ 
  GPIOF->PUPDR   = 0x00000000;

  /* Connect PGx pins to FMC Alternate function */
  GPIOG->AFR[0]  = 0xcccccccc;
  GPIOG->AFR[1]  = 0xcccccccc;
  /* Configure PGx pins in Alternate function mode */ 
  GPIOG->MODER   = 0xaaaaaaaa;
  /* Configure PGx pins speed to 50 MHz */ 
  GPIOG->OSPEEDR = 0xaaaaaaaa;
  /* Configure PGx pins Output type to push-pull */  
  GPIOG->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PGx pins */ 
  GPIOG->PUPDR   = 0x00000000;
  
  /* Connect PHx pins to FMC Alternate function */
  GPIOH->AFR[0]  = 0x00C0CC00;
  GPIOH->AFR[1]  = 0xCCCCCCCC;
  /* Configure PHx pins in Alternate function mode */ 
  GPIOH->MODER   = 0xAAAA08A0;
  /* Configure PHx pins speed to 50 MHz */ 
  GPIOH->OSPEEDR = 0xAAAA08A0;
  /* Configure PHx pins Output type to push-pull */  
  GPIOH->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PHx pins */ 
  GPIOH->PUPDR   = 0x00000000;
  
  /* Connect PIx pins to FMC Alternate function */
  GPIOI->AFR[0]  = 0xCCCCCCCC;
  GPIOI->AFR[1]  = 0x00000CC0;
  /* Configure PIx pins in Alternate function mode */ 
  GPIOI->MODER   = 0x0028AAAA;
  /* Configure PIx pins speed to 50 MHz */ 
  GPIOI->OSPEEDR = 0x0028AAAA;
  /* Configure PIx pins Output type to push-pull */  
  GPIOI->OTYPER  = 0x00000000;
  /* No pull-up, pull-down for PIx pins */ 
  GPIOI->PUPDR   = 0x00000000;
  
/*-- FMC Configuration ------------------------------------------------------*/
  /* Enable the FMC interface clock */
  RCC->AHB3ENR |= 0x00000001;
  
  /* Configure and enable SDRAM bank1 */
  FMC_Bank5_6->SDCR[0] = 0x000039D0;
  FMC_Bank5_6->SDTR[0] = 0x01115351;      
  
  /* SDRAM initialization sequence */
  /* Clock enable command */
  FMC_Bank5_6->SDCMR = 0x00000011; 
  tmpreg = FMC_Bank5_6->SDSR & 0x00000020; 
  while((tmpreg != 0) & (timeout-- > 0))
  {
    tmpreg = FMC_Bank5_6->SDSR & 0x00000020; 
  }
  
  /* Delay */
  for (index = 0; index<1000; index++);
  
  /* PALL command */
  FMC_Bank5_6->SDCMR = 0x00000012;           
  timeout = 0xFFFF;
  while((tmpreg != 0) & (timeout-- > 0))
  {
  tmpreg = FMC_Bank5_6->SDSR & 0x00000020; 
  }
  
  /* Auto refresh command */
  FMC_Bank5_6->SDCMR = 0x00000073;
  timeout = 0xFFFF;
  while((tmpreg != 0) & (timeout-- > 0))
  {
  tmpreg = FMC_Bank5_6->SDSR & 0x00000020; 
  }
 
  /* MRD register program */
  FMC_Bank5_6->SDCMR = 0x00046014;
  timeout = 0xFFFF;
  while((tmpreg != 0) & (timeout-- > 0))
  {
  tmpreg = FMC_Bank5_6->SDSR & 0x00000020; 
  } 
  
  /* Set refresh count */
  tmpreg = FMC_Bank5_6->SDRTR;
  FMC_Bank5_6->SDRTR = (tmpreg | (0x0000027C<<1));
  
  /* Disable write protection */
  tmpreg = FMC_Bank5_6->SDCR[0]; 
  FMC_Bank5_6->SDCR[0] = (tmpreg & 0xFFFFFDFF);
  
/*
  Bank1_SDRAM is configured as follow:

  FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;      
  FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 6;  
  FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;        
  FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 6;         
  FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;      
  FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;                
  FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;               

  FMC_SDRAMInitStructure.FMC_Bank = SDRAM_BANK;
  FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
  FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_11b;
  FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = FMC_SDMemory_Width_16b;
  FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4;
  FMC_SDRAMInitStructure.FMC_CASLatency = FMC_CAS_Latency_3; 
  FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable;
  FMC_SDRAMInitStructure.FMC_SDClockPeriod = FMC_SDClock_Period_2;
  FMC_SDRAMInitStructure.FMC_ReadBurst = FMC_Read_Burst_disable;
  FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_1;
  FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure;
*/
  
}

该函数没有使用栈空间,仅使用 register 关键字定义了两个分配到内核寄存器的变量,其余配置 均通过直接操作寄存器完成。这个函数针对 ST 的一个官方评估编写的,在其它硬件平台直接使 用可能有错误,若有需要可仔细分析它的代码再根据自己的硬件平台进行修改。

这个函数是使用条件编译语句“#elif defined (DATA_IN_ExtSRAM)”包装起来的,默认情况下这 个函数并不会被编译,需要使用这个函数时只要定义这个宏即可。

定义了 DATA_IN_ExtSRAM 宏之后,SystemInit_ExtMemCtl 函数会被 SystemInit 函数调用,见代 码清单:MDK-33 ,而我们又知道 SystemInit 会在启动文件中的 Reset_handler 函数中执行。

所以,如果希望把栈空间分配到外部 SRAM,可按以下步骤操作:

• 修改 sct 文件,使用“*.o(STACK)”语句把栈空间分配到外部 SRAM 的执行域;

• 根据自己的硬件平台,修改 SystemInit_ExtMemCtl 函数,该函数要实现外部 SRAM 的初始 化过程,且该函数不能使用栈空间;

• 定义 DATA_IN_ExtSRAM 宏,从而使得 SystemInit_ExtMemCtl 函数被加进编译,并被 SystemInit 调用;

• 由于 Reset_handler 默认会调用 SystemInit 函数执行,所以不需要修改启动文件。


以上为本文章全部内容,关于内存篇章,还剩下对内部flash的读写操作,主要是为了实现代码更新,也就是boot loader。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;