STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但当程序较大,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了。
市场上SDRAM的价格普遍比SRAM性价比要高一些,所以正常电脑扩展的内存,那些内存条都是由SDRAM集成的。
给STM32芯片扩展内存与给PC扩展内存的原理是一样的,只是PC上一般以内存条的形式扩展,内存条实质是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块,而STM32直接与SDRAM芯片连接。
一、SDRAM控制原理
下面以型号IS2-45S16400J的SDRAM芯片为例:
(1)引脚
#代表低电平有效。
信号线 | 类型 | 说明 |
---|---|---|
CLK | I | 同步时钟信号,所有输入信号都在CLK为上升沿的时候被采集 |
CKE | I | 时钟使能信号,禁止时钟信号时SDRAM会启动自刷新操作(低功耗模式 ) |
CS# | I | 片选信号,低电平有效 |
CAS# | I | 列地址选通,为低电平时地址线表示的是列地址 |
RAS# | I | 行地址选通,为低电平时地址线表示的是行地址 |
WE# | I | 写入使能,低电平有效 |
DQM[0:1] | I | 数据输入/输出掩码信号,表示DQ信号线的有效部分,取高8位或者低8位。 |
BA[0:1] | I | Bank地址输入,选择要控制的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命令
-
命令禁止(COMMAND INHIBIT):只要CS引脚为高电平,则用于禁止SDRAM执行的新命令,但它不能停止当前正在执行的命令。
-
空操作(NO OPERATION):命令禁止的反操作,用于选中SDRAM,以便接下来发送命令。防止干扰。
-
行有效(ACTIVE):进行存储单元寻址时,需要先选中访问的BANK和行,使它处于激活状态。发送行有效命令时,RAS线为低电平,同时通过BA线以及A线发送Bank地址和行地址。
-
列读写(READ):“读命令”(READ)和“写命令”(WRITE)的时序很相似,通过共用的地址线A发送列地址,同时使用WE引脚表示读/写方向,WE为低电平时表示写,高电平时表示读。数据读写时,使用DQM线表示有效的DQ数据线。
-
预充电(PRECHARGE):SDRAM的寻址具有独占性,所以在进行读写操作后,如果要对同一个Bank的另一行进行寻址,就要将原来有效(ACTIVE)的行关闭,重新发送行/列地址。Bank关闭当前工作行,准备打开新行的操作就是预充电。配合使用A10线控制,若A10为高电平时,所有Bank都预充电;A10为低电平时,使用BA线选择要预充电的Bank。
-
刷新(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有效才能退出模式并且进入正常的工作状态。
-
-
加载模式寄存器
-
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# | DQM | ADDR | DQ |
---|---|---|---|---|---|---|---|
COMMAND INHIBIT | H | X | X | X | X | X | X |
NO OPERATION | L | H | H | H | X | X | X |
ACTIVE | L | L | H | H | X | Bank/row | X |
READ | L | H | L | H | L/H | Bank/col | X |
WRITE | L | H | L | L | L/H | Bank/col | Valid |
PRECHARGE | L | L | H | L | X | Code | X |
AUTO REFRESH or SELF REFRESH | L | L | L | H | X | X | X |
LOAD MODE REGISTER | L | L | L | L | X | Op-code | X |
BURST TERMINATE | L | H | H | L | X | X | active |
(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_SDCLK | CLK | 同步时钟信号 |
FMC_SDNWE | WE# | 写入使能 |
FMC_SDCKE[1:0] | CKE | SDCKE0:SDRAM 存储区域 1 时钟使能 SDCKE1:SDRAM 存储区域 2 时钟使能 |
FMC_SDNE[1:0] | -- | SDNE0:SDRAM 存储区域 1 芯片使能 SDNE1:SDRAM 存储区域 2 芯片使能 |
FMC_NRAS | RAS# | 行地址选通信号 |
FMC_NCAS | CAS# | 列地址选通信号 |
其中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的方法:
-
使用指针直接访问SDRAM的映射地址
-
使用编译器强制指定变量定义的地址
-
使用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;
}
今天是大年初一啦,终于有时间发表一篇完整的文章了,一直都在忙着学习忙着比赛,熬了又熬,但是还好找到方向了!!接下来也要继续努力!!新年快乐!!