• 第26章 FMC—扩展外部SDRAM—零死角玩转STM32-F429系列


    第26章     FMC—扩展外部SDRAM

    全套200集视频教程和1000PDF教程请到秉火论坛下载:www.firebbs.cn

    野火视频教程优酷观看网址:http://i.youku.com/firege

    本章参考资料:《STM32F4xx 中文参考手册2》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。

    关于SDRAM存储器,请参考前面的"常用存储器介绍"章节,实验中SDRAM芯片的具体参数,请参考其规格书《IS42-45S16400J》来了解。

    26.1 SDRAM控制原理

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

    STM32F429系列芯片扩展内存时可以选择SRAMSDRAM,由于SDRAM的"容量/价格"比较高,即使用SDRAM要比SRAM要划算得多。我们以SDRAM为例讲解如何为STM32扩展内存。

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

    261 SDRAM芯片外观

    262 一种SDRAM芯片的内部结构框图

    26.1.1 SDRAM信号线

    262虚线框外引出的是SDRAM芯片的控制引脚,其说明见表 261

    261 SDRAM控制引脚说明

    信号线

    类型

    说明

    CLK

    I

    同步时钟信号,所有输入信号都在CLK为上升沿的时候被采集

    CKE

    I

    时钟使能信号,禁止时钟信号时SDRAM会启动自刷新操作

    CS#

    I

    片选信号,低电平有效

    CAS#

    I

    列地址选通,为低电平时地址线表示的是列地址

    RAS#

    I

    行地址选通,为低电平时地址线表示的是行地址

    WE#

    I

    写入使能,低电平有效

    DQM[0:1]

    I

    数据输入/输出掩码信号,表示DQ信号线的有效部分

    BA[0:1]

    I

    Bank地址输入,选择要控制的Bank

    A[0:11]

    I

    地址输入

    DQ[0:15]

    I/O

    数据输入输出信号

    除了时钟、地址和数据线,控制SDRAM还需要很多信号配合,它们具体作用在描述时序图时进行讲解。

    26.1.2 控制逻辑

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

    26.1.3 地址控制

    SDRAM包含有"A"以及"BA"两类地址线,A类地址线是行(Row)与列(Column)共用的地址总线,BA地址线是独立的用于指定SDRAM内部存储阵列号(Bank)。在命令模式下,A类地址线还用于某些命令输入参数。

    26.1.4 SDRAM的存储阵列

    要了解SDRAM的储存单元寻址以及"A"、"BA"线的具体运用,需要先熟悉它内部存储阵列的结构,见图 263

    263 SDRAM存储阵列模型

    SDRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址和列地址,就可以精确地找到目标单元格,这是SDRAM芯片寻址的基本原理。这样的每个单元格被称为存储单元,而这样的表则被称为存储阵列(Bank),目前设计的SDRAM芯片基本上内部都包含有4个这样的Bank,寻址时指定Bank号以及行地址,然后再指定列地址即可寻找到目标存储单元。SDRAM内部具有多个Bank时的结构见图 264

    264 SDRAM内有多个Bank时的结构图

    SDRAM芯片向外部提供有独立的BA类地址线用于Bank寻址,而行与列则共用A类地址线。

    262标号„中表示的就是它内部的存储阵列结构,通讯时当RAS线为低电平,则"行地址选通器"被选通,地址线A[11:0]表示的地址会被输入到"行地址译码及锁存器"中,作为存储阵列中选定的行地址,同时地址线BA[1:0]表示的Bank也被锁存,选中了要操作的Bank号;接着控制CAS线为低电平,"列地址选通器"被选通,地址线A[11:0]表示的地址会被锁存到"列地址译码器"中作为列地址,完成寻址过程。

    26.1.5 数据输入输出

    若是写SDRAM内容,寻址完成后,DQ[15:0]线表示的数据经过图 262标号…中的输入数据寄存器,然后传输到存储器阵列中,数据被保存;数据输出过程相反。

    本型号的SDRAM存储阵列的"数据宽度"是16(即数据线的数量),在与SDRAM进行数据通讯时,16位的数据是同步传输的,但实际应用中我们可能会以8位、16位的宽度存取数据,也就是说16位的数据线并不是所有时候都同时使用的,而且在传输低宽度数据的时候,我们不希望其它数据线表示的数据被录入。如传输8位数据的时候,我们只需要DQ[7:0]表示的数据,而DQ[15:8]数据线表示的数据必须忽略,否则会修改非目标存储空间的内容。所以数据输入输出时,还会使用DQM[1:0]线来配合,每根DQM线对应8位数据,如"DQM0(LDQM)"为低电平,"DQM1(HDQM)"为高电平时,数据线DQ[7:0]表示的数据有效,而DQ[15:8]表示的数据无效。

    26.1.6 SDRAM的命令

    控制SDRAM需要用到一系列的命令,见表 262。各种信号线状态组合产生不同的控制命令。

    262 SDRAM命令表

    命令名

    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

    表中的H表示高电平,L表示低电平,X表示任意电平,High-Z表示高阻态。

    1.    命令禁止

    只要CS引脚为高电平,即表示"命令禁止"(COMMAND INHBIT),它用于禁止SDRAM执行新的命令,但它不能停止当前正在执行的命令。

    2.    空操作

    "空操作"(NO OPERATION),"命令禁止"的反操作,用于选中SDRAM,以便接下来发送命令。

    3.    行有效

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

    265 行有效命令时序图

    4.    列读写

    行地址通过"行有效"命令确定后,就要对列地址进行寻址了。"读命令"(READ)和"写命令"(WRITE)的时序很相似,见图 266,通过共用的地址线A发送列地址,同时使用WE引脚表示读/写方向,WE为低电平时表示写,高电平时表示读。数据读写时,使用DQM线表示有效的DQ数据线。

    266 读取命令时序

    本型号的SDRAM芯片表示列地址时仅使用A[7:0]线,而A10线用于控制是否"自动预充电",该线为高电平时使能,低电平时关闭。

    5.    预充电

     SDRAM 的寻址具有独占性,所以在进行完读写操作后,如果要对同一个Bank 的另一行进行寻址,就要将原来有效(ACTIVE)的行关闭,重新发送行/列地址。Bank 关闭当前工作行,准备打开新行的操作就是预充电(Precharge)。

    预充电可以通过独立的命令控制,也可以在每次发送读写命令的同时使用"A10"线控制自动进行预充电。实际上,预充电是一种对工作行中所有存储阵列进行数据重写,并对行地址进行复位,以准备新行的工作。

    独立的预充电命令时序见图 267。该命令配合使用A10线控制,若A10为高电平时,所有Bank都预充电;A10为低电平时,使用BA线选择要预充电的Bank

    267 PRECHARGE命令时序

    6.    刷新

    SDRAM要不断进行刷新(Refresh)才能保留住数据,因此它是 DRAM 最重要的操作。刷新操作与预充电中重写的操作本质是一样的。

    但因为预充电是对一个或所有Bank 中的工作行操作,并且不定期,而刷新则是有固定的周期,依次对所有行进行操作,以保证那些久久没被访问的存储单元数据正确。

    刷新操作分为两种:"自动刷新"(Auto Refresh)与"自我刷新"(Self Refresh),发送命令后CKE时钟为有效时(低电平),使用自动刷新操作,否则使用自我刷新操作。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。

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

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

    7.    加载模式寄存器

    前面提到SDRAM的控制逻辑是根据它的模式寄存器来管理整个系统的,而这个寄存器的参数就是通过"加载模式寄存器"命令(LOAD MODE REGISTER)来配置的。发送该命令时,使用地址线表示要存入模式寄存器的参数"OP-Code",各个地址线表示的参数见图 268

    268 模式寄存器解析图

    模式寄存器的各个参数介绍如下:

    Burst Length

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

    上文讲到的读/写操作,都是一次对一个存储单元进行寻址,如果要连续读/写就还要对当前存储单元的下一个单元进行寻址,也就是要不断的发送列地址与读/写命令(行地址不变,所以不用再对行寻址)。虽然由于读/写延迟相同可以让数据的传输在 I/O 端是连续的,但它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。

    为此,人们开发了突发传输技术,只要指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一笔数据的传输需要若干个周期外,其后每个数据只需一个周期的即可获得。其实我们在EERPOMFLASH读写章节讲解的按页写入就是突发写入,而它们的读取过程都是突发性质的。

    非突发连续读取模式:不采用突发传输而是依次单独寻址,此时可等效于 BL=1。虽然也可以让数据连续地传输,但每次都要发送列地址与命令信息,控制资源占用极大。突发连续读取模式:只要指定起始列地址与突发长度,寻址与数据的读取自动进行,而只要控制好两段突发读取命令的间隔周期( BL 相同)即可做到连续的突发传输。BL 的数值,也是不能随便设或在数据进行传输前临时决定。在初始化SDRAM调用LOAD MODE REGISTER命令时就被固定。BL可用的选项是 1248,常见的设定是 4 8。若传输时实际需要数据长度小于设定的BL值,则调用"突发停止"(BURST TERMINATE)命令结束传输。

    BT

    模式寄存器中的BT位用于设置突发模式,突发模式分为顺序(Sequential)与间隔(Interleaved)两种。在顺序方式中,操作按地址的顺序连续执行,如果是间隔模式,则操作地址是跳跃的。跳跃访问的方式比较乱,不太符合思维习惯,我们一般用顺序模式。顺序访问模式时按照"0-1-2-3-4-5-6-7"的地址序列访问。

    CASLatency

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

    269 CL=2CL=3的说明图

    CL只是针对读命令时的数据延时,在写命令是不需要这个延时的,发出写命令时可同时发送要写入的数据。

    Op Mode

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

    WB

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

    Reserved

    模式寄存器的最后三位的被保留,没有设置参数。

    26.1.7 SDRAM的初始化流程

    最后我们来了解SDRAM的初始化流程。SDRAM并不是上电后立即就可以开始读写数据的,它需要按步骤进行初始化,对存储矩阵进行预充电、刷新并设置模式寄存器,见图 2610

    2610 SDRAM初始化流程

    该流程说明如下:

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

    (2)    发送"空操作"(NOP)命令;

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

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

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

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

    其中tRPtRFCtMRD等时间参数跟具体的SDRAM有关,可查阅其数据手册获知,STM32 FMC访问时配置需要这些参数。

    26.1.8 SDRAM的读写流程

    初始化步骤完成,开始读写数据,其时序流程见图 2611及图 2612

    2611 CL=2时,带AUTO PRECHARGE的读时序

    2612 AUTO PRECHARGE 命令的写时序

    读时序和写时序的命令过程很类似,下面我们统一解说:

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

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

    (3)    执行"预充电"(auto precharge)命令后,需要等待tRP时间,tRP表示预充电与其它命令之间的延迟;

    (4)    图中的标号„处的tRAS,表示自刷新周期,即在前一个"行有效"与"预充电"命令之间的时间;

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

    其中tRCDtWRtRPtRAS以及tRC等时间参数跟具体的SDRAM有关,可查阅其数据手册获知,STM32 FMC访问时配置需要这些参数。

    26.2 FMC简介

    STM32F429使用FMC外设来管理扩展的存储器,FMCFlexible Memory Controller的缩写,译为可变存储控制器。它可以用于驱动包括SRAMSDRAMNOR FLASH以及NAND FLSAH类型的存储器。在其它系列的STM32控制器中,只有FSMC控制器(Flexible Static Memory Controller),译为可变静态存储控制器,所以它们不能驱动SDRAM这样的动态存储器,因为驱动SDRAM时需要定时刷新,STM32F429FMC外设才支持该功能,且只支持普通的SDRAM,不支持DDR类型的SDRAM。我们只讲述FMCSDRAM控制功能。

    26.3

    FMC框图剖析

    STM32FMC外设内部结构见图 2613

    2613 FMC控制器框图

    1.    通讯引脚

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

    263 FMC中的SDRAM控制信号线

    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

    SDCKE0SDRAM 存储区域 1 时钟使能

    SDCKE1SDRAM 存储区域 2 时钟使能

    FMC_SDNE[1:0]

    --

    SDNE0SDRAM 存储区域 1 芯片使能

    SDNE1SDRAM 存储区域 2 芯片使能

    FMC_NRAS

    RAS#

    行地址选通信号

    FMC_NCAS

    CAS#

    列地址选通信号

    其中比较特殊的是FMC_A[15:14]引脚用作Bank的寻址线;而FMC_SDCKE线和FMC_SDNE都各有2条,FMC_SDCKE用于控制SDRAM的时钟使能,FMC_SDNE用于控制SDRAM芯片的片选使能。它们用于控制STM32使用不同的存储区域驱动SDRAM,使用编号为0的信号线组会使用STM32的存储器区域1,使用编号为1的信号线组会使用存储器区域2。使用不同存储区域时,STM32访问SDRAM的地址不一样,具体将在"FMC的地址映射"小节讲解。

    2.    存储器控制器

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

    控制SDRAM的有FMC_SDCR1/FMC_SDCR2控制寄存器、FMC_SDTR1/FMC_SDTR2时序寄存器、FMC_SDCMR命令模式寄存器以及FMC_SDRTR刷新定时器寄存器。其中控制寄存器及时序寄存器各有2个,分别对应于SDRAM存储区域1和存储区域2的配置。

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

    FMC_SDTR时序寄存器用于配置SDRAM访问时的各种时间延迟,如TRP行预充电延迟、TMRD加载模式寄存器激活延迟等。

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

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

    3.    时钟控制逻辑

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

    26.4 FMC的地址映射

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

    2614 FMC的地址映射

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

    1.    SDRAM的存储区域

    FMCSDRAM的存储区域分成了Bank1Bank2两块,这里的BankSDRAM芯片内部的Bank是不一样的概念,只是FMC的地址区域划分而已。每个Bank有不一样的起始地址,且有独立的FMC_SDCR控制寄存器和FMC_SDTR时序寄存器,还有独立的FMC_SDCKE时钟使能信号线和FMC_SDCLK信号线。FMC_SDCKE0FMC_SDCLK0对应的存储区域1的地址范围是0xC000 0000-0xCFFF FFFF,而FMC_SDCKE1FMC_SDCLK1对应的存储区域2的地址范围是0xD000 0000- 0xDFFF FFFF。当程序里控制内核访问这些地址的存储空间时,FMC外设会即会产生对应的时序,对它外接的SDRAM芯片进行读写。

    2.    External RAM 与External device的区别

    比较遗憾的是FMCSDRAM分配的区域不在External RAM区,这个区域可以直接执行代码,而SDRAM所在的External device区却不支持这个功能。这里说的可直接执行代码的特性就是在"常用存储器"章节介绍的XIP(eXecute In Place)特性,即存储器上若存储了代码,CPU可直接访问代码执行,无需缓存到其它设备上再运行;而且XIP特性还对存储器的种类有要求,SRAM/SDRAMNOR Flash都支持这种特性,而NAND FLASHPC卡是不支持XIP的。结合存储器的特性和STM32 FMC存储器种类的地址分配,就发现它的地址规划不合理了,NAND FLASHPC卡这些不支持XIP的存储器却占据了External RAM的空间,而支持XIPSDRAM存储器的空间却被分配到了Extern device区。为了解决这个问题,通过配置"SYSCFG_MEMRMP"寄存器的"SWP_FMC"寄存器位可用于交换SDRAMNAND/PC卡的地址映射,使得存储在SDRAM中的代码能被执行,只是由于SDRAM的最高同步时钟是90MHz,代码的执行速度会受影响。

    本章主要讲解当STM32的片内SRAM不够用时使用SDRAM扩展内存,但假如程序太大,它的程序空间FLASH不够用怎么办呢?首先是裁剪代码,目前STM32F429系列芯片内部FLASH空间最高可达2MB,实际应用中只要我们把代码中的图片、字模等占据大空间的内容放到外部存储器中,纯粹的代码很难达到2MB。如果还不够用,非要扩展程序空间的话,一种方法是使用FMC扩展NOR FLASH,把程序存储到NOR上,程序代码能够直接在NOR FLASH上执行。另一种方法是把程序存储在其它外部存储器,如SD卡,需要时把存储在SD卡上的代码加载到SRAMSDRAM上,再在RAM上执行代码。

    如果SDRAM不是用于存储可执行代码,只是用来保存数据的话,在External RAMExteranl device区域都没有区别,不需要与NAND的映射地址交换。

    26.5 SDRAM时序结构体

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

    SDRAM时序结构体的成员见代码清单 241

    代码清单 261 SDRAM时序结构体FMC_SDRAMTimingInitTypeDef

    1 /* @brief 控制SDRAM的时序参数,这些参数的单位都是"周期"

    2 * 各个参数的值可设置为1-16个周期。 */

    3 typedef struct

    4 {

    5 uint32_t FMC_LoadToActiveDelay; /*TMRD:加载模式寄存器命令后的延迟*/

    6 uint32_t FMC_ExitSelfRefreshDelay; /*TXSR:自刷新命令后的延迟 */

    7 uint32_t FMC_SelfRefreshTime; /*TRAS:自刷新时间*/

    8 uint32_t FMC_RowCycleDelay; /*TRC:行循环延迟*/

    9 uint32_t FMC_WriteRecoveryTime; /*TWR:恢复延迟 */

    10 uint32_t FMC_RPDelay; /*TRP:行预充电延迟*/

    11 uint32_t FMC_RCDDelay; /*TRCD:行到列延迟*/

    12 } FMC_SDRAMTimingInitTypeDef;

    这个结构体成员定义的都是SDRAM发送各种命令后必须的延迟,它的配置对应到FMC_SDTR中的寄存器位。所有成员参数值的单位是周期,参数值大小都可设置成"1-16"。关于这些延时时间的定义可以看"SDRAM初始化流程"和"SDRAM读写流程"小节的时序图了解。具体参数值根据SDRAM芯片的手册说明来配置。各成员介绍如下:

    (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),即行有效命令到列读写命令之间的延迟。

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

    26.6 SDRAM初始化结构体

    FMCSDRAM初始化结构体见代码清单 262

    代码清单 262 SDRAM初始化结构体FMC_SDRAMInitTypeDef

    1 /* @brief FMC SDRAM 初始化结构体类型定义 */

    2 typedef struct

    3 {

    4 uint32_t FMC_Bank; /*选择FMCSDRAM存储区域*/

    5 uint32_t FMC_ColumnBitsNumber; /*定义SDRAM的列地址宽度 */

    6 uint32_t FMC_RowBitsNumber; /*定义SDRAM的行地址宽度 */

    7 uint32_t FMC_SDMemoryDataWidth; /*定义SDRAM的数据宽度 */

    8 uint32_t FMC_InternalBankNumber; /*定义SDRAM内部的Bank数目 */

    9 uint32_t FMC_CASLatency; /*定义CASLatency的时钟个数*/

    10 uint32_t FMC_WriteProtection; /*定义是否使能写保护模式 */

    11 uint32_t FMC_SDClockPeriod; /*配置同步时钟SDCLK的参数*/

    12 uint32_t FMC_ReadBurst; /*是否使能突发读模式*/

    13 uint32_t FMC_ReadPipeDelay; /*定义在CAS个延迟后再等待多

    14 少个HCLK时钟才读取数据 */

    15 FMC_SDRAMTimingInitTypeDef* FMC_SDRAMTimingStruct; /*定义SDRAM的时序参数*/

    16 } FMC_SDRAMInitTypeDef;

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

    (1)    FMC_Bank

    本成员用于选择FMC映射的SDRAM存储区域,可选择存储区域12 (FMC_Bank1/2_SDRAM)

    (2)    FMC_ColumnBitsNumber

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

    (3)     FMC_RowBitsNumber

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

    (4)    FMC_SDMemoryDataWidth

    本成员用于设置要控制的SDRAM的数据宽度,可选择设置成81632(FMC_SDMemory_Width_8/16/32b)

    (5)     FMC_InternalBankNumber

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

    (6)     FMC_CASLatency

    本成员用于设置CASLatencyCL的时钟数目,可选择设置为123个时钟周期(FMC_CAS_Latency_1/2/3)

    (7)     FMC_WriteProtection

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

    (8)     FMC_SDClockPeriod

    本成员用于设置FMC与外部SDRAM通讯时的同步时钟参数,可以设置成STM32HCLK时钟频率的1/21/3或禁止输出时钟(FMC_SDClock_Period_2/3FMC_SDClock_Disable)

    (9)     FMC_ReadBurst

    本成员用于设置是否使能突发读取模式,禁止时等效于BL=1,使能时BL的值等于模式寄存器中的配置。

    (10)     FMC_ReadPipeDelay

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

    (11)     FMC_SDRAMTimingStruct

    这个成员就是我们上一小节讲解的SDRAM时序结构体了,设置完时序结构体后再把赋值到这里即可。

    配置完SDRAM初始化结构体后,调用FMC_SDRAMInit函数把这些配置写入到FMCSDRAM控制寄存器及时序寄存器,实现FMC的初始化。

    26.7 SDRAM命令结构体

    控制SDRAM时需要各种命令,通过向FMC的命令模式寄存器FMC_SDCMR写入控制参数,即可控制FMC对外发送命令,为了方便使用,STM32标准库也把它封装成了结构体,见代码清单 263

    代码清单 263 SDRAM命令结构体

    1 typedef struct

    2 {

    3 uint32_t FMC_CommandMode; /*要发送的命令 */

    4 uint32_t FMC_CommandTarget; /*目标存储器区域 */

    5 uint32_t FMC_AutoRefreshNumber; /*若发送的是自动刷新命令,

    6 此处为发送的刷新次数,其它命令时无效 */

    7 uint32_t FMC_ModeRegisterDefinition; /*若发送的是加载模式寄存器命令,

    8 此处为要写入SDRAM模式寄存器的参数 */

    9 } FMC_SDRAMCommandTypeDef;

    命令结构体中的各个成员介绍如下:

    (1)    FMC_CommandMode

    本成员用于配置将要发送的命令,它可以被赋值为表 264中的宏,这些宏代表了不同命令;

    264 FMC可输出的SDRAM控制命令

    命令说明

    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存储区域,可选择存储区域12(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外设就会发送相应的命令了。

    26.8 FMC—扩展外部SDRAM实验

    本小节以型号为"IS42S16400J"的SDRAM芯片为STM32扩展内存。它的行地址宽度为12位,列地址宽度为8位,内部含有4Bank,数据线宽度为16位,容量大小为8MB

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

    26.8.1 硬件设计

    2615 SDRAM硬件连接图

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

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

    26.8.2 软件设计

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

    1.    编程要点

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

    (14)    使能FMC外设的时钟;

    (15)    配置FMC SDRAM的时序、工作模式;

    (16)    根据SDRAM的初始化流程编写初始化函数;

    (17)    建立机制访问外部SDRAM存储器;

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

    2.    代码分析
    FMC硬件相关宏定义

    我们把FMC SDRAM硬件相关的配置都以宏的形式定义到"bsp_sdram.h"文件中,见代码清单 242

    代码清单 264 SDRAM硬件配置相关的宏(省略了大部分数据线)

    1 /*A行列地址信号线*/

    2 #define FMC_A0_GPIO_PORT GPIOF

    3 #define FMC_A0_GPIO_CLK RCC_AHB1Periph_GPIOF

    4 #define FMC_A0_GPIO_PIN GPIO_Pin_0

    5 #define FMC_A0_PINSOURCE GPIO_PinSource0

    6 #define FMC_A0_AF GPIO_AF_FMC

    7 /*......*/

    8 /*此处省略A1-A11信号线的宏,具体可参考工程中的代码*/

    9 /*BA地址线*/

    10 #define FMC_BA0_GPIO_PORT GPIOG

    11 #define FMC_BA0_GPIO_CLK RCC_AHB1Periph_GPIOG

    12 #define FMC_BA0_GPIO_PIN GPIO_Pin_4

    13 #define FMC_BA0_PINSOURCE GPIO_PinSource4

    14 #define FMC_BA0_AF GPIO_AF_FMC

    15 /*......*/

    16 /*此处省略BA1信号线的宏,具体可参考工程中的代码*/

    17

    18 /*DQ数据信号线*/

    19 #define FMC_D0_GPIO_PORT GPIOD

    20 #define FMC_D0_GPIO_CLK RCC_AHB1Periph_GPIOD

    21 #define FMC_D0_GPIO_PIN GPIO_Pin_14

    22 #define FMC_D0_PINSOURCE GPIO_PinSource14

    23 #define FMC_D0_AF GPIO_AF_FMC

    24 /*......*/

    25 /*此处省略D1-A15信号线的宏,具体可参考工程中的代码*/

    26

    27 /*控制信号线*/

    28 /*CS片选*/

    29 #define FMC_CS_GPIO_PORT GPIOH

    30 #define FMC_CS_GPIO_CLK RCC_AHB1Periph_GPIOH

    31 #define FMC_CS_GPIO_PIN GPIO_Pin_6

    32 #define FMC_CS_PINSOURCE GPIO_PinSource6

    33 #define FMC_CS_AF GPIO_AF_FMC

    34 /*WE写使能*/

    35 #define FMC_WE_GPIO_PORT GPIOC

    36 #define FMC_WE_GPIO_CLK RCC_AHB1Periph_GPIOC

    37 #define FMC_WE_GPIO_PIN GPIO_Pin_0

    38 #define FMC_WE_PINSOURCE GPIO_PinSource0

    39 #define FMC_WE_AF GPIO_AF_FMC

    40 /*RAS行选通*/

    41 #define FMC_RAS_GPIO_PORT GPIOF

    42 #define FMC_RAS_GPIO_CLK RCC_AHB1Periph_GPIOF

    43 #define FMC_RAS_GPIO_PIN GPIO_Pin_11

    44 #define FMC_RAS_PINSOURCE GPIO_PinSource11

    45 #define FMC_RAS_AF GPIO_AF_FMC

    46 /*CAS列选通*/

    47 #define FMC_CAS_GPIO_PORT GPIOG

    48 #define FMC_CAS_GPIO_CLK RCC_AHB1Periph_GPIOG

    49 #define FMC_CAS_GPIO_PIN GPIO_Pin_15

    50 #define FMC_CAS_PINSOURCE GPIO_PinSource15

    51 #define FMC_CAS_AF GPIO_AF_FMC

    52 /*CLK同步时钟,存储区域2*/

    53 #define FMC_CLK_GPIO_PORT GPIOG

    54 #define FMC_CLK_GPIO_CLK RCC_AHB1Periph_GPIOG

    55 #define FMC_CLK_GPIO_PIN GPIO_Pin_8

    56 #define FMC_CLK_PINSOURCE GPIO_PinSource8

    57 #define FMC_CLK_AF GPIO_AF_FMC

    58 /*CKE时钟使能,存储区域2*/

    59 #define FMC_CKE_GPIO_PORT GPIOH

    60 #define FMC_CKE_GPIO_CLK RCC_AHB1Periph_GPIOH

    61 #define FMC_CKE_GPIO_PIN GPIO_Pin_7

    62 #define FMC_CKE_PINSOURCE GPIO_PinSource7

    63 #define FMC_CKE_AF GPIO_AF_FMC

    64

    65 /*DQM1数据掩码*/

    66 #define FMC_UDQM_GPIO_PORT GPIOE

    67 #define FMC_UDQM_GPIO_CLK RCC_AHB1Periph_GPIOE

    68 #define FMC_UDQM_GPIO_PIN GPIO_Pin_1

    69 #define FMC_UDQM_PINSOURCE GPIO_PinSource1

    70 #define FMC_UDQM_AF GPIO_AF_FMC

    71 /*DQM0数据掩码*/

    72 #define FMC_LDQM_GPIO_PORT GPIOE

    73 #define FMC_LDQM_GPIO_CLK RCC_AHB1Periph_GPIOE

    74 #define FMC_LDQM_GPIO_PIN GPIO_Pin_0

    75 #define FMC_LDQM_PINSOURCE GPIO_PinSource0

    76 #define FMC_LDQM_AF GPIO_AF_FMC

    以上代码根据硬件的连接,把与SDRAM通讯使用的引脚号、引脚源以及复用功能映射都以宏封装起来。其中FMC_CKEFMC_CLK引脚对应的是FMC的存储区域2,所以后面我们对SDRAM的寻址空间也是要指向存储区域2的。

    初始化FMC的 GPIO

    利用上面的宏,编写FMCGPIO引脚初始化函数,见代码清单 243

    代码清单 265 FMCGPIO初始化函数(省略了大部分数据线)

     1 /**
    				

    2 * @brief 初始化控制SDRAMIO

    3 * @param

    4 * @retval

    5 */

    6 static void SDRAM_GPIO_Config(void)

    7 {

    8 GPIO_InitTypeDef GPIO_InitStructure;

    9

    10 /*此处省略大量地址线、数据线以及控制信号线,

    11 它们的时钟配置都相同,具体请查看工程中的代码*/

    12 /* 使能SDRAM相关的GPIO时钟 */

    13 /*地址信号线*/

    14 RCC_AHB1PeriphClockCmd(FMC_A0_GPIO_CLK | /*...*/

    15 /*数据信号线*/ /*控制信号线*/

    16 FMC_D0_GPIO_CLK |FMC_CS_GPIO_CLK | , ENABLE);

    17

    18 /*--所有GPIO的配置都相同,此处省略大量引脚初始化,具体请查看工程中的代码*/

    19 /* 通用 GPIO 配置 */

    20 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //配置为复用功能

    21 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    22 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出

    23 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

    24

    25 /*A行列地址信号线针对引脚配置*/

    26 GPIO_InitStructure.GPIO_Pin = FMC_A0_GPIO_PIN;

    27 GPIO_Init(FMC_A0_GPIO_PORT, &GPIO_InitStructure);

    28 GPIO_PinAFConfig(FMC_A0_GPIO_PORT, FMC_A0_PINSOURCE , FMC_A0_AF);

    29 /*...*/

    30 /*DQ数据信号线针对引脚配置*/

    31 GPIO_InitStructure.GPIO_Pin = FMC_D0_GPIO_PIN;

    32 GPIO_Init(FMC_D0_GPIO_PORT, &GPIO_InitStructure);

    33 GPIO_PinAFConfig(FMC_D0_GPIO_PORT, FMC_D0_PINSOURCE , FMC_D0_AF);

    34 /*...*/

    35 /*控制信号线*/

    36 GPIO_InitStructure.GPIO_Pin = FMC_CS_GPIO_PIN;

    37 GPIO_Init(FMC_CS_GPIO_PORT, &GPIO_InitStructure);

    38 GPIO_PinAFConfig(FMC_CS_GPIO_PORT, FMC_CS_PINSOURCE , FMC_CS_AF);

    39 /*...*/

    40 }

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

    配置FMC的模式

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

    代码清单 266 配置FMC的模式

    1 /**

    2 * @brief 初始化配置使用SDRAMFMCGPIO接口,

    3 * 本函数在SDRAM读写操作前需要被调用

    4 * @param None

    5 * @retval None

    6 */

    7 void SDRAM_Init(void)

    8 {

    9 FMC_SDRAMInitTypeDef FMC_SDRAMInitStructure;

    10 FMC_SDRAMTimingInitTypeDef FMC_SDRAMTimingInitStructure;

    11

    12 /* 配置FMC接口相关的 GPIO*/

    13 SDRAM_GPIO_Config();

    14

    15 /* 使能 FMC 时钟 */

    16 RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);

    17

    18 /* SDRAM时序结构体,根据SDRAM参数表配置----------------*/

    19 /* SDCLK 90 Mhz (HCLK/2 :180Mhz/2) 1个时钟周期Tsdclk =1/90MHz=11.11ns*/

    20 /* TMRD: 2 Clock cycles */

    21 FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;

    22 /* TXSR: min=70ns (7x11.11ns) */

    23 FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;

    24 /* TRAS: min=42ns (4x11.11ns) max=120k (ns) */

    25 FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;

    26 /* TRC: min=70 (7x11.11ns) */

    27 FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7;

    28 /* TWR: min=1+ 7ns (1+1x11.11ns) */

    29 FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;

    30 /* TRP: 15ns => 2x11.11ns */

    31 FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;

    32 /* TRCD: 15ns => 2x11.11ns */

    33 FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;

    34

    35 /* FMC SDRAM 控制配置 */

    36 /*选择存储区域*/

    37 FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;

    38 /* 行地址线宽度: [7:0] */

    39 FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;

    41 /* 列地址线宽度: [11:0] */

    42 FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b;

    43 /* 数据线宽度 */

    44 FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = SDRAM_MEMORY_WIDTH;

    45 /* SDRAM内部bank数量*/

    46 FMC_SDRAMInitStructure.FMC_InternalBankNumber =FMC_InternalBank_Number_4;

    48 /* CAS潜伏期 */

    49 FMC_SDRAMInitStructure.FMC_CASLatency = FMC_CAS_Latency_2;

    50 /* 禁止写保护*/

    51 FMC_SDRAMInitStructure.FMC_WriteProtection =

    52 FMC_Write_Protection_Disable;

    53 /* SDCLK时钟分频因子,SDCLK = HCLK/SDCLOCK_PERIOD*/

    54 FMC_SDRAMInitStructure.FMC_SDClockPeriod = FMC_SDClock_Period_2;

    55 /* 突发读模式设置*/

    56 FMC_SDRAMInitStructure.FMC_ReadBurst = FMC_Read_Burst_Enable;

    57 /* 读延迟配置 */

    58 FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_0;

    59 /* SDRAM时序参数 */

    60 FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct =&FMC_SDRAMTimingInitStructure;

    62

    63 /* 调用初始化函数,向寄存器写入配置 */

    64 FMC_SDRAMInit(&FMC_SDRAMInitStructure);

    65

    66 /* 执行FMC SDRAM的初始化流程*/

    67 SDRAM_InitSequence();

    68 }

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

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

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

    (2)    时序结构体赋值

    接下来对时序结构体FMC_SDRAMTimingInitStructure赋值。在前面我们了解到时序结构体各个成员值的单位是同步时钟SDCLK的周期数,而根据我们使用的SDRAM芯片,可查询得它对这些时序要求,见表 265

    265 SDRAM的延时参数(摘自《IS42-45S16400J》规格书)

    时间参数

    说明

    最小值

    单位

    trc

    两个刷新命令或两个行有效命令之间的延迟

    63

    ns

    tras

    行有效与预充电命令之间的延迟

    42

    ns

    trp

    预充电与行有效命令之间的延迟

    15

    ns

    trcd

    行有效与列读写命令之间的延迟

    15

    ns

    twr

    写入命令到预充电命令之间的延迟

    2

    cycle

    txsr

    退出自我刷新到行有效命令之间的延迟

    70

    ns

    tmrd

    加载模式寄存器命令与行有效或刷新命令之间的延迟

    2

    cycle

    部分时间参数以ns为单位,因此我们需要进行单位转换,而以SDCLK时钟周期数(cycle)为单位的时间参数,直接赋值到时序结构体成员里即可。

    由于我们配置FMC输出的SDCLK时钟频率为HCLK1/2(在后面的程序里配置的),即FSDCLK=90MHz,可得1SDCLK时钟周期长度为TSDCLK=1/FSDCLK =11.11ns,然后设置各个成员的时候,只要保证时间大于以上SDRAM延时参数表的要求即可。如trc要求大于63ns,而11.11ns x 7=77.77ns,所以FMC_RowCycleDelay(TRC)成员值被设置为7个时钟周期,依葫芦画瓢完成时序参数的设置。

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

    函数接下来对FMC SDRAM的初始化结构体赋值。包括行列地址线宽度、数据线宽度、SDRAM内部Bank数量以及CL长度,这些都是根据外接的SDRAM的特性设置的,其中CL长度要与后面初始化流程中给SDRAM模式寄存器中的赋值一致。

        设置存储区域

    FMC_Bank成员设置FMCSDRAM存储区域映射选择为FMC_Bank2_SDRAM,这是由于我们的SDRAM硬件连接到FMC_CKE1FMC_CLK1,所以对应到存储区域2

         行地址、列地址、数据线宽度及内部Bank数量

    这些结构体成员都是根据SDRAM芯片的特性配置的,行地址宽度为8位,列地址宽度为12位,数据线宽度为16位,SDRAM内部有4Bank

         CL长度

    CL的长度这里被设置为2个同步时钟周期,它需要与后面SDRAM模式寄存器中的配置一样;

         写保护

    FMC_WriteProtection用于设置写保护,如果使能了这个功能是无法向SDRAM写入数据的,所以我们关闭这个功能;

        同步时钟参数

    FMC_SDClockPeriod成员被设置为FMC_SDClock_Period_2 ,所以同步时钟的频率就被设置为HCLK1/2了;

         突发读模式及读延迟

    为了加快读取速度,我们使能突发读功能,且读延迟周期为0

        时序参数

    最后向FMC_SDRAMTimingStruct赋值为前面的时序结构体,包含了我们设定的SDRAM时间参数。

        赋值完成后调用库函数FMC_SDRAMInit把初始化结构体配置的各种参数写入到FMC_SDCR控制寄存器及FMC_SDTR时序寄存器中。函数的最后调用SDRAM_InitSequence函数实现执行SDRAM的上电初始化时序。

    实现SDRAM的初始化时序

    在上面配置完成STM32FMC外设参数后,在读写SDRAM前还需要执行前面介绍的SDRAM上电初始化时序,它就是由SDRAM_InitSequence函数实现的,见代码清单 458

    代码清单 267 SDRAM上电初始化时序

    1 /**

    2 * @brief SDRAM芯片进行初始化配置

    3 * @param None.

    4 * @retval None.

    5 */

    6 static void SDRAM_InitSequence(void)

    7 {

    8 FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure;

    9 uint32_t tmpr = 0;

    10

    11 /* Step 3 -----------------------------------------------*/

    12 /* 配置命令:开启提供给SDRAM的时钟 */

    13 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled;

    14 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;

    15 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;

    16 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;

    17 /* 检查SDRAM标志,等待至SDRAM空闲 */

    18 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);

    19 /* 发送上述命令*/

    20 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);

    21

    22 /* Step 4 ---------------------------------------------*/

    23 /*延时 */

    24 SDRAM_delay(10);

    25

    26 /* Step 5 -------------------------------------------*/

    27 /* 配置命令:对所有的bank预充电 */

    28 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL;

    29 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;

    30 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;

    31 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;

    32 /* 检查SDRAM标志,等待至SDRAM空闲 */

    33 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);

    34 /* 发送上述命令*/

    35 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);

    36

    37 /* Step 6 --------------------------------------------*/

    38 /* 配置命令:自动刷新 */

    39 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh;

    40 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;

    41 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 2;

    42 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;

    43 /* 检查SDRAM标志,等待至SDRAM空闲 */

    44 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);

    45 /* 发送自动刷新命令*/

    46 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);

    47

    48 /* Step 7 ----------------------------------------------*/

    49 /* 设置sdram寄存器配置 */

    50 tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_8 |

    51 SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |

    52 SDRAM_MODEREG_CAS_LATENCY_2 |

    53 SDRAM_MODEREG_OPERATING_MODE_STANDARD |

    54 SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;

    55

    56 /* 配置命令:设置SDRAM寄存器 */

    57 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode;

    58 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;

    59 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;

    60 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr;

    61 /* 检查SDRAM标志,等待至SDRAM空闲 */

    62 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);

    63

    64 /* 发送上述命令*/

    65 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);

    66

    67 /* Step 8 --------------------------------------------*/

    68

    69 /* 设置刷新计数器 */

    70 /*刷新速率 = (COUNT + 1) x SDRAM 频率时钟

    71 COUNT = SDRAM 刷新周期/行数) - 20*/

    72 /* 64ms/4096=15.62us (15.62 us x FSDCLK) - 20 =1386 */

    73 FMC_SetRefreshCount(1386);

    74 /* 发送上述命令*/

    75 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);

    76 }

    SDRAM的初始化流程实际上是发送一系列控制命令,利用命令结构体FMC_SDRAMCommandTypeDef及库函数FMC_SDRAMCmdConfig配合即可发送各种命令。函数中按次序发送了使能CLK命令、预充电命令、2个自动刷新命令以及加载模式寄存器命令,每次发调用FMC_SDRAMCmdConfig发送命令后需要调用库函数FMC_GetFlagStatus检查BUSY标志位,等待上一个命令操作完毕。

    其中发送加载模式寄存器命令时使用了一些自定义的宏,使用这些宏组合起来然后赋值到命令结构体的FMC_ModeRegisterDefinition成员中,这些宏定义见代码清单 268

    代码清单 268 加载模式寄存器命令相关的宏

    1 /**

    2 * @brief FMC SDRAM 模式配置的寄存器相关定义

    3 */

    4 #define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)

    5 #define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)

    6 #define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)

    7 #define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)

    8 #define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)

    9 #define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)

    10 #define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)

    11 #define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)

    12 #define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)

    13 #define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)

    14 #define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)

    这些宏是根据"SDRAM的模式寄存器"的位定义的,例如突发长度、突发模式、CL长度、SDRAM工作模式以及突发写模式,其中的CL长度注意要与前面FMC SDRAN初始化结构体中定义的一致。

    设置自动刷新周期

    在上面SDRAM_InitSequence函数的最后,我们还调用了库函数FMC_SetRefreshCount设置FMC自动刷新周期,这个函数会向刷新定时寄存器FMC_SDRTR写入计数值,这个计数值每个SDCLK周期自动减1,减至0FMC会自动向SDRAM发出自动刷新命令,控制SDRAM刷新,SDRAM每次收到刷新命令后,刷新一行,对同一行进行刷新操作的时间间隔称为SDRAM的刷新周期。

    根据STM32F4xx 中文参考手册的说明,COUNT值的计算公式如下:

    刷新速率 = (COUNT + 1) x SDRAM 频率时钟

    COUNT =SDRAM 刷新周期/行数) – 20

    而查询我们的SDRAM芯片规格书,可知它的SDRAM刷新周期为64ms,行数为4096,可算出它的SDRAM刷新要求:

    TRefresh = 64ms/4096=15.62us

    即每隔15.62us需要收到一次自动刷新命令。

    所以:

    COUNTA = TRefresh/TSDCLK=15.62x90=1406

    但是根据要求,如果SDRAM在接受读请求后出现内部刷新请求,则必须将刷新速率增加 20 SDRAM 时钟周期以获得重充足的裕量。

    最后计算出:COUNT=COUNTA-20=1386

    以上就是函数FMC_SetRefreshCount参数值的计算过程。

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

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

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

    代码清单 269 使用指针的方式访问SDRAM

    1 /*SDRAM起始地址存储空间2的起始地址*/

    2 #define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)

    3 /*SDRAM大小,8M字节*/

    4 #define IS42S16400J_SIZE 0x800000

    5

    6 uint32_t temp;

    7

    8 /*SDRAM写入8位数据*/

    9 *( uint8_t*) (SDRAM_BANK_ADDR ) = (uint8_t)0xAA;

    10 /*SDRAM读取数据*/

    11 temp = *( uint8_t*) (SDRAM_BANK_ADDR );

    12

    13 /*/ 16位数据*/

    14 *( uint16_t*) (SDRAM_BANK_ADDR+10 ) = (uint16_t)0xBBBB;

    15 temp = *( uint16_t*) (SDRAM_BANK_ADDR+10 );

    16

    17 /*/ 32位数据*/

    18 *( uint32_t*) (SDRAM_BANK_ADDR+20 ) = (uint32_t)0xCCCCCCCC;

    19 temp = *( uint32_t*) (SDRAM_BANK_ADDR+20 );

    为方便使用,代码中首先定义了宏SDRAM_BANK_ADDR表示SDRAM的起始地址,该地址即FMC映射的存储区域2的首地址;宏IS42S16400J_SIZE表示SDRAM的大小,所以从地址(SDRAM_BANK_ADDR)(SDRAM_BANK_ADDR+IS42S16400J_SIZE)都表示在SDRAM的存储空间,访问这些地址,直接就能访问SDRAM

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

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

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

    代码清单 2610 直接指定变量地址的方式访问SDRAM

    1 /*SDRAM起始地址存储空间2的起始地址*/

    2 #define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)

    3 /*绝对定位方式访问SDRAM,这种方式必须定义成全局变量*/

    4 uint8_t testValue __attribute__((at(SDRAM_BANK_ADDR)));

    5 testValue = 0xDD;

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

    更常见的是利用这种方法定义一个很大的数组,整个数组都指定到SDRAM地址上,然后就像使用malloc函数一样,用户自定义一些内存管理函数,动态地使用SDRAM的内存,我们在使用emWinGUI应用的时候就是这样做的。

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

    3.    main函数

    最后我们来编写main函数,进行SDRAM芯片读写校验,见代码清单 2414

    代码清单 2611 main函数

    1 /**

    2 * @brief 主函数

    3 * @param

    4 * @retval

    5 */

    6 int main(void)

    7 {

    8 /* LED 端口初始化 */

    9 LED_GPIO_Config();

    10 /* 初始化串口 */

    11 Debug_USART_Config();

    12 printf(" 秉火STM32F429 SDRAM 读写测试例程 ");

    13 /*初始化SDRAM模块*/

    14 SDRAM_Init();

    15

    16 /*蓝灯亮,表示正在读写SDRAM测试*/

    17 LED_BLUE;

    18 /*SDRAM进行读写测试,检测SDRAM是否正常*/

    19 if (SDRAM_Test()==1)

    20 {

    21 //测试正常绿灯亮

    22 LED_GREEN;

    23 }

    24 else

    25 {

    26 //测试失败红灯亮

    27 LED_RED;

    28 }

    29 while (1);

    30 }

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

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

    下载验证

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

    26.9 每课一问

    5.    如果要把SDRAM映射到FMC SDRAM的存储区域1,需要如何修改STM32与SDRAM的硬件连接?程序上需要修改哪些内容?

  • 相关阅读:
    1、MyBatis 快速入门
    1、Spring Framework入门
    1、基本数据类型
    2020软件工程个人作业06
    谭山明月有秋水--事后诸葛亮
    谭山明月有秋水——项冲刺第七天
    谭山明月有秋水——冲刺第六天
    谭山明月有秋水——冲刺第一天
    谭山明月有秋水——冲刺第二天
    谭山明月有秋水——冲刺第三天
  • 原文地址:https://www.cnblogs.com/firege/p/5805812.html
Copyright © 2020-2023  润新知