FMC——扩展外部SDRAM

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

市场上SDRAM的价格普遍比SRAM性价比要高一些,所以正常电脑扩展的内存,那些内存条都是由SDRAM集成的。

给STM32芯片扩展内存与给PC扩展内存的原理是一样的,只是PC上一般以内存条的形式扩展,内存条实质是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块,而STM32直接与SDRAM芯片连接。

一、SDRAM控制原理

下面以型号IS2-45S16400J的SDRAM芯片为例:

 

(1)引脚

#代表低电平有效。

信号线类型说明
CLKI同步时钟信号,所有输入信号都在CLK为上升沿的时候被采集
CKEI时钟使能信号,禁止时钟信号时SDRAM会启动自刷新操作(低功耗模式 )
CS#I片选信号,低电平有效
CAS#I列地址选通,为低电平时地址线表示的是列地址
RAS#I行地址选通,为低电平时地址线表示的是行地址
WE#I写入使能,低电平有效
DQM[0:1]I数据输入/输出掩码信号,表示DQ信号线的有效部分,取高8位或者低8位。
BA[0:1]IBank地址输入,选择要控制的Bank
A[0:11]I地址输入(行、列)
DQ[0:15]I/O数据输入输出信号

SDRAM芯片的内部功能框架:

 

在文章存储器介绍中有提及到,SDRAM采用的是同步方式进行采样。

(2)控制逻辑

SDRAM内部的“控制逻辑”指挥着整个系统的运行,外部可通过CS、WE、CAS、RAS以及地址线来向控制逻辑输入命令,命令经过“命令器译码器”,译码,并将控制参数保存到“模式寄存器中”,控制逻辑依次运行。

(3)SDRAM的存储阵列

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

 

 和表格查找一样, 指定一个行地址和列地址 ,就可以精确地找到目标单元格,这是SDRAM寻址的基本原理。这样的每个单元格都被称为存储单元,而这样的表则被称为存储阵列(Bank),目前设计的SDRAM芯片基本上内部都包含有4个这样的Bank,寻址时指定Bank号以及行地址,然后再指定列地址即可寻找到目标存储单元。一定要先指定行地址,因为SDRAM先指定行地址后,会对本行进行预刷新的功能!!!

该SDRAM的内部存储阵列结构如下:也就是内部框架功能图的第四部分

通讯时当RAS线为低电平,则“行地址选通器”被选通,地址A[12:0]表示的地址会被输入到"行地址译码及锁存器"中,作为存储阵列中选定的行地址,同时地址线BS[1:0]表示的Bank也被锁存,选中了要操作的Bank号;接着控制CAS线为低电平,“列地址选通器”被选通,地址线A[12:0]表示地址会被锁存到“列地址译码器”中作为列地址,完成寻址过程。

 

 

以上的功能都使用FMC来实现。综上所述,SDRAM包含有“A”以及“BA”两类地址线,A类地址线是行(ROW)与列(COLUMM)共用的地址总线,也就是功能框图第一步部分的A0-A11引脚,BA地址线是独立的用于指定SDRAM内部存储阵列号(Bank)。在命令模式下,A类地址线还用于某些命令输入参数。

(4)数据输入输出

也就是功能框图的第五部分,SDRAM的数据进行数据通讯时,16位的数据是同步传输的,但实际应用中我们可能会以8位、16位的宽度存取数据,也就是说16位的数据线并不是所有时候都同时使用,而且在传输低宽度数据的时候,我们不希望其他数据线表示的数据被录入,这时使用DQM信号线作为掩码信号,控制要读取哪个字节。

(5)SDRAM命令

  1. 命令禁止(COMMAND INHIBIT):只要CS引脚为高电平,则用于禁止SDRAM执行的新命令,但它不能停止当前正在执行的命令。

  2. 空操作(NO OPERATION):命令禁止的反操作,用于选中SDRAM,以便接下来发送命令。防止干扰。

  3. 行有效(ACTIVE):进行存储单元寻址时,需要先选中访问的BANK和行,使它处于激活状态。发送行有效命令时,RAS线为低电平,同时通过BA线以及A线发送Bank地址和行地址。

  4. 列读写(READ):“读命令”(READ)和“写命令”(WRITE)的时序很相似,通过共用的地址线A发送列地址,同时使用WE引脚表示读/写方向,WE为低电平时表示写,高电平时表示读。数据读写时,使用DQM线表示有效的DQ数据线

  5. 预充电(PRECHARGE):SDRAM的寻址具有独占性,所以在进行读写操作后,如果要对同一个Bank的另一行进行寻址,就要将原来有效(ACTIVE)的行关闭,重新发送行/列地址。Bank关闭当前工作行,准备打开新行的操作就是预充电。配合使用A10线控制,若A10为高电平时,所有Bank都预充电;A10为低电平时,使用BA线选择要预充电的Bank。

  6. 刷新(AUTO REFRESH or SELF REFRESH):SDRAM要不断进行刷新(Refresh)才能保留住数据,因此它是DRAM最重要的操作。刷新操作与预充电中重写的操作本质是一样的。但因为预充电是对一个或所有Bank中的工作行操作,并且不定期,而刷新则是有固定的周期,依次对所有行进行操作,以保证那些久久没有被访问的存储单元数据正确。刷新操作分为两类:自动刷新(Auto Refresh)自我刷新(Self Refresh),发送命令后CKE时钟为有效时(低电平),使用自动刷新操作,否则使用自我刷新操作。不论是何种刷新方式,都不需要外部提供地址信息,因为这是一个内部的自动操作。

    • 自动刷新:需要在CLK的驱动下

      对于自动刷新,SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址,每收到一次命令刷新一行。在刷新过程中,所有Bank都停止工作,而每次刷新所占用的时间为N个时钟周期(视SDRAM型号而定,通常为N=9),刷新结束之后才可以进入正常的工作状态,也就是说在这N个时钟周期内,所有工作指令只能等待而无法执行。一次次地按行刷新,刷新玩所有行后,将再次对第一行重新进行刷新操作,这个对同一行刷新操作的时间间隔,称为SDRAM的刷新周期,通常为64ms。显然刷新会对SDRAM的性能造成影响,但这是它的DRAM的特性决定的,也是DRAM相对于SRAM取得成本优势的同时所付出的代价。

    • 自我刷新:不需要在CLK的驱动下

      自我刷新则主要用于休眠模式低功耗状态下的数据保存,也就是说即使外部控制器不工作了,SDRAM都能自己确保数据正常。当CKE置于无效状态(低电平),就进入了自我刷新模式,此时不再依靠外部时钟工作,而是根据SDRAM内部的时钟进行刷新操作。在自我刷新期间除了CKE之外的所有外部信号都是无效的,只有重新使CKE有效才能退出模式并且进入正常的工作状态。

  7. 加载模式寄存器

 

  • Burst Length

    Burst Length译为突发长度,下面简称BL。突发是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。

  • BT

    模式寄存器中的BT位用于设置突发模式,突发模式分为顺序(Sequential)间隔(Interleaved)两种。在顺序方式中,操作按地址的顺序连续执行,如果是间隔模式,则操作地址是跳跃的。

  • CASLatency

    模式寄存器中的CASLatency是指列地址选通延迟,简称CL。在发出读命令(命令同时包含列地址)后,需要等待几个时钟周期数据线DQ才会输出有效数据,这之间的时钟周期就是指CL,CL一般可以设置为2或3个时钟周期。具体配置需要参考SDRAM的数据手册

 

 

  • OP Mode

    OP Mode指Operating Mode,SDRAM的工作模式。当它被配置为“00”的时候表示工作在正常模式,其它值是测试模式或被保留的设定。实际使用时必须配置成正常模式。

  • WB

    WB用于配置写操作的突发特性,可选择使用BL设置的突发长度或非突发模式

  • Reserved

    后三位被保留。

 

命令名CS#RAS#CAS#WE#DQMADDRDQ
COMMAND INHIBITHXXXXXX
NO OPERATIONLHHHXXX
ACTIVELLHHXBank/rowX
READLHLHL/HBank/colX
WRITELHLLL/HBank/colValid
PRECHARGELLHLXCodeX
AUTO REFRESH or SELF REFRESHLLLHXXX
LOAD MODE REGISTERLLLLXOp-codeX
BURST TERMINATELHHLXXactive

(6)SDRAM初始化流程

初始化需要对存储矩阵进行预充电、刷新并设置模式寄存器。

 

具体流程如下:

  • 给SDRAM上电,并提供稳定的时钟,至少100us;

  • 发送“空操作”(NOP)命令;

  • 发送“预充电”(PRECHARGE)命令,控制所有Bank进行预充电,并等待tRP时间, tRP表示预充电与其它命令之间的延迟;

  • 发送至少2个“自动刷新”(AUTO REFRESH)命令,每个命令后需等待tRFC时间,tRFC表示自动刷新时间;

  • 发送“加载模式寄存器”(LOAD MODE REGISTER)命令,配置SDRAM的工作参数,并等待tMRD时间,tMRD表示加载模式寄存器命令与行有行或刷新命令之间的延迟;

  • 初始化流程完毕,可以开始读写数据。

(7)SDRAM读写流程

初始化结束后即可读写:

读时序:

 写时序:

  • 发送“行有效”(ACTIVE)命令,发送命令的同时包含行地址和Bank地址,然后等待tRCD时间,tRCD表示行有效命令与读/写命令之间的延迟;

  • 发送“读/写”(READ/WRITE)命令,在发送命令的同时发送列地址,完成寻址的地址输入。对于读命令,根据模式寄存器的CL定义,延迟CL个时钟周期后,SDRAM的数据线DQ才输出有效数据,而写命令是没有CL延迟的,主机在发送写命令的同时就可以把要写入的数据用DQ输入到SDRAM中,这是读命令与写命令的时序最主要的区别。图中的读/写命令都通过地址线A10控制自动预充电,而SDRAM接收到带预充电要求的读/写命令后,并不会立即预充电,而是等待tWR时间才开始,tWR表示写命令与预充电之间的延迟;

  • 执行“预充电”(auto precharge)命令后,需要等待tRP时间,tRP表示预充电与其它命令之间的延迟;这个是拿来寻址下一行的操作。

  • 图中的标号4处的tRAS,表示自刷新周期,即在前一个“行有效”与 “预充电”命令之间的时间;需要很严谨的控制内存的刷新周期。

  • 发送第二次“行有效”(ACTIVE)命令准备读写下一个数据,在图中的标号5处的tRC,表示两个行有效命令或两个刷新命令之间的延迟。

二、STM32——FMC功能框图

STM32F429使用FMC外设来管理扩展的存储器,FMC是Flexible Memory Controller的缩写,译为可变存储控制器(相当于引出地址总线)。它可以用于驱动包括SRAM、SDRAM、NOR FLASH以及NAND FLASH类型的存储器。在其他系列的STM32中不具备此功能,只有FSMC控制器,译为可变静态存储控制器,他们不能驱动SDRAM这样的动态存储器,因为驱动SDRAM需要定时刷新,只有STM32F429的FMC外设才支持此功能。

FMC功能框图:

 

 

1-通讯引脚 2-存储器控制引脚 3-时钟控制引脚

(1) 通讯引脚

 

FMC引脚名称对应SDRAM说明
FMC_NBL[3:0]DQM[3:0]数据掩码信号
FMC_A[12:0]A[12:0]行/列地址线
FMC_A[15:14]BA[1:0]Bank地址线
FMC_D[31:0]DQ[31:0]数据线
FMC_SDCLKCLK同步时钟信号
FMC_SDNWEWE#写入使能
FMC_SDCKE[1:0]CKESDCKE0:SDRAM 存储区域 1 时钟使能 SDCKE1:SDRAM 存储区域 2 时钟使能
FMC_SDNE[1:0]--SDNE0:SDRAM 存储区域 1 芯片使能 SDNE1:SDRAM 存储区域 2 芯片使能
FMC_NRASRAS#行地址选通信号
FMC_NCASCAS#列地址选通信号

其中FMC_SDCKE与FMC_SDNE需要一一对应。他们用于控制STM32使用不同的存储区域驱动SDRAM,使用编号0的信号线组会使用STM32的存储器区域1,使用编号1的信号线组会使用STM32的存储器区域2。使用不同存储区域时,STM32访问SDRAM的地址不一样。

(2)存储器控制器

区分:

  • NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器

  • SDRAM存储器使用独立的控制器。

  • 不同的控制器有专用的寄存器用于配置其工作模式。

控制SDRAM的有:

  • FMC_SDCR1/FMC_SDCR2控制寄存器(分别存储于存储区域1和存储区域2)

  • FMC_SDTR1/FMC_SDTR2时序寄存器(分别存储于存储区域1和存储区域2)

  • FMC_SDCMR命令寄存器

  • FMC_SDRTR刷新定时器寄存器

FMC_SDCR1/2:

可配置SDCLK的同步时钟频率、突发读使能、写保护、CAS延迟、行列地址位数及数据总线宽度等。

 

FMC_SDTR1/2:

结合SDRAM手册中的延迟时间要求进行延迟,要注意转换单位。

时序寄存器用于配置SDRAM访问时的各种时间延迟(延迟多少个时钟周期),如TRP行预充电延迟、TMRD加载模式寄存器激活延迟等。

 

其中:

TRCD:行到列延迟

TRP:行预充电延迟

TWR:恢复延迟

TRC:行循环延迟

TRAS:自刷新延迟

TXSR:退出自刷新延迟

TMRD:加载模式寄存器到激活

FMC_SDCMR:

命令模式寄存器用于存储要发送到SDRAM模式寄存器的配置,以及要向SDRAM芯片发送的命令。

 FMC_SDRTR:

用于配置SDRAM的自动刷新周期。

 

我们使用的SDRAM所需要刷新的周期为64ms,所以刷新速率为64ms/4096行 = 1.56us。之后用这个时间乘以我们的时钟频率1.56us*60MHz=936。这个时候得到了内部刷新请求后,我们必须将刷新速率减去20,得到916对应到COUNT值“001110101000”。

将得到的1位字段加载到使用SDRAM时钟递减的定时器。此定时器在计数到0时生成刷新脉冲。COUNT值必须设置为至少41个SDRAM时钟周期。

 

 

一旦完成对 FMC_SDRTR 寄存器的编程,定时器就开始计数。如果寄存器中编程的值为 “0”,则不会执行刷新。切不可在初始化后重新编程该寄存器以避免刷新速率被修改。每次要生成刷新脉冲时,都会重新将该13位COUNT字段加载到计数器中。

在存储器访问和自动刷新请 求同时出现时,则优先处理自动刷新请求。如果在刷新期间访问存储器,则会缓存访问请求 并在刷新完成后进行处理。

我们要使用FMC时,就需要配置以上几个寄存器。

(3)时序控制逻辑

FMC外设挂载在AHB3总线上,时钟信号来自于HCLK(默认180MHz),控制器的时钟输出就是由它分频得到。如SDRAM控制器的FMC_SDCLK引脚输出的时钟,是用于与SDRAM芯片进行同步通讯,它的时钟频率可通过FMC_SDCR1寄存器的SDCLK位配置,可以配置为HCLK的1/2或1/3,也就是说,与SDRAM通讯的同步时钟最高频率为90MHz。

三、FMC地址映射

 

  • 使用FMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针(图中的虚线),然后就可以通过指针直接修改该存储单元的内容,FMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。

  • FMC把SDRAM的存储区域分成了Bank1和Bank2两块,每个Bank有不一样的起始地址,且有独立的FMC_SDCR控制寄存器和FMC_SDTR时序寄存器,还有独立的FMC_SDCKE时钟使能信号线和FMC_SDCLK信号线。FMC_SDCKE0和FMC_SDCLK0对应的存储区域1的地址范围是0xC000 0000-0xCFFF FFFF,而FMC_SDCKE1和FMC_SDCLK1对应的存储区域2的地址范围是0xD000 0000- 0xDFFFFFFF。

  • 当程序里控制内核访问这些地址的存储空间时,FMC外设会即会产生对应的时序,对它外接的SDRAM芯片进行读写。

由图我们可以得知有External RAM和External device,他们之间的区别如下:

External RAM区:这个区域可以直接执行代码,支持XIP功能。

External device区:不支持XIP功能。

XIP功能就是能否直接执行内存中的内容的功能。

可是在实际应用中,我们可以将程序扩展到SDRAM使其自动运行,SDRAM本身具有XIP功能,可是现在芯片上挂载在XIP区上的地址都没有XIP功能。这是在设计芯片时分配不当的问题。但是我们也可以通过配置寄存器来解决这个问题:

通过配置“SYSCFG_MEMRMP”寄存器的“SWP_FMC”寄存器位可用于交换SDRAM与NAND/PC卡的地址映射,使得存储在SDRAM中的代码能被执行,只是由于SDRAM的最高同步时钟是90MHz,代码的执行速度会受影响。

这样就可以扩展STM32的内存,从几k到8M,主要是受到了执行速度的限制。

四、FMC结构体

在STM32中有如下三个结构体:

  • FMC时序结构体 FMC_SDRAMTimingInitTypeDef

 typedef struct
 {
     uint32_t FMC_LoadToActiveDelay;  	/*TMRD:加载模式寄存器命令后的延迟*/
     uint32_t FMC_ExitSelfRefreshDelay; /*TXSR:自刷新命令后的延迟 */
     uint32_t FMC_SelfRefreshTime; 		/*TRAS:自刷新时间*/
     uint32_t FMC_RowCycleDelay; 		/*TRC:行循环延迟*/
     uint32_t FMC_WriteRecoveryTime; 	/*TWR:恢复延迟 */
     uint32_t FMC_RPDelay; 				/*TRP:行预充电延迟*/
     uint32_t FMC_RCDDelay; 			/*TRCD:行到列延迟*/
 } FMC_SDRAMTimingInitTypeDef

以上的时间数据,需要查找SDRAM的文档,并按照要求来设定。这些成员参数值的单位是周期,参数值的大小都可以设置为“1-16”。其中对应的时间为下图的时间:

(1) FMC_LoadToActiveDelay 本成员设置 TMRD 延迟(Load Mode Register to Active),即发送加载模式寄存器命令后 要等待的时间,过了这段时间才可以发送行有效或刷新命令。

(2) FMC_ExitSelfRefreshDelay 本成员设置退出 TXSR 延迟(Exit Self-refresh delay),即退出自我刷新命令后要等待的 时间,过了这段时间才可以发送行有效命令。

(3) FMC_SelfRefreshTime 本成员设置自我刷新时间 TRAS,即发送行有效命令后要等待的时间,过了这段时间 才执行预充电命令。

(4) FMC_RowCycleDelay 本成员设置 TRC 延迟(Row cycle delay),即两个行有效命令之间的延迟,以及两个相 邻刷新命令之间的延迟。

(5) FMC_WriteRecoveryTime 本成员设置 TWR 延迟(Recovery delay),即写命令和预充电命令之间的延迟,等待这 段时间后才开始执行预充电命令。

(6) FMC_RPDelay 本成员设置 TRP 延迟(Row precharge delay),即预充电命令与其它命令之间的延迟。

(7) FMC_RCDDelay 本成员设置 TRCD 延迟(Row to column delay),即行有效命令到列读写命令之间的延迟。

关于我们所使用的SDRAM,我们需要按照数据手册的要求进行配置。

 

 

  • FMC初始化结构体

 typedef struct
 {
     uint32_t FMC_Bank; 				/*选择 FMC 的 SDRAM 存储区域*/
     uint32_t FMC_ColumnBitsNumber; 	/*定义 SDRAM 的列地址宽度 */
     uint32_t FMC_RowBitsNumber;		/*定义 SDRAM 的行地址宽度 */
     uint32_t FMC_SDMemoryDataWidth; 	/*定义 SDRAM 的数据宽度 */
     uint32_t FMC_InternalBankNumber; 	/*定义 SDRAM 内部的 Bank 数目 */
     uint32_t FMC_CASLatency; 			/*定义 CASLatency 的时钟个数*/
     uint32_t FMC_WriteProtection; 		/*定义是否使能写保护模式 */
     uint32_t FMC_SDClockPeriod; 		/*配置同步时钟 SDCLK 的参数*/
     uint32_t FMC_ReadBurst; 			/*是否使能突发读模式*/
     uint32_t FMC_ReadPipeDelay; 		/*定义在 CAS 个延迟后再等待多少个 HCLK 时钟才读取数据 */
     FMC_SDRAMTimingInitTypeDef* FMC_SDRAMTimingStruct; /*定义 SDRAM 的时序参数*/
 } FMC_SDRAMInitTypeDef;

 

FMC初始化结构体,除了最后一个成员是时序结构体配置外,其他结构体成员的配置都对应到FMC_SDCR中的寄存器位。

(1)FMC_Bank:用于选择FMC映射的SDRAM存储区域,可选择存储区域1或2。

(2)FMC_ColumnBitsNumber:本成员用于设置要控制的 SDRAM 的列地址宽度,可选择 8-11 位 (FMC_ColumnBits_Number_8/9/10/11b)。

 

由数据手册可得,列地址宽度为8,行地址宽度为12。

(3)FMC_RowBitsNumber:本成员用于设置要控制的 SDRAM 的行地址宽度,可选择设置成 11-13 位 (FMC_RowBits_Number_11/12/13b)。

(4) FMC_SDMemoryDataWidth:本成员用于设置要控制的 SDRAM 的数据宽度,可选择设置成 8、16 或 32 位 (FMC_SDMemory_Width_8/16/32b)。由图可得,数据宽度也就是Data I/O的个数,为16。

(5)FMC_InternalBankNumber:本成员用于设置要控制的 SDRAM 的内部 Bank 数目,可选择设置成 2 或 4 个 Bank 数 目(FMC_InternalBank_Number_2/4),请注意区分这个结构体成员与 FMC_Bank 的区别。

(6)FMC_CASLatency:本成员用于设置 CASLatency 即 CL 的时钟数目,可选择设置为 1、2 或 3 个时钟周期 (FMC_CAS_Latency_1/2/3)。由上方读时序的图可知,在两个时钟周期后,DQ才会输送有效数据。要注意的是,在配置这个寄存器时,配置的是STM32这边的FMC,在SDRAM的模式寄存器中也有CA用来配置模式,这个时候要注意相对应。

(7)FMC_WriteProtection:本成员用于设置是否使能写保护模式,如果使能了写保护则不能向 SDRAM 写入数据, 正常使用都是禁止写保护的。

(8)FMC_SDClockPeriod:本成员用于设置 FMC 与外部 SDRAM 通讯时的同步时钟参数,可以设置成 STM32 的 HCLK 时钟频率的 1/2 、 1/3 或 禁 止 输 出 时 钟 (FMC_SDClock_Period_2/3 或 FMC_SDClock_Disable)。当设置为禁止输出时钟时,代表STM32不再给SDRAM输出时钟信号,SDRAM需要进行自我刷新。

(9)FMC_ReadBurst:本成员用于设置是否使能突发读取模式,禁止时等效于 BL=1,使能时 BL 的值等于模式寄存器中的配置。(如果禁止的话,只能读写一个;如果没有禁止使能,则会与加载模式寄存器中的突发长度相匹配)

(10)FMC_ReadPipeDelay:本成员用于配置在 CASLatency 个时钟周期后,再等待多少个 HCLK 时钟周期才进行 数据采样,在确保正确的前提下,这个值设置为越短越好,可选择设置的参数值为 0、 1 或 2 个 HCLK 时钟周期(FMC_ReadPipe_Delay_0/1/2)。

  • FMC命令结构体 FMC_SDRAMCommandTypeDef

控制 SDRAM 时需要各种命令,通过向 FMC 的命令模式寄存器 FMC_SDCMR 写入控制参数,即可控制 FMC 对外发送命令。

 typedef struct
 {
     uint32_t FMC_CommandMode; /*要发送的命令 */
     uint32_t FMC_CommandTarget; /*目标存储器区域 */
     uint32_t FMC_AutoRefreshNumber; /*若发送的是自动刷新命令,此处为发送的刷新次数,其它命令时无效 */
     uint32_t FMC_ModeRegisterDefinition; /*若发送的是加载模式寄存器命令,此处为要写入 SDRAM 模式寄存器的参数 */
 } FMC_SDRAMCommandTypeDef;

(1)FMC_CommandMode:

命令说明
FMC_Command_Mode_normal正常模式命令
FMC_Command_Mode_CLK_Enabled使能CLK命令 (空操作)
FMC_Command_Mode_PALL对所有Bank预充电命令
FMC_Command_Mode_AutoRefresh自动刷新命令
FMC_Command_Mode_LoadMode加载模式寄存器命令
FMC_Command_Mode_Selfrefresh自我刷新命令
FMC_Command_Mode_PowerDown掉电命令

(2)FMC_CommandTarget:本成员用于选择要控制的FMC存储区域,可选择存储区域1或2(FMC_Command_Target_bank1/2)。(要对应地址映射)

(3)FMC_AutoRefreshNumber:有时需要连续发送多个 “自动刷新”(Auto Refresh)命令时,配置本成员即可控制它发送多少次,可输入参数值为1-16,若发送的是其它命令,本参数值无效。如FMC_CommandMode成员被配置为宏FMC_Command_Mode_AutoRefresh,而FMC_AutoRefreshNumber被设置为2时,FMC就会控制发送2次自动刷新命令。初始化流程中的一部分。

(4)FMC_ModeRegisterDefinition:当向SDRAM发送加载模式寄存器命令时,这个结构体成员的值将通过地址线发送到SDRAM的模式寄存器中,这个成员值长度为13位,各个位一一对应SDRAM的模式寄存器。

配置完这些结构体成员,调用库函数FMC_SDRAMCmdConfig即可把这些参数写入到FMC_SDCMR寄存器中,然后FMC外设就会发送相应的命令了。

五、实操

在开始使用前,我们需要了解芯片上的SDRAM引脚的分配:

 

补充:由输入的地址可知,起始地址为0xD000 0000,结束地址为0xD080 0000,由我们的SDRAM的内存空间8MByte可知,对应的内存地址应该为0x0800 0000。经过验算 0x0800 0000化为十进制数,之后除以1024,得到kb,再除以1024,就可得8MByte。得知地址之后可以使用C语言的指针在地址之间读取。可以将多种变量存入其中,进行读写以及修改。

我们做一个在SDRAM中写入数据并且打印数据的功能:

(1)初始化FMC使用的GPIO

(2)初始化SDRAM的时序结构体

(3)初始化SDRAM的初始化结构体

(4)使用命令初始化SDRAM芯片

(5)进行读写SDRAM

(1)配置引脚

 

/*A行列地址信号线*/    
#define FMC_A0_GPIO_PORT        GPIOF
#define FMC_A0_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A0_GPIO_PIN         GPIO_Pin_0
#define FMC_A0_PINSOURCE        GPIO_PinSource0
#define FMC_A0_AF               GPIO_AF_FMC

#define FMC_A1_GPIO_PORT        GPIOF
#define FMC_A1_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A1_GPIO_PIN         GPIO_Pin_1
#define FMC_A1_PINSOURCE        GPIO_PinSource1
#define FMC_A1_AF               GPIO_AF_FMC

#define FMC_A2_GPIO_PORT        GPIOF
#define FMC_A2_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A2_GPIO_PIN         GPIO_Pin_2
#define FMC_A2_PINSOURCE        GPIO_PinSource2
#define FMC_A2_AF               GPIO_AF_FMC

#define FMC_A3_GPIO_PORT        GPIOF
#define FMC_A3_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A3_GPIO_PIN         GPIO_Pin_3
#define FMC_A3_PINSOURCE        GPIO_PinSource3
#define FMC_A3_AF               GPIO_AF_FMC

#define FMC_A4_GPIO_PORT        GPIOF
#define FMC_A4_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A4_GPIO_PIN         GPIO_Pin_4
#define FMC_A4_PINSOURCE        GPIO_PinSource4
#define FMC_A4_AF               GPIO_AF_FMC

#define FMC_A5_GPIO_PORT        GPIOF
#define FMC_A5_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A5_GPIO_PIN         GPIO_Pin_5
#define FMC_A5_PINSOURCE        GPIO_PinSource5
#define FMC_A5_AF               GPIO_AF_FMC

#define FMC_A6_GPIO_PORT        GPIOF
#define FMC_A6_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A6_GPIO_PIN         GPIO_Pin_12
#define FMC_A6_PINSOURCE        GPIO_PinSource12
#define FMC_A6_AF               GPIO_AF_FMC

#define FMC_A7_GPIO_PORT        GPIOF
#define FMC_A7_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A7_GPIO_PIN         GPIO_Pin_13
#define FMC_A7_PINSOURCE        GPIO_PinSource13
#define FMC_A7_AF               GPIO_AF_FMC

#define FMC_A8_GPIO_PORT        GPIOF
#define FMC_A8_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A8_GPIO_PIN         GPIO_Pin_14
#define FMC_A8_PINSOURCE        GPIO_PinSource14
#define FMC_A8_AF               GPIO_AF_FMC

#define FMC_A9_GPIO_PORT        GPIOF
#define FMC_A9_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_A9_GPIO_PIN         GPIO_Pin_15
#define FMC_A9_PINSOURCE        GPIO_PinSource15
#define FMC_A9_AF               GPIO_AF_FMC


#define FMC_A10_GPIO_PORT        GPIOG
#define FMC_A10_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_A10_GPIO_PIN         GPIO_Pin_0
#define FMC_A10_PINSOURCE        GPIO_PinSource0
#define FMC_A10_AF               GPIO_AF_FMC


#define FMC_A11_GPIO_PORT        GPIOG
#define FMC_A11_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_A11_GPIO_PIN         GPIO_Pin_1
#define FMC_A11_PINSOURCE        GPIO_PinSource1
#define FMC_A11_AF               GPIO_AF_FMC

/*BA地址线*/
#define FMC_BA0_GPIO_PORT        GPIOG
#define FMC_BA0_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_BA0_GPIO_PIN         GPIO_Pin_4
#define FMC_BA0_PINSOURCE        GPIO_PinSource4
#define FMC_BA0_AF               GPIO_AF_FMC

#define FMC_BA1_GPIO_PORT        GPIOG
#define FMC_BA1_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_BA1_GPIO_PIN         GPIO_Pin_5
#define FMC_BA1_PINSOURCE        GPIO_PinSource5
#define FMC_BA1_AF               GPIO_AF_FMC

/*DQ数据信号线*/
#define FMC_D0_GPIO_PORT        GPIOD
#define FMC_D0_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D0_GPIO_PIN         GPIO_Pin_14
#define FMC_D0_PINSOURCE        GPIO_PinSource14
#define FMC_D0_AF               GPIO_AF_FMC

#define FMC_D1_GPIO_PORT        GPIOD
#define FMC_D1_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D1_GPIO_PIN         GPIO_Pin_15
#define FMC_D1_PINSOURCE        GPIO_PinSource15
#define FMC_D1_AF               GPIO_AF_FMC

#define FMC_D2_GPIO_PORT        GPIOD
#define FMC_D2_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D2_GPIO_PIN         GPIO_Pin_0
#define FMC_D2_PINSOURCE        GPIO_PinSource0
#define FMC_D2_AF               GPIO_AF_FMC

#define FMC_D3_GPIO_PORT        GPIOD
#define FMC_D3_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D3_GPIO_PIN         GPIO_Pin_1
#define FMC_D3_PINSOURCE        GPIO_PinSource1
#define FMC_D3_AF               GPIO_AF_FMC

#define FMC_D4_GPIO_PORT        GPIOE
#define FMC_D4_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D4_GPIO_PIN         GPIO_Pin_7
#define FMC_D4_PINSOURCE        GPIO_PinSource7
#define FMC_D4_AF               GPIO_AF_FMC

#define FMC_D5_GPIO_PORT        GPIOE
#define FMC_D5_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D5_GPIO_PIN         GPIO_Pin_8
#define FMC_D5_PINSOURCE        GPIO_PinSource8
#define FMC_D5_AF               GPIO_AF_FMC

#define FMC_D6_GPIO_PORT        GPIOE
#define FMC_D6_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D6_GPIO_PIN         GPIO_Pin_9
#define FMC_D6_PINSOURCE        GPIO_PinSource9
#define FMC_D6_AF               GPIO_AF_FMC

#define FMC_D7_GPIO_PORT        GPIOE
#define FMC_D7_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D7_GPIO_PIN         GPIO_Pin_10
#define FMC_D7_PINSOURCE        GPIO_PinSource10
#define FMC_D7_AF               GPIO_AF_FMC

#define FMC_D8_GPIO_PORT        GPIOE
#define FMC_D8_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D8_GPIO_PIN         GPIO_Pin_11
#define FMC_D8_PINSOURCE        GPIO_PinSource11
#define FMC_D8_AF               GPIO_AF_FMC

#define FMC_D9_GPIO_PORT        GPIOE
#define FMC_D9_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D9_GPIO_PIN         GPIO_Pin_12
#define FMC_D9_PINSOURCE        GPIO_PinSource12
#define FMC_D9_AF               GPIO_AF_FMC

#define FMC_D10_GPIO_PORT        GPIOE
#define FMC_D10_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D10_GPIO_PIN         GPIO_Pin_13
#define FMC_D10_PINSOURCE        GPIO_PinSource13
#define FMC_D10_AF               GPIO_AF_FMC

#define FMC_D11_GPIO_PORT        GPIOE
#define FMC_D11_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D11_GPIO_PIN         GPIO_Pin_14
#define FMC_D11_PINSOURCE        GPIO_PinSource14
#define FMC_D11_AF               GPIO_AF_FMC

#define FMC_D12_GPIO_PORT        GPIOE
#define FMC_D12_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_D12_GPIO_PIN         GPIO_Pin_15
#define FMC_D12_PINSOURCE        GPIO_PinSource15
#define FMC_D12_AF               GPIO_AF_FMC

#define FMC_D13_GPIO_PORT        GPIOD
#define FMC_D13_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D13_GPIO_PIN         GPIO_Pin_8
#define FMC_D13_PINSOURCE        GPIO_PinSource8
#define FMC_D13_AF               GPIO_AF_FMC

#define FMC_D14_GPIO_PORT        GPIOD
#define FMC_D14_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D14_GPIO_PIN         GPIO_Pin_9
#define FMC_D14_PINSOURCE        GPIO_PinSource9
#define FMC_D14_AF               GPIO_AF_FMC

#define FMC_D15_GPIO_PORT        GPIOD
#define FMC_D15_GPIO_CLK         RCC_AHB1Periph_GPIOD
#define FMC_D15_GPIO_PIN         GPIO_Pin_10
#define FMC_D15_PINSOURCE        GPIO_PinSource10
#define FMC_D15_AF               GPIO_AF_FMC


/*控制信号线*/  
/*CS片选*/
#define FMC_CS_GPIO_PORT        GPIOH
#define FMC_CS_GPIO_CLK         RCC_AHB1Periph_GPIOH
#define FMC_CS_GPIO_PIN         GPIO_Pin_6
#define FMC_CS_PINSOURCE        GPIO_PinSource6
#define FMC_CS_AF               GPIO_AF_FMC

/*WE写使能*/
#define FMC_WE_GPIO_PORT        GPIOC
#define FMC_WE_GPIO_CLK         RCC_AHB1Periph_GPIOC
#define FMC_WE_GPIO_PIN         GPIO_Pin_0
#define FMC_WE_PINSOURCE        GPIO_PinSource0
#define FMC_WE_AF               GPIO_AF_FMC
/*RAS行选通*/
#define FMC_RAS_GPIO_PORT        GPIOF
#define FMC_RAS_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FMC_RAS_GPIO_PIN         GPIO_Pin_11
#define FMC_RAS_PINSOURCE        GPIO_PinSource11
#define FMC_RAS_AF               GPIO_AF_FMC
/*CAS列选通*/
#define FMC_CAS_GPIO_PORT        GPIOG
#define FMC_CAS_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_CAS_GPIO_PIN         GPIO_Pin_15
#define FMC_CAS_PINSOURCE        GPIO_PinSource15
#define FMC_CAS_AF               GPIO_AF_FMC
/*CLK同步时钟,存储区域2*/
#define FMC_CLK_GPIO_PORT        GPIOG
#define FMC_CLK_GPIO_CLK         RCC_AHB1Periph_GPIOG
#define FMC_CLK_GPIO_PIN         GPIO_Pin_8
#define FMC_CLK_PINSOURCE        GPIO_PinSource8
#define FMC_CLK_AF               GPIO_AF_FMC
/*CKE时钟使能,存储区域2*/
#define FMC_CKE_GPIO_PORT        GPIOH
#define FMC_CKE_GPIO_CLK         RCC_AHB1Periph_GPIOH
#define FMC_CKE_GPIO_PIN         GPIO_Pin_7
#define FMC_CKE_PINSOURCE        GPIO_PinSource7
#define FMC_CKE_AF               GPIO_AF_FMC

/*DQM1数据掩码*/
#define FMC_UDQM_GPIO_PORT        GPIOE
#define FMC_UDQM_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_UDQM_GPIO_PIN         GPIO_Pin_1
#define FMC_UDQM_PINSOURCE        GPIO_PinSource1
#define FMC_UDQM_AF               GPIO_AF_FMC
/*DQM0数据掩码*/
#define FMC_LDQM_GPIO_PORT        GPIOE
#define FMC_LDQM_GPIO_CLK         RCC_AHB1Periph_GPIOE
#define FMC_LDQM_GPIO_PIN         GPIO_Pin_0
#define FMC_LDQM_PINSOURCE        GPIO_PinSource0
#define FMC_LDQM_AF               GPIO_AF_FMC

初始化函数:

static void SDRAM_GPIO_Config(void)
{
 
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /* 使能SDRAM相关的GPIO时钟 */

                         /*地址信号线*/
  RCC_AHB1PeriphClockCmd(FMC_A0_GPIO_CLK | FMC_A1_GPIO_CLK | FMC_A2_GPIO_CLK | 
                         FMC_A3_GPIO_CLK | FMC_A4_GPIO_CLK | FMC_A5_GPIO_CLK |
                         FMC_A6_GPIO_CLK | FMC_A7_GPIO_CLK | FMC_A8_GPIO_CLK |
                         FMC_A9_GPIO_CLK | FMC_A10_GPIO_CLK| FMC_A11_GPIO_CLK| 
											FMC_BA0_GPIO_CLK|FMC_BA1_GPIO_CLK|
                         /*数据信号线*/
                         FMC_D0_GPIO_CLK | FMC_D1_GPIO_CLK | FMC_D2_GPIO_CLK | 
                         FMC_D3_GPIO_CLK | FMC_D4_GPIO_CLK | FMC_D5_GPIO_CLK |
                         FMC_D6_GPIO_CLK | FMC_D7_GPIO_CLK | FMC_D8_GPIO_CLK |
                         FMC_D9_GPIO_CLK | FMC_D10_GPIO_CLK| FMC_D11_GPIO_CLK|
                         FMC_D12_GPIO_CLK| FMC_D13_GPIO_CLK| FMC_D14_GPIO_CLK|
                         FMC_D15_GPIO_CLK|  
                         /*控制信号线*/
                         FMC_CS_GPIO_CLK  | FMC_WE_GPIO_CLK | FMC_RAS_GPIO_CLK |
                         FMC_CAS_GPIO_CLK |FMC_CLK_GPIO_CLK | FMC_CKE_GPIO_CLK |
                         FMC_UDQM_GPIO_CLK|FMC_LDQM_GPIO_CLK, ENABLE);
                         

  /* 通用 GPIO 配置 */
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;       //配置为复用功能
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽输出
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;   //无需上拉
  
  
  /*A行列地址信号线 针对引脚配置*/
  GPIO_InitStructure.GPIO_Pin = FMC_A0_GPIO_PIN; 
  GPIO_Init(FMC_A0_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A0_GPIO_PORT, FMC_A0_PINSOURCE , FMC_A0_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A1_GPIO_PIN; 
  GPIO_Init(FMC_A1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A1_GPIO_PORT, FMC_A1_PINSOURCE , FMC_A1_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A2_GPIO_PIN; 
  GPIO_Init(FMC_A2_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A2_GPIO_PORT, FMC_A2_PINSOURCE , FMC_A2_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A3_GPIO_PIN; 
  GPIO_Init(FMC_A3_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A3_GPIO_PORT, FMC_A3_PINSOURCE , FMC_A3_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A4_GPIO_PIN; 
  GPIO_Init(FMC_A4_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A4_GPIO_PORT, FMC_A4_PINSOURCE , FMC_A4_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A5_GPIO_PIN; 
  GPIO_Init(FMC_A5_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A5_GPIO_PORT, FMC_A5_PINSOURCE , FMC_A5_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A6_GPIO_PIN; 
  GPIO_Init(FMC_A6_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A6_GPIO_PORT, FMC_A6_PINSOURCE , FMC_A6_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A7_GPIO_PIN; 
  GPIO_Init(FMC_A7_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A7_GPIO_PORT, FMC_A7_PINSOURCE , FMC_A7_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A8_GPIO_PIN; 
  GPIO_Init(FMC_A8_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A8_GPIO_PORT, FMC_A8_PINSOURCE , FMC_A8_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A9_GPIO_PIN; 
  GPIO_Init(FMC_A9_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A9_GPIO_PORT, FMC_A9_PINSOURCE , FMC_A9_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A10_GPIO_PIN; 
  GPIO_Init(FMC_A10_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A10_GPIO_PORT, FMC_A10_PINSOURCE , FMC_A10_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_A11_GPIO_PIN; 
  GPIO_Init(FMC_A11_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_A11_GPIO_PORT, FMC_A11_PINSOURCE , FMC_A11_AF);
	
	/*BA地址信号线*/
	GPIO_InitStructure.GPIO_Pin = FMC_BA0_GPIO_PIN; 
  GPIO_Init(FMC_BA0_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_BA0_GPIO_PORT, FMC_BA0_PINSOURCE , FMC_BA0_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_BA1_GPIO_PIN;
  GPIO_Init(FMC_BA1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_BA1_GPIO_PORT, FMC_BA1_PINSOURCE , FMC_BA1_AF);
  
  
  /*DQ数据信号线 针对引脚配置*/
  GPIO_InitStructure.GPIO_Pin = FMC_D0_GPIO_PIN; 
  GPIO_Init(FMC_D0_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D0_GPIO_PORT, FMC_D0_PINSOURCE , FMC_D0_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D1_GPIO_PIN; 
  GPIO_Init(FMC_D1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D1_GPIO_PORT, FMC_D1_PINSOURCE , FMC_D1_AF);
    
  GPIO_InitStructure.GPIO_Pin = FMC_D2_GPIO_PIN; 
  GPIO_Init(FMC_D2_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D2_GPIO_PORT, FMC_D2_PINSOURCE , FMC_D2_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D3_GPIO_PIN; 
  GPIO_Init(FMC_D3_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D3_GPIO_PORT, FMC_D3_PINSOURCE , FMC_D3_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D4_GPIO_PIN; 
  GPIO_Init(FMC_D4_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D4_GPIO_PORT, FMC_D4_PINSOURCE , FMC_D4_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D5_GPIO_PIN; 
  GPIO_Init(FMC_D5_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D5_GPIO_PORT, FMC_D5_PINSOURCE , FMC_D5_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D6_GPIO_PIN; 
  GPIO_Init(FMC_D6_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D6_GPIO_PORT, FMC_D6_PINSOURCE , FMC_D6_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D7_GPIO_PIN; 
  GPIO_Init(FMC_D7_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D7_GPIO_PORT, FMC_D7_PINSOURCE , FMC_D7_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D8_GPIO_PIN; 
  GPIO_Init(FMC_D8_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D8_GPIO_PORT, FMC_D8_PINSOURCE , FMC_D8_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D9_GPIO_PIN; 
  GPIO_Init(FMC_D9_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D9_GPIO_PORT, FMC_D9_PINSOURCE , FMC_D9_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D10_GPIO_PIN; 
  GPIO_Init(FMC_D10_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D10_GPIO_PORT, FMC_D10_PINSOURCE , FMC_D10_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D11_GPIO_PIN; 
  GPIO_Init(FMC_D11_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D11_GPIO_PORT, FMC_D11_PINSOURCE , FMC_D11_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D12_GPIO_PIN; 
  GPIO_Init(FMC_D12_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D12_GPIO_PORT, FMC_D12_PINSOURCE , FMC_D12_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D13_GPIO_PIN; 
  GPIO_Init(FMC_D13_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D13_GPIO_PORT, FMC_D13_PINSOURCE , FMC_D13_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D14_GPIO_PIN; 
  GPIO_Init(FMC_D14_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D14_GPIO_PORT, FMC_D14_PINSOURCE , FMC_D14_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_D15_GPIO_PIN; 
  GPIO_Init(FMC_D15_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_D15_GPIO_PORT, FMC_D15_PINSOURCE , FMC_D15_AF);
  
  /*控制信号线*/
  GPIO_InitStructure.GPIO_Pin = FMC_CS_GPIO_PIN; 
  GPIO_Init(FMC_CS_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_CS_GPIO_PORT, FMC_CS_PINSOURCE , FMC_CS_AF);
    
  GPIO_InitStructure.GPIO_Pin = FMC_WE_GPIO_PIN; 
  GPIO_Init(FMC_WE_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_WE_GPIO_PORT, FMC_WE_PINSOURCE , FMC_WE_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_RAS_GPIO_PIN; 
  GPIO_Init(FMC_RAS_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_RAS_GPIO_PORT, FMC_RAS_PINSOURCE , FMC_RAS_AF);
    
  GPIO_InitStructure.GPIO_Pin = FMC_CAS_GPIO_PIN; 
  GPIO_Init(FMC_CAS_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_CAS_GPIO_PORT, FMC_CAS_PINSOURCE , FMC_CAS_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_CLK_GPIO_PIN; 
  GPIO_Init(FMC_CLK_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_CLK_GPIO_PORT, FMC_CLK_PINSOURCE , FMC_CLK_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_CKE_GPIO_PIN; 
  GPIO_Init(FMC_CKE_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_CKE_GPIO_PORT, FMC_CKE_PINSOURCE , FMC_CKE_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_UDQM_GPIO_PIN; 
  GPIO_Init(FMC_UDQM_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_UDQM_GPIO_PORT, FMC_UDQM_PINSOURCE , FMC_UDQM_AF);
  
  GPIO_InitStructure.GPIO_Pin = FMC_LDQM_GPIO_PIN; 
  GPIO_Init(FMC_LDQM_GPIO_PORT, &GPIO_InitStructure);
  GPIO_PinAFConfig(FMC_LDQM_GPIO_PORT, FMC_LDQM_PINSOURCE , FMC_LDQM_AF);
	

}

(2)时序配置

以下配置均为数据手册所规定的。

//初始化FMC的时序及初始化结构体
static void SDRAM_TIMING_Config(void)
{
 FMC_SDRAMInitTypeDef   FMC_SDRAMInitStruct;
 FMC_SDRAMTimingInitTypeDef FMC_SDRAMTimingInitStruct;

  //SDCLK时钟频率为f = 90MHz,一个时钟周期 T =1/f =1/90M =1.111*10^8 s = 11 ns 
  //配置时可以使时间超过所规定的
  //初始化时序结构体
  FMC_SDRAMTimingInitStruct.FMC_ExitSelfRefreshDelay = 7; //txsr
  FMC_SDRAMTimingInitStruct.FMC_LoadToActiveDelay = 2;  //tmrd
  FMC_SDRAMTimingInitStruct.FMC_RCDDelay = 2;//trcd
  FMC_SDRAMTimingInitStruct.FMC_RowCycleDelay = 6;
  FMC_SDRAMTimingInitStruct.FMC_RPDelay = 2; //trpd
  FMC_SDRAMTimingInitStruct.FMC_SelfRefreshTime = 4; //tras
  FMC_SDRAMTimingInitStruct.FMC_WriteRecoveryTime = 2 ;//twr
  
  //配置成90MHz
  FMC_SDRAMInitStruct.FMC_SDClockPeriod = FMC_SDClock_Period_2;//hclk的2分频
  FMC_SDRAMInitStruct.FMC_Bank = FMC_Bank2_SDRAM; //使用的sdram区域
  FMC_SDRAMInitStruct.FMC_CASLatency = FMC_CAS_Latency_2  ;//CAS延迟周期
  
  FMC_SDRAMInitStruct.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b  ; //列地址线数目
  FMC_SDRAMInitStruct.FMC_RowBitsNumber = FMC_RowBits_Number_12b;//行地址线数目
  FMC_SDRAMInitStruct.FMC_SDMemoryDataWidth = FMC_SDMemory_Width_16b ; //数据线宽度:16位
  
  FMC_SDRAMInitStruct.FMC_InternalBankNumber = FMC_InternalBank_Number_4 ;//sdram内部bank数目
  FMC_SDRAMInitStruct.FMC_ReadBurst = FMC_Read_Burst_Enable; //使能突发读
  FMC_SDRAMInitStruct.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_0 ;//cas后延时多少个HCLK
  FMC_SDRAMInitStruct.FMC_WriteProtection = FMC_Write_Protection_Disable ; //禁止写保护

  FMC_SDRAMInitStruct.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStruct;
  
  FMC_SDRAMInit (&FMC_SDRAMInitStruct);

}

 

(3)使用命令初始化SDRAM

此函数需要根据以上的初始化流程来编写:

//使用命令初始化SDRAM
static void SDRAM_Init_Process(void)
{
  
  FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStruct;
 
  //1.开启SDCLK
  FMC_SDRAMCommandStruct.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled  ;
  FMC_SDRAMCommandStruct.FMC_CommandTarget = FMC_Command_Target_bank2  ; 
  FMC_SDRAMCommandStruct.FMC_AutoRefreshNumber = 0 ;//非自动刷新模式
  FMC_SDRAMCommandStruct.FMC_ModeRegisterDefinition = 0; //非加载模式寄存器 
  
  //发送命令
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStruct);

  //等待命令发送完毕
  while( FMC_GetFlagStatus(FMC_Bank2_SDRAM,FMC_FLAG_Busy)== SET);
   
  //延时
  SDRAM_Delay(0xFFFFF);
  
  //2.对所有BANK进行预充电
  FMC_SDRAMCommandStruct.FMC_CommandMode = FMC_Command_Mode_PALL  ;
  FMC_SDRAMCommandStruct.FMC_CommandTarget = FMC_Command_Target_bank2  ; 
  FMC_SDRAMCommandStruct.FMC_AutoRefreshNumber = 0 ;
  FMC_SDRAMCommandStruct.FMC_ModeRegisterDefinition = 0;  
  
  //发送命令
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStruct);

  //等待命令发送完毕
  while( FMC_GetFlagStatus(FMC_Bank2_SDRAM,FMC_FLAG_Busy)== SET);
  
  //延时
  SDRAM_Delay(0xFFFFF);
  
  //3.自动刷新至少两次
  FMC_SDRAMCommandStruct.FMC_CommandMode = FMC_Command_Mode_AutoRefresh  ;
  FMC_SDRAMCommandStruct.FMC_CommandTarget = FMC_Command_Target_bank2  ; 
  FMC_SDRAMCommandStruct.FMC_AutoRefreshNumber = 2 ;
  FMC_SDRAMCommandStruct.FMC_ModeRegisterDefinition = 0;  
  
  //发送命令
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStruct);

  //等待命令发送完毕
  while( FMC_GetFlagStatus(FMC_Bank2_SDRAM,FMC_FLAG_Busy)== SET);
  
  //延时
  SDRAM_Delay(0xFFFFF);
  
  //4.加载模式寄存器
  FMC_SDRAMCommandStruct.FMC_CommandMode = FMC_Command_Mode_LoadMode  ;
  FMC_SDRAMCommandStruct.FMC_CommandTarget = FMC_Command_Target_bank2  ; 
  FMC_SDRAMCommandStruct.FMC_AutoRefreshNumber = 0 ;
  FMC_SDRAMCommandStruct.FMC_ModeRegisterDefinition = BURST_LENGTH|  BT|CAS_LATENCY|OP_CODE|WB;
  
  //发送命令
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStruct);

  //等待命令发送完毕
  while( FMC_GetFlagStatus(FMC_Bank2_SDRAM,FMC_FLAG_Busy)== SET);

  SDRAM_Delay(0xFFFFF);

  //设置FMC的刷新周期64mss
  //T_Sclk = 1/90MHz
  //count = 64ms/4096 = 15.625us/T = 1406.25
  //count = 1406.25-20 = 1386.25
  FMC_SetRefreshCount(1368);
  
} 

 

 

检测命令是否发生完成。

配置模式寄存器,最后使用时使用或将其输入寄存器:

//模式寄存器配置

#define BURST_LENGTH      (0x0003)    //8字节突发
#define BT                 (0x0000)    //连续模式
#define CAS_LATENCY       (0x0020)    //CAL =2
#define OP_CODE            (0x0000)    //正常工作模式  
#define WB                  (0x0000)    //写突发跟读突发一致

 

最后要记得配置刷新周期。

(4)使用SDRAM

定义基地址:

//SDRAM地址
#define SDRAM_BASE_ADDR  (0xD0000000)
#define SDRAM_SIZE        (0x00800000)
#define SDRAM_END_ADDR    (SDRAM_BASE_ADDR+SDRAM_SIZE-1)

这个地址是FMC映射在STM32上的地址,非SDRAM中的地址,需要弄清。

使用指针访问SDRAM的数据并且写入:

*(uint16_t *)(SDRAM_BASE_ADDR) = 0XF5F5;

 其实上面那句话是这个用法:(双重指针)

pdata_test = (uint16_t *)SDRAM_BASE_ADDR;
*pdata_test = 0xF5F5

使用指针访问SDRAM数据并且读出:

read_temp = *(uint16_t *)(SDRAM_BASE_ADDR);

其实是相当于下面的操作:

pdata_test = (uint16_t *)(SDRAM_BASE_ADDR);
read_temp = *pdata_test;

以上的使用方法并不严谨,我们并没有使用malloc函数申请变量空间,但是它是可行的。

我们平时定义的普通的全局变量是存在SRAM中,如果想要使其定义在外部的SDRAM中则可使用下面的这句:在使用时要记得定义为全局变量,若为局部变量则存放在SRAM中。

uint16_t ptest __attribute__((at(SDRAM_BASE_ADDR+100));
ptest = 0xAA	//此时读出来的值为0xAA

到目前有三种访问外部SDRAM的方法:

  1. 使用指针直接访问SDRAM的映射地址

  2. 使用编译器强制指定变量定义的地址

  3. 使用sct文件实现自动分配变量的地址到SDRAM

最后去看了大佬写好的封装写入或读取的函数,确实佩服:

 

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

  /* 禁止写保护 */
  FMC_SDRAMWriteProtectionConfig(FMC_Bank2_SDRAM, DISABLE);
  
  /* 检查SDRAM标志,等待至SDRAM空闲 */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }

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

    /* 地址自增*/
    write_pointer += 4;
  }
    
}
/**
  * @brief  从SDRAM中读取数据 
  * @param  pBuffer: 指向存储数据的buffer
  * @param  ReadAddress: 要读取数据的地十
  * @param  uwBufferSize: 要读取的数据大小
  * @retval None.
  */
void SDRAM_ReadBuffer(uint32_t* pBuffer, uint32_t uwReadAddress, uint32_t uwBufferSize)
{
  __IO uint32_t write_pointer = (uint32_t)uwReadAddress;
  
   
  /* 检查SDRAM标志,等待至SDRAM空闲 */  
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  
  /*读取数据 */
  for(; uwBufferSize != 0x00; uwBufferSize--)
  {
   *pBuffer++ = *(__IO uint32_t *)(SDRAM_BANK_ADDR + write_pointer );
    
   /* 地址自增*/
    write_pointer += 4;
  } 
}

还有测试是否读写正常的函数:

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

#define SDRAM_INFO(fmt,arg...)           printf("<<-SDRAM-INFO->> "fmt"\n",##arg)
#define SDRAM_ERROR(fmt,arg...)          printf("<<-SDRAM-ERROR->> "fmt"\n",##arg)
#define SDRAM_DEBUG(fmt,arg...)          do{\
                                          if(SDRAM_DEBUG_ON)\
                                          printf("<<-SDRAM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)
/**
  * @brief  测试SDRAM是否正常 
  * @param  None
  * @retval 正常返回1,异常返回0
  */
uint8_t SDRAM_Test(void)
{
  /*写入数据计数器*/
  uint32_t counter=0;
  
  /* 8位的数据 */
  uint8_t ubWritedata_8b = 0, ubReaddata_8b = 0;  
  
  /* 16位的数据 */
  uint16_t uhWritedata_16b = 0, uhReaddata_16b = 0; 
  
  SDRAM_INFO("正在检测SDRAM,以8位、16位的方式读写sdram...");


  /*按8位格式读写数据,并校验*/
  
  /* 把SDRAM数据全部重置为0 ,IS42S16400J_SIZE是以8位为单位的 */
  for (counter = 0x00; counter < IS42S16400J_SIZE; counter++)
  {
    *(__IO uint8_t*) (SDRAM_BANK_ADDR + counter) = (uint8_t)0x0;
  }
  
  /* 向整个SDRAM写入数据  8位 */
  for (counter = 0; counter < IS42S16400J_SIZE; counter++)
  {
    *(__IO uint8_t*) (SDRAM_BANK_ADDR + counter) = (uint8_t)(ubWritedata_8b + counter);
  }
  
  /* 读取 SDRAM 数据并检测*/
  for(counter = 0; counter<IS42S16400J_SIZE;counter++ )
  {
    ubReaddata_8b = *(__IO uint8_t*)(SDRAM_BANK_ADDR + counter);  //从该地址读出数据
    
    if(ubReaddata_8b != (uint8_t)(ubWritedata_8b + counter))      //检测数据,若不相等,跳出函数,返回检测失败结果。
    {
      SDRAM_ERROR("8位数据读写错误!");
      return 0;
    }
  }
	
  
  /*按16位格式读写数据,并检测*/
  
  /* 把SDRAM数据全部重置为0 */
  for (counter = 0x00; counter < IS42S16400J_SIZE/2; counter++)
  {
    *(__IO uint16_t*) (SDRAM_BANK_ADDR + 2*counter) = (uint16_t)0x00;
  }
  
  /* 向整个SDRAM写入数据  16位 */
  for (counter = 0; counter < IS42S16400J_SIZE/2; counter++)
  {
    *(__IO uint16_t*) (SDRAM_BANK_ADDR + 2*counter) = (uint16_t)(uhWritedata_16b + counter);
  }
  
    /* 读取 SDRAM 数据并检测*/
  for(counter = 0; counter<IS42S16400J_SIZE/2;counter++ )
  {
    uhReaddata_16b = *(__IO uint16_t*)(SDRAM_BANK_ADDR + 2*counter);  //从该地址读出数据
    
    if(uhReaddata_16b != (uint16_t)(uhWritedata_16b + counter))      //检测数据,若不相等,跳出函数,返回检测失败结果。
    {
      SDRAM_ERROR("16位数据读写错误!");

      return 0;
    }
  }

  
  SDRAM_INFO("SDRAM读写测试正常!"); 
  /*检测正常,return 1 */
  return 1;
  
}

今天是大年初一啦,终于有时间发表一篇完整的文章了,一直都在忙着学习忙着比赛,熬了又熬,但是还好找到方向了!!接下来也要继续努力!!新年快乐!!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值