delem53t 自动编程怎么操作(ALIENTEK阿波罗STM32F767)
delem53t 自动编程怎么操作(ALIENTEK阿波罗STM32F767)19.1 SDRAM 简介并测试其容量。本章分为如下几个部分:跑算法或者跑 GUI 等,就可能不太够用,所以阿波罗 STM32F767 开发板板载了一颗 32M 字节容量的 SDRAM 芯片:W9825G6KH,满足大内存使用的需求。本章,我们将使用 STM32F767 来驱动 W9825G6KH,实现对 W9825G6KH 的访问控制,
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
第十九章 SDRAM 实验
STM32F767IGT6 自带了 512K 字节的 SRAM,对一般应用来说,已经足够了,不过在一些
对内存要求高的场合,STM32F767 自带的这些内存就不够用了。比如使用 LTDC 驱动 RGB 屏、
跑算法或者跑 GUI 等,就可能不太够用,所以阿波罗 STM32F767 开发板板载了一颗 32M 字节
容量的 SDRAM 芯片:W9825G6KH,满足大内存使用的需求。
本章,我们将使用 STM32F767 来驱动 W9825G6KH,实现对 W9825G6KH 的访问控制,
并测试其容量。本章分为如下几个部分:
19.1 SDRAM 简介
19.2 硬件设计
19.3 软件设计
19.4 下载验证
19.5 STM32CubeMX 配置 FMC(SDRAM)
19.1 SDRAM 简介
本章我们将通过 STM32F767 的 FMC 接口,来驱动 W9825G6KH 这颗 SDRAM 芯片,本节
我们将介绍 SDRAM 相关知识点,包括:1,SDRAM 简介;2,FMC SDRAM 接口简介;
19.1.1 SDRAM 简介
SDRAM,英文名是:Synchronous Dynamic Random Access Memory,即同步动态随机存储
器,相较于 SRAM(静态存储器),SDRAM 具有:容量大和价格便宜的特点。STM32F767 支
持 SDRAM,因此,我们可以外挂 SDRAM,从而大大降低外扩内存的成本。
阿波罗板载的 SDRAM 型号为:W9825G6KH,其内部结构框图如图 19.1.1.1 所示:
图 19.1.1.1 W9825G6KH 内部结构框图
接下来,我们结合图 19.1.1.1,对 SDRAM 的几个重要知识点进行介绍。
(1)SDRAM 信号线
SDRAM 的信号线如表 19.1.1.1 所示:
表 19.1.1.1 SDRAM 信号线
(2)存储单元
SDRAM 的存储单元(称之为:BANK)是以阵列的形式排列的,如图 19.1.1.1 所示。每个
存储单元的结构示意图,如图 19.1.1.2 所示:
图 19.1.1.2 SDRAM BANK 结构示意图
对于这个存储阵列,我们可以将其看成是一个表格,只需要给定行地址和列地址,就可以
确定其唯一位置,这就是 SDRAM 寻址的基本原理。而一个 SDRAM 芯片内部,一般又有 4 个
这样的存储单元(BANK),所以,在 SDRAM 内部寻址的时候,先指定 BANK 号和行地址,
然后再指定列地址,就可以查找到目标地址。
SDRAM 的存储结构示意图,如图 19.1.1.3 所示,寻址的时候,首先 RAS 信号为低电平,
选通行地址,地址线 A0~A12 所表示的地址,会被传输并锁存到行地址译码器里面,最为行地
址,同时BANK地址线上面的BS0,BS1所表示的BANK地址,也会被锁存,选中对应的BANK,
然后,CAS 信号为低电平,选通列地址,地址线 A0~A12 所表示的地址,会被传输并锁存到列
地址译码器里面,作为列地址,这样,就完成了一次寻址。
W9825G6KH 的存储结构为:行地址:8192 个;列地址:512 个;BANK 数:4 个;位宽:
16 位;这样,整个芯片的容量为:8192*512*4*16=32M 字节。
19.1.1.3 SDRAM 存储结构图
(3)数据传输
在完成寻址以后,数据线 DQ0~DQ15 上面的数据会通过图 19.1.1.1 中所示的数据控制逻辑
写入(或读出)存储阵列。
特别注意:因为 SDRAM 的位宽,可以达到 32 位,也就是最多有 32 条数据线,在实际使
用的时候,我们可能会以:8 位、16 位、24 位和 32 位等宽度来读写数据,这样的话,并不是
每条数据线,都会被使用到,未被用到的数据线上面的数据,必须被忽略,这个时候就需要用
到数据掩码(DQM)线来控制了,每一个数据掩码线,对应 8 个位的数据,低电平表示对应数
据位有效,高电平表示对应数据位无效。
以 W9825G6KH 为例,假设以 8 位数据访问,我们只需要 DQ0~DQ7 的数据,而 DQ8~DQ15
的数据需要忽略,此时,我们只需要设置 LDQM 为低电平,UDQM 为高电平,就可以了。
(4)控制命令
SDRAM 的驱动需要用到一些命令,我们列出几个常用的命令给大家做讲解,如表 19.1.1.2
所示:
表 19.1.1.2 SDRAM 控制命令
1, NO-Operation
NO-Operation,即空操作命令,用于选中 SDRAM,防止 SDRAM 接受错误的命令,为接
下来的命令发送做准备。
2, Active
Active,即激活命令,该命令必须在读写操作之前被发送,用于设置所需要的 Bank 和
行地址(同时设置这 2 个地址),Bank 地址由 BS0,BS1(也写作 BA0,BA1,下同)指定,
行地址由 A0~A12 指定,时序图如图 19.1.1.4 所示:
图 19.1.1.4 激活命令时序图
3, Read/Write
Read/Write,即读/写命令,在发送完激活命令后,再发送列地址就可以完成对 SDRAM
的寻址,并进行读写操作了,读/写命令和列地址的发送,是通过一次传输完成的,如图
19.1.1.5 所示:
图 19.1.1.5 读/写命令时序图
列地址由 A0~A9 指定,WE 信号控制读/写命令,高电平表示读命令,低电平表示写命令,
各条信号线的状态,在 CLK 的上升沿被锁存到芯片内部。
4, Precharge
Precharge,即预充电指令,用于关闭 Bank 中所打开的行地址。由于 SDRAM 的寻址具
体独占性,所以在进行完读写操作后,如果要对同一 Bank 的另一行进行寻址,就要将原来
有效(打开)的行关闭,重新发送行/列地址。Bank 关闭现有行,准备打开新行的操作就
叫做预充电(Precharge)。
预充电命令时序,如图 19.1.1.6 所示:
图 19.1.1.6 预充电命令时序图
预充电命令可以通过独立的命令发送,也可以在每次发送读/写命令的时候,使用地址线
A10,来设置自动预充电。在发送读/写命令的时候,当 A10=1,则使能所有 Bank 的预充电,
在读/写操作完成后,自动进行预充电。这样,下次读/写操作之前,就不需要再发预充电命令
了,从而提高读/写速度。
5, Refresh
Refresh,即刷新命令,用于刷新一行数据。SDRAM 里面存储的数据,需要不断的进行
刷新操作才能保留住,因此刷新命令对于 SDRAM 来说,尤为重要。预充电命令和刷新命令,
都可以实现对 SDRAM 数据的刷新,不过预充电仅对当前打开的行有效(仅刷新当前行),而
刷新命令,则可以依次对所有的行进行刷新操作。
总共有两种刷新操作:自动刷新(Auto Refresh)和自我刷新(Self Refresh),在发送 Refresh
命令时,如果 CKE 有效(高电平),则使用自动刷新模式,否则使用自我刷新模式。不论是何
种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。
自动刷新:SDRAM 内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成要
刷新的行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址。刷新涉及到所有
Bank,因此在刷新过程中,所有 Bank 都停止工作,而每次刷新所占用的时间为 9 个时钟周期
(PC133 标准),之后就可进入正常的工作状态,也就是说在这 9 个时钟期间内,所有工作指
令只能等待而无法执行。刷新操作必须不停的执行,完成一次所有行的刷新所需要的时间,称
为刷新周期,一般为 64ms。显然,刷新操作肯定会对 SDRAM 的性能造成影响,但这是没办法
的事情,也是 DRAM 相对于 SRAM(静态内存,无需刷新仍能保留数据)取得成本优势的同
时所付出的代价。
自我刷新:主要用于休眠模式低功耗状态下的数据保存,在发出自动刷新命令时,将 CKE
置于无效状态(低电平),就进入了自我刷新模式,此时不再依靠系统时钟工作,而是根据内部
的时钟进行刷新操作。在自我刷新期间除了 CKE 之外的所有外部信号都是无效的(无需外部提
供刷新指令),只有重新使 CKE 有效(高电平)才能退出自刷新模式并进入正常操作状态。
6, Mode Register Set
Mode Register Set,即设置模式寄存器。SDRAM 芯片内部有一个逻辑控制单元,控制
单元的相关参数由模式寄存器提供,我们通过设置模式寄存器命令,来完成对模式寄存器
的设置,这个命令在每次对 SDRAM 进行初始化的时候,都需要用到。
发送该命令时,通过地址线来传输模式寄存器的值,W9825G6KH 的模式寄存器描述如
图 19.1.1.7 所示:
图 19.1.1.7 W9825G6KH 的模式寄存器
由图可知,模式寄存器的配置分为几个部分:
Burst Length,即突发长度(简称 BL),通过 A0~A2 设置,是指在同一行中相邻的存储单
元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。
前面我们说的读/写操作,都是一次对一个存储单元进行寻址,如果要连续读/写就还要对
当前存储单元的下一个单元进行寻址,也就是要不断的发送列地址与读/写命令(行地址不变,
所以不用再对行寻址)。虽然由于读/写延迟相同可以让数据的传输在 I/O 端是连续的,但它占
用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。
为此,人们开发了突发传输技术,只要指定起始列地址与突发长度,内存就会依次地自动
对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第
一个数据的传输需要若干个周期外,其后每个数据只需一个周期的即可获得。
非突发连续读取模式:不采用突发传输而是依次单独寻址,此时可等效于 BL=1。虽然可
以让数据是连续的传输,但每次都要发送列地址与命令信息,控制资源占用极大。突发连续读
取模式:只要指定起始列地址与突发长度,寻址与数据的读取自动进行,而只要控制好两段突
发读取命令的间隔周期(与 BL 相同)即可做到连续的突发传输。 至于 BL 的数值,也是不能
随便设或在数据进行传输前临时决定,而是在初始化的时候,通过模式寄存器设置命令,进行
设置。目前可用的选项是 1、2、4、8、全页(Full Page),常见的设定是 4 和 8。若传输长度
小于突发长度,则需要发送 Burst Stop(停止突发)命令,结束突发传输。
Addressing Mode,即突发访问的地址模式,通过 A3 设置,可以设置为:Sequential(顺序)
或 Interleave(交错)。顺序方式,地址连续访问,而交错模式则地址是乱序的,一般选择连续
模式。
CAS Latency,即列地址选通延迟(简称 CL)。在读命令(同时发送列地址)发送完之后,
需要等待几个时钟周期,DQ 数据线上的数据,才会有效,这个延迟时间,就叫 CL,一般设置
为 2/3 个时钟周期,如图 19.1.1.8 所示:
图 19.1.1.8 CAS 延迟(2/3)
特别注意:列地址选通延迟(CL),仅在读命令的时候有效果,在写命令的时候,并不需
要这个延迟。
Write Mode,即写模式,用于设置单次写的模式,可以选择突发写入或者单次写入。
(五)初始化
SDRAM 上电后,必须进行初始化,才可以正常使用。SDRAM 初始化时序图如图 19.1.1.9
所示:
图 19.1.1.9 SDRAM 初始化时序图
初始化过程分为五步:
① 上电
此步,给 SDRAM 供电,使能 CLK 时钟,并发送 NOP(No Operation 命令),注意,上电
后,要等待最少 200us,再发送其他指令。
② 发送预充电命令
第二步,就是发送预充电命令,给所有 Bank 预充电。
③ 发送自动刷新命令
这一步,至少要发送发送 8 次自刷新命令, 每一个自刷新命令之间的间隔时间为 tRC。
④ 设置模式寄存器
这一步,发送模式寄存器的值,配置 SDRAM 的工作参数。配置完成后,需要等待 tMRD
(也叫 tRSC),使模式寄存器的配置生效,才能发送其他命令。
⑤ 完成
经过前面四步的操作,SDRAM 的初始化就完成了,接下来,就可以发送激活命令和读/写
命令,进行数据的读/写了。
这里提到的 tRC、tMRD 和 tRSC 见 SDRAM 的芯片数据手册。
(六)写操作
在完成对 SDRAM 的初始化之后,我们就可以对 SDRAM 进行读写操作了,首先,我们来
看写操作,时序图如图 19.1.1.10 所示:
图 19.1.1.10 SDRAM 写时序图(自动预充电)
SDRAM 的写流程如下:
① 发送激活命令
此命令同时设置行地址和 BANK 地址,发送该命令后,需要等待 tRCD 时间,才可以发送
写命令。
② 发送写命令
在发送完激活命令,并等待 tRCD 后,发送写命令,该命令同时设置列地址,完成对 SDRAM
的寻址。同时,将数据通过 DQ 数据线,存入 SDRAM。
③ 使能自动预充电
在发送写命令的同时,拉高 A10 地址线,使能自动预充电,以提高读写效率。
④ 执行预充电
预充电在发送激活命令的 tRAS 时间后启动,并且需要等待 tRP 时间,来完成。
⑤ 完成一次数据写入
最后,发送第二个激活命令,启动下一次数据传输。这样,就完成了一次数据的写入。
(七)读操作
前面介绍了 SDRAM 的写操作,接下来我们看读操作,读操作时序,如图 19.1.1.11 所示:
图 19.1.1.11 SDRAM 读时序图(自动预充电)
SDRAM 的读流程如下:
① 发送激活命令
此命令同时设置行地址和 BANK 地址,发送该命令后,需要等待 tRCD 时间,才可以发送
读命令。
② 发送写命令
在发送完激活命令,并等待 tRCD 后,发送读命令,该命令同时设置列地址,完成对 SDRAM
的寻址。读操作还有一个 CL 延迟(CAS Latency),所以需要等待给定的 CL 延迟(2 个或 3 个
CLK)后,再从 DQ 数据线上读取数据。
③ 使能自动预充电
在发送读命令的同时,拉高 A10 地址线,使能自动预充电,以提高读写效率。
④ 执行预充电
预充电在发送激活命令的 tRAS 时间后启动,并且需要等待 tRP 时间,来完成。
⑤ 完成一次数据写入
最后,发送第二个激活命令,启动下一次数据传输。这样,就完成了一次数据的读取。
SDRAM 的简介,就给大家介绍到这里,以上,tRCD、tRAS 和 tRP 等时间参数,见 SDRAM
的数据手册,且在后续配置 FMC 的时候,需要用到。
19.1.2 FMC SDRAM 接口简介
在上一章,我们对 STM32F767 的 FMC 接口进行了简介,并利用 FMC 接口,来驱动 MCU
屏,本章,我们将介绍如何利用 FMC 接口,驱动 SDRAM。STM32F767 FMC 接口的 SDRAM
控制器,具有如下特点:
两个 SDRAM 存储区域,可独立配置
支持 8 位、16 位和 32 位数据总线宽度
支持 13 位行地址,11 位列地址,4 个内部存储区域:4x16Mx32bit (256MB)、
4x16Mx16bit(128 MB)、4x16Mx8bit (64 MB)
支持字、半字和字节访问
自动进行行和存储区域边界管理
多存储区域乒乓访问
可编程时序参数
支持自动刷新操作,可编程刷新速率
自刷新模式
读 FIFO 可缓存,支持 6 行 x32 位深度(6 x14 位地址标记)
通过 19.1.1 的介绍,我们对 SDRAM 已经有了一个比较深入的了解,包括接线、命令、初
始化流程和读写流程等,接下来,我们介绍一些配置 FMC SDRAM 控制器需要用到的几个寄存
器。
首先,我们介绍 SDRAM 的控制寄存器:FMC_SDCRx,x=1/2,该寄存器各位描述如图
19.1.2.1 所示:
图 19.1.2.1 FMC_SDCRx 寄存器各位描述
该寄存器只有低 15 位有效,且都需要进行配置:
NC:这两个位定义列地址的位数(00~11,表示 8~11 位),W9825G6KH 有 9 位列地址,
所以,这里应该设置为 01。
NR:这两个位定义行地址的位数(00~10,表示 11~13 位),W9825G6KH 有 13 位行地址,
所以,这里设置为 10。
MWID:这两个位定义存储器数据总线宽度(00~10,表示 8~32 位),W9825G6KH 数据位
宽为 16 位,所以,这里设置为 01。
NB:该位用于设置 SDRAM 内部存储区域(BANK)数量(0=2 个,1=4 个),W9825G6KH
内部有 4 个 BANK,所以,这里设置为:1。
CAS:这两个位可设置 SDRAM 的 CAS 延迟,按存储器时钟周期计(01~11,表示 1~3 个)。
W9825G6KH 可以设置为 2,也可以设置为 3,我们设置为 11。
WP:该位用于写保护设置(0=写使能,1=写保护),我们需要用到写操作,所以这里设置
为 1 即可。
SDCLK:这两个位用于配置 SDRAM 的时钟(10=HCLK/2,11=HCLK/3),需要在禁止
SDRAM 时钟的前提下配置。W9825G6KH 最快可以到 200M(@CL=3),为了较快的速度,我
们设置为 10。
RBURST:此位用于使能突发读模式(0=禁止,1=使能)。这里我们设置为 1,使能突发读。
RPIPE:这两个位可定义在 CAS 延迟后延后多少个 HCLK 时钟周期读取数据(00~10,
表示 0~2 个)。这里,我们设置为 00 即可。
接下来,我们介绍 SDRAM 的时序寄存器:FMC_SDTRx,x=1/2,该寄存器各位描述如图
19.1.2.2 所示:
图 19.1.2.2 FMC_SDTRx 寄存器各位描述
该寄存器用于控制 SDRAM 的时序,非常重要,接下来我们分别介绍各个参数:
TMRD:这四个位定义加载模式寄存器命令和激活或刷新命令之间的延迟,这个参数就是
SDRAM 数据手册里面的 tMRD 或 tRSC 参数,W9825G6KH 的 tRSC 值为 2 个时钟,所以我们
设置为 1 即可(2 个时钟周期,这里的时钟周期是指 SDRAM 的时钟周期,下同)。
TXSR:这四个位定义从发出自刷新命令到发出激活命令之间的延迟。W9825G6KH 的这个
时间为 72ns,我们设置 STM32F767 的时钟频率为 216Mhz,那么一个 SDRAM 的时钟频率为
108M,一个周期为 9.3ns,设置 TXSR 为 7,即 8 个时钟周期即可。
TRAS:这四个位用于设置自刷新周期。W9825G6KH 的自刷新周期为 60ns,我们设置 TRAS
为 6,即 7 个时钟周期即可。
TRC:这四个位定义刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟。
W9825G6KH 的这个时间同样是 60ns,我们设置 TRC 为 6,即 7 个时钟周期即可。
TWR:这四个位定义写命令和预充电命令之间的延迟。W9825G6KH 的这个时间为 2 个时
钟周期,所以,我们设置 TWR=1 即可。
TRP:这四个位定义预充电命令与其它命令之间的延迟。W9825G6KH 的这个时间为 15ns,
所以,我们设置 TRP=1,即 2 个时钟周期(18.6ns)。
TRCD:这四个位定义激活命令与读/写命令之间的延迟。W9825G6KH 的这个时间为 15ns,所以,
我们设置 TRP=1,即 2 个时钟周期(18.6ns)。
接下来,我们介绍 SDRAM 的命令模式寄存器:FMC_SDCMR,该寄存器各位描述如图
19.1.2.3 所示:
图 19.1.2.3 FMC_SDCMR 寄存器各位描述
该寄存器用于发送控制 SDRAM 的命令,以及 SDRAM 控制器的工作模式时序,非常重要,
接下来我们分别介绍各个参数:
MODE:这三个位定义发送到 SDRAM 存储器的命令。000:正常模式;001:时钟配置使
能;010:预充电所有存储区;011:自刷新命令;100:配置模式寄存器;101:自刷新命令;
110:掉电命令;111:保留。加粗部分的命令,我们配置的时候需要用到。
CTB2/CTB1:这两个位用于指定命令所发送的目标存储器,因为 SDRAM 控制器可以外挂
2 个 SDRAM,发送命令的时候,需要通过 CTB1/CTB2 指定命令发送给哪个存储器。我们使用
的是第一个存储器(SDNE0),所以设置 CTB1 即可。
NRFS:这四个位定义在 MODE=011 时,所发出的连续自刷新命令的个数。0000~1110,表
示 1~15 个自刷新命令。W9825G6KH 在初始化的时候,至少需要连续发送 8 个自刷新命令。
MRD:这十三个位,定义 SDRAM 模式寄存器的内容(通过地址线发送),在 MODE=100
的时候,需要配置。
接下来,我们介绍 SDRAM 的刷新定时器寄存器:FMC_SDRTR,该寄存器各位描述如图
19.1.2.4 所示:
图 19.1.2.4 FMC_SDRTR 寄存器各位描述
该寄存器通过配置刷新定时器计数值来设置刷新循环之间的刷新速率,按 SDRAM 的 时
钟周期计数。计算公式为:
刷新速率=(COUNT 1)*SDRAM 频率时钟
COUNT=(SDRAM 刷新周期/行数)-20
我们以 W9825G6KH 为例讲解计算过程,W9825G6KH 的刷新周期为 64ms,行数为 8192
行,所以刷新速率为:
刷新速率=64ms/8192=7.81us
而 SDRAM 时钟频率=216Mhz/2=108Mhz(9.26ns),所以 COUNT 的值为:
COUNT=7.81us/9.26ns≈844
而如果 SDRAM 在接受读请求后,出现内部刷新请求,则必须将刷新速率增加 20 个 SDRAM
时钟周期,以获得充足的余量,所以,实际设计的 COUNT 值应该是:COUNT-20=824。所以,
我们设置 FMC_SDRTR 的 COUNT=824,就可以完成对该寄存器的配置。
至此,FMC SDRAM 部分的寄存器就介绍完了,关于 FMC SDRAM 控制器的详细介绍,
请大家参考《STM32F7 中文参考手册》第 13.7 节。通过以上两个小节的了解,我们可以开始
写 SDRAM 的驱动代码了。不过,MDK 并没有将寄存器定义成 FMC_SDCR1/2 的形式,而是
定义成:FMC_SDCR[0]/[1],对应的就是 FMC_SDCR1/2,其他几个寄存器类似,使用的时候
注意一下。
阿波罗 STM32F767 核心板板载的 W9825G6KH 芯片挂在 FMC SDRAM 的控制器 1 上面
(SDNE0),其原理图如图 19.1.2.5 所示:
图 19.1.2.5 W9825G6KH 原理图
从原理图可以看出,W9825G6KH 同 STM32F767 的连接关系:
A[0:12]接 FMC_A[0:12]
BA[0:1]接 FMC_BA[0:1]
D[0:15]接 FMC_D[0:15]
CKE 接 FMC_SDCKE0
CLK 接 FMC_SDCLK
UDQM 接 FMC_NBL1
LDQM 接 FMC_NBL0
WE 接 FMC_SDNWE
CAS 接 FMC_SDNCAS
RAS 接 FMC_SDNRAS
CS 接 FMC_SDNE0
最后,我们来看看使用 HAL 库实现对 W9825G6KH 的驱动,需要对 FMC 进行哪些配置。
对 于 SDRAM 配置,我们要新引入的 HAL 库文件为 stm32f7xx_hal_sdram.c 和
stm32f7xx_hal_sdram.h。具体步骤如下:
1)使能 FMC 时钟,并配置 FMC 相关的 IO 及其时钟使能。
要使用 FMC,当然首先得开启其时钟。然后需要把 FMC_D0~15,FMCA0~12 等相关 IO
口,全部配置为复用输出,并使能各 IO 组的时钟。
使能时钟和初始化 IO 口方法前面已经多次讲解,这里就不累赘,请参考实验代码。
2)初始化SDRAM控制参数和时间参数,也就是设置寄存器FMC_SDCR1和FMC_SDTR1。
寄存器 FMC_SDCR1 用来设置 SDRAM 的相关控制参数,比如地址线宽度、CAS 延迟、
SDRAM 时钟等。设置该寄存器的 HAL 库函数为 FMC_SDRAM_Init,声明如下:
HAL_StatusTypeDef FMC_SDRAM_Init(FMC_SDRAM_TypeDef *Device
FMC_SDRAM_InitTypeDef *Init);
寄存器 FMC_SDTR1 用来设置 SDRAM 时间相关参数,比如自刷新时间、恢复延迟、预充
电延迟等。设置该寄存器的 HAL 库函数为 FMC_SDRAM_Timing_Init 函数,声明如下:
HAL_StatusTypeDef FMC_SDRAM_Timing_Init(FMC_SDRAM_TypeDef *Device
FMC_SDRAM_TimingTypeDef *Timing uint32_t Bank);
实际上,HAL 库还提供了共同设置 SDRAM 控制参数和时间参数函数 HAL_SDRAM_Init,
该函数会在内部会依次调用函数 FMC_SDRAM_Init 和 FMC_SDRAM_Timing_Init 进行 SDRAM
控制参数初始化和时间参数初始化,所以这里我们着重讲解函数 FMC_SDRAM_Init,声明如下:
HAL_StatusTypeDef HAL_SDRAM_Init(SDRAM_HandleTypeDef *hsdram
FMC_SDRAM_TimingTypeDef *Timing);
在讲解该函数之前首先我们要说明一点,和其他外设初始化一样,HAL 库同样提供了
SDRAM 的 MSP 初始化回调函数,函数为 HAL_SDRAM_MspInit ,该函数声明为:
void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef *hsdram) ;
SDRAM 初始化函数内部会调用该回调函数,用来进行 MCU 相关初始化。对于 MSP 函数
这里我们就不做过多讲解。接下来我们继续讲解 SDRAM 初始化函数 HAL_SDRAM_Init,该函
数有两个入口参数,一个入口参数是 hsdram,该参数是 SDRAM_HandleTypeDef 结构体指针类
型 , 用 来 设 置 SDRAM 的 控 制 参 数 , 另 一 个 入 口 参 数 是 Timing , 该 参 数 是
FMC_SDRAM_TimingTypeDef 结构体指针类型,用来设置 SDRAM 的时间相关参数。我们先看
看结构体 SDRAM_HandleTypeDef,定义如下。
typedef struct
{
FMC_SDRAM_TypeDef *Instance;
FMC_SDRAM_InitTypeDef Init;
__IO HAL_SDRAM_StateTypeDef State;
HAL_LockTypeDef
Lock;
DMA_HandleTypeDef *hdma;
}SDRAM_HandleTypeDef;
该结构体有 4 个成员变量,第一个成员变量用来设置 BANK 寄存器基地址,这个我们根据
其入口参数有效范围即可找到,这里我们设置为 FMC_SDRAM_DEVICE 即可。第三个和第四
个成员变量是 HAL 库使用的一些状态标识参数,最后一个成员变量 hdma 是与 DMA 相关,这
里暂不讲解。
接下来我们重点看看第二个成员变量 Init,它是真正的初始化结构体类型变量,结构体
FMC_SDRAM_InitTypeDef 定义如下:
typedef struct
{
uint32_t SDBank;
uint32_t ColumnBitsNumber; //列地址数量,FMC_SDCRx 寄存器的 NC 位
uint32_t RowBitsNumber;
//行地址数量,FMC_SDCRx 寄存器的 NR 位
uint32_t MemoryDataWidth; //存储器数据总线宽度,FMC_SDCRx 的 MWID 位
uint32_t InternalBankNumber; //SDRAM 内部存储区域数量,FMC_SDCRx 的 NB 位
uint32_t CASLatency;
//SDRAM 的 CAS 延迟,FMC_SDCRx 的 CAS 位
uint32_t WriteProtection;
//写保护,FMC_SDCRx 的 WP
uint32_t SDClockPeriod;
//SDRAM 的时钟,FMC_SDCRx 的 SDCLK 位
uint32_t ReadBurst;
//使能突发读模式,FMC_SDCRx 的 RBURST 位
uint32_t ReadPipeDelay;
//读取数据延迟,也就是 FMC_SDCRx 的 RPIPE 位
}FMC_SDRAM_InitTypeDef;
成员变量 SDBank 用来设置是使用的 SDRAM 的第几个 BANK,前面说过 SDRAM 有两个
独立的 BANK,取值为 FMC_SDRAM_BANK1 或者 FMC_SDRAM_BANK2,我们使用的是
SDRAM 的 BANK1,所以设置为 FMC_SDRAM_BANK1 即可。
其他成员变量都是用来配置 FMC_SDCRx 控制寄存器的相应位的值。
ColumnBitsNumber 用来设置列地址数量,也就是 FMC_SDCRx 寄存器的 NC 位。
RowBitsNumber 用来设置行地址数量,也就 FMC_SDCRx 寄存器的 NR 位。
MemoryDataWidth 用来设置存储器数据总线宽度,也就是 FMC_SDCRx 的 MWID 位。
InternalBankNumber 用来设置 SDRAM 内部存储区域(BANK)数量,也就是 FMC_SDCRx
的 NB 位。
CASLatency 用来设置 SDRAM 的 CAS 延迟。也就是 FMC_SDCRx 的 CAS 位。
WriteProtection 用来设置写保护,也就是 FMC_SDCRx 的 WP。
SDClockPeriod 用来设置 SDRAM 的时钟,也就是 FMC_SDCRx 的 SDCLK 位。
ReadBurst 用来设置使能突发读模式,也就是 FMC_SDCRx 的 RBURST 位。
ReadPipeDelay 用来设置在 CAS 延迟后延后多少个 HCLK 时钟周期读取数据,也就是
FMC_SDCRx 的 RPIPE 位。
讲解完 HAL_SDRAM_Init 函数的第一个入口参数 hsdram,接下来我们看看第二个入口参
数 Timing,它是 FMC_SDRAM_TimingTypeDef 结构体指针类型,该结构体主要用来设置寄存
器 FMC_SDTRx 的值。该结构体定义如下:
typedef struct
{
uint32_t LoadToActiveDelay; //加载模式寄存器命令和激活或刷新命令之间的延迟
uint32_t ExitSelfRefreshDelay; //从发出自刷新命令到发出激活命令之间的延迟
uint32_t SelfRefreshTime; //自刷新周期
uint32_t RowCycleDelay; //刷新和激活命令之间的延迟以及两个相邻刷新命令之间延迟
uint32_t WriteRecoveryTime;//写命令和预充电命令之间的延迟
uint32_t RPDelay;
//预充电命令与其它命令之间的延迟
uint32_t RCDDelay; //激活命令与读/写命令之间的延迟
}FMC_SDRAM_TimingTypeDef;
该结构体一共有 7 个成员变量,这些成员变量都是时间参数,每个而参数与寄存器的四个
位对应,取值范围均为 1-16。
成员变量 LoadToActiveDelay用来设置加载模式寄存器命令和激活或刷新命令之间的延迟,
对应寄存器 FMC_SDTRx 的 TMRD 位。ExitSelfRefreshDelay 用来设置从发出自刷新命令到发
出激活命令之间的延迟,对应 TXSR 位。SelfRefreshTime 用来设置自刷新周期,对应 TRAS 位。
RowCycleDelay 用来设置刷新命令和激活命令之间的延迟以及两个相邻刷新命令之间的延迟,
对应 TRC 位。WriteRecoveryTime 用来设置写命令和预充电命令之间的延迟,对应 TWR 位。
RPDelay 用来设置预充电命令与其它命令之间的延迟,对应位 TRP。RCDDelay 用来设置激活
命令与读/写命令之间的延迟,对应位 TRCD。
函数 HAL_SDRAM_Init 的使用范例如下:
SDRAM_HandleTypeDef SDRAM_Handler; //SDRAM 句柄
FMC_SDRAM_TimingTypeDef SDRAM_Timing;
SDRAM_Handler.Instance=FMC_SDRAM_DEVICE; //SDRAM 在 BANK5 6
SDRAM_Handler.Init.SDBank=FMC_SDRAM_BANK1;
SDRAM_Handler.Init.ColumnBitsNumber=
FMC_SDRAM_COLUMN_BITS_NUM_9;//列数量
SDRAM_Handler.Init.RowBitsNumber=FMC_SDRAM_ROW_BITS_NUM_13; //行数量
SDRAM_Handler.Init.MemoryDataWidth=FMC_SDRAM_MEM_BUS_WIDTH_16;
SDRAM_Handler.Init.InternalBankNumber=FMC_SDRAM_INTERN_BANKS_NUM_4;
SDRAM_Handler.Init.CASLatency=FMC_SDRAM_CAS_LATENCY_3;
SDRAM_Handler.Init.WriteProtection
=FMC_SDRAM_WRITE_PROTECTION_DISABLE;
SDRAM_Handler.Init.SDClockPeriod=FMC_SDRAM_CLOCK_PERIOD_2;
SDRAM_Handler.Init.ReadBurst=FMC_SDRAM_RBURST_ENABLE; //使能突发
SDRAM_Handler.Init.ReadPipeDelay=FMC_SDRAM_RPIPE_DELAY_1; //读通道延时
SDRAM_Timing.LoadToActiveDelay=2; //加载模式到激活时间的延迟为 2 个时钟周期
SDRAM_Timing.ExitSelfRefreshDelay=8; //退出自刷新延迟为 8 个时钟周期
SDRAM_Timing.SelfRefreshTime=6; //自刷新时间为 6 个时钟周期
SDRAM_Timing.RowCycleDelay=6; //行循环延迟为 6 个时钟周期
SDRAM_Timing.WriteRecoveryTime=2; //恢复延迟为 2 个时钟周期
SDRAM_Timing.RPDelay=2; //行预充电延迟为 2 个时钟周期
SDRAM_Timing.RCDDelay=2; //行到列延迟为 2 个时钟周期
HAL_SDRAM_Init(&SDRAM_Handler &SDRAM_Timing);
3)发送 SDRAM 初始化序列。
这里根据前面提到的 SDRAM 初始化步骤,对 SDRAM 进行初始化,首先使能时钟配置,
然后等待至少 200us,对所有 BANK 进行预充电,执行自刷新命令等,最后配置模式寄存器。
完成对 SDRAM 的初始化。发送初始化系列主要是向 SRAM 存储区发送命令,HAL 库提供了
发送命令函数为:
HAL_StatusTypeDef HAL_SDRAM_SendCommand(SDRAM_HandleTypeDef *hsdram
FMC_SDRAM_CommandTypeDef *Command uint32_t Timeout) ;
该函数的第一个入口参数 hsdram 是 SDRAM 句柄,第三个参数是发送命令 Timeout 时间,
这两个参数都比较好理解,接下来我们着重讲解第二个入口参数 Command 该参数是
FMC_SDRAM_CommandTypeDef 结构体指针类型,该结构体定义如下:
typedef struct
{
uint32_t CommandMode;
//命令类型
uint32_t CommandTarget;
//目标 SDRAM 存储区域
uint32_t AutoRefreshNumber;
//自刷新次数
uint32_t ModeRegisterDefinition; // SDRAM 模式寄存器的内容
}FMC_SDRAM_CommandTypeDef;
成员变量 CommandMode 用来设置命令类型,我们前面讲解过,一共有 7 种命令类型,包
括 时 钟 配 置 使 能 命 令
FMC_SDRAM_CMD_CLK_ENABLE , 自 刷 新 命 令
FMC_SDRAM_CMD_AUTOREFRESH_MODE 等,这里我们就不一一讲解。
CommandTarget 用来设置目标 SDRAM 存储区域,因为 SDRAM 控制器可以外挂 2 个
SDRAM ,发送命令的时候,需要指定命令发送给哪个存储器取值范围为:
FMC_SDRAM_CMD_TARGET_BANK1
,
FMC_SDRAM_CMD_TARGET_BANK2
和
FMC_SDRAM_CMD_TARGET_BANK1_2。
AutoRefreshNumber 用来设置自刷新次数,ModeRegisterDefinition 用来设置 SDRAM 模式
寄存器的内容。
了解了向 SRAM 存储区发送命令方法,那么发送 SDRAM 初始化序列也就是发送命令到
SRAM 存储区,这就变得非常简单了。后面我们会给大家列出发送 SDRAM 初始化序列方法。
4)设置刷新频率,也就是设置寄存器 FMC_ SDRTR 参数。
HAL 库提供的设置刷新频率函数为:
HAL_StatusTypeDef HAL_SDRAM_ProgramRefreshRate(SDRAM_HandleTypeDef *hsdram
uint32_t RefreshRate);
该函数入口参数比较简单,这里我们不做过多讲解。
通过以上几个步骤,我们就完成了 FMC 的配置,可以访问 W9825G6KH 了。
19.2 硬件设计
本章实验功能简介:开机后,显示提示信息,然后按下 KEY0 按键,即测试外部 SDRAM
容量大小并显示在 LCD 上。按下 KEY1 按键,即显示预存在外部 SDRAM 的数据。DS0 指示
程序运行状态。
本实验用到的硬件资源有:
1) 指示灯 DS0
2) KEY0 和 KEY1 按键
3) 串口
4) TFTLCD 模块
5) W9825G6KH
这些我们都已经介绍过(W9825G6KH 与 STM32F767 的各 IO 对应关系,请参考光盘原理
图),接下来我们开始软件设计。
19.3 软件设计
打开本章实验工程可以看到,我们新添加了 sdram.c 到 HARDWARE 分组,用来存放我们
编写的 SDRAM 相关驱动函数。
打开 sdram.c 文件,代码如下:
SDRAM_HandleTypeDef SDRAM_Handler; //SDRAM 句柄
//SDRAM 初始化
void SDRAM_Init(void)
{
FMC_SDRAM_TimingTypeDef SDRAM_Timing;
SDRAM_Handler.Instance=FMC_SDRAM_DEVICE;
//SDRAM 在 BANK5 6
SDRAM_Handler.Init.SDBank=FMC_SDRAM_BANK1; //SDRAM 的 BANK1
SDRAM_Handler.Init.ColumnBitsNumber=FMC_SDRAM_COLUMN_BITS_NUM_9;
SDRAM_Handler.Init.RowBitsNumber=FMC_SDRAM_ROW_BITS_NUM_13; //行数量
SDRAM_Handler.Init.MemoryDataWidth=FMC_SDRAM_MEM_BUS_WIDTH_16;
SDRAM_Handler.Init.InternalBankNumber=FMC_SDRAM_INTERN_BANKS_NUM_4;
SDRAM_Handler.Init.CASLatency=FMC_SDRAM_CAS_LATENCY_3; //CAS 为 3
SDRAM_Handler.Init.WriteProtection=
FMC_SDRAM_WRITE_PROTECTION_DISABLE;//失能写保护
SDRAM_Handler.Init.SDClockPeriod=FMC_SDRAM_CLOCK_PERIOD_2;
SDRAM_Handler.Init.ReadBurst=FMC_SDRAM_RBURST_ENABLE; //使能突发
SDRAM_Handler.Init.ReadPipeDelay=FMC_SDRAM_RPIPE_DELAY_1; //读通道延时
SDRAM_Timing.LoadToActiveDelay=2;
//加载模式寄存器到激活时间的延迟为 2 个时钟周期
SDRAM_Timing.ExitSelfRefreshDelay=8; //退出自刷新延迟为 8 个时钟周期
SDRAM_Timing.SelfRefreshTime=6; //自刷新时间为 6 个时钟周期
SDRAM_Timing.RowCycleDelay=6; //行循环延迟为 6 个时钟周期
SDRAM_Timing.WriteRecoveryTime=2; //恢复延迟为 2 个时钟周期
SDRAM_Timing.RPDelay=2; //行预充电延迟为 2 个时钟周期
SDRAM_Timing.RCDDelay=2; //行到列延迟为 2 个时钟周期
HAL_SDRAM_Init(&SDRAM_Handler &SDRAM_Timing);
SDRAM_Initialization_Sequence(&SDRAM_Handler);//发送 SDRAM 初始化序列
HAL_SDRAM_ProgramRefreshRate(&SDRAM_Handler 683);//设置刷新频率
}
//发送 SDRAM 初始化序列
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram)
{
u32 temp=0;
//SDRAM 控制器初始化完成以后还需要按照如下顺序初始化 SDRAM
SDRAM_Send_Cmd(0 FMC_SDRAM_CMD_CLK_ENABLE 1 0); //时钟配置使能
delay_us(500); //至少延时 200us
SDRAM_Send_Cmd(0 FMC_SDRAM_CMD_PALL 1 0); //对所有存储区预充电
SDRAM_Send_Cmd(0 FMC_SDRAM_CMD_AUTOREFRESH_MODE 8 0);
//设置自刷新次数
temp=(u32)SDRAM_MODEREG_BURST_LENGTH_1 |//设置突发长度:1
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //设置突发类型
SDRAM_MODEREG_CAS_LATENCY_3 |//设置 CAS 值:3(可以是 2/3)
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //标准模式
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //单点访问
SDRAM_Send_Cmd(0 FMC_SDRAM_CMD_LOAD_MODE 1 temp); //发送命令
}
//SDRAM 底层驱动,引脚配置,时钟使能
//此函数会被 HAL_SDRAM_Init()调用
//hsdram:SDRAM 句柄
void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef *hsdram)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_FMC_CLK_ENABLE();
//使能 FMC 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
//使能 GPIOC 时钟
…//此处省略部分 IO 时钟使能,详情请参考实验工程
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_2|GPIO_PIN_3;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
//推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
GPIO_Initure.Alternate=GPIO_AF12_FMC;
//复用为 FMC
HAL_GPIO_Init(GPIOC &GPIO_Initure);
//初始化 PC0 2 3
…//此处省略部分 IO 口初始化,详情请参考实验工程
}
//向 SDRAM 发送命令
//bankx:0 向 BANK5 上面的 SDRAM 发送指令
// 1 向 BANK6 上面的 SDRAM 发送指令
//cmd:指令
//refresh:自刷新次数
//regval:模式寄存器的定义
//返回值:0 正常;1 失败.
u8 SDRAM_Send_Cmd(u8 bankx u8 cmd u8 refresh u16 regval)
{
u32 target_bank=0;
FMC_SDRAM_CommandTypeDef Command;
if(bankx==0) target_bank=FMC_SDRAM_CMD_TARGET_BANK1;
else if(bankx==1) target_bank=FMC_SDRAM_CMD_TARGET_BANK2;
Command.CommandMode=cmd;
//命令
Command.CommandTarget=target_bank; //目标 SDRAM 存储区域
Command.AutoRefreshNumber=refresh; //自刷新次数
Command.ModeRegisterDefinition=regval;
//要写入模式寄存器的值
if(HAL_SDRAM_SendCommand(&SDRAM_Handler &Command 0X1000)==HAL_OK)
//向 SDRAM 发送命令
{
return 0;
}
else return 1;
}
//在指定地址(WriteAddr Bank5_SDRAM_ADDR)开始 连续写入 n 个字节.
//pBuffer:字节指针
//WriteAddr:要写入的地址
//n:要写入的字节数
void FMC_SDRAM_WriteBuffer(u8 *pBuffer u32 WriteAddr u32 n)
{
for(;n!=0;n--)
{
*(vu8*)(Bank5_SDRAM_ADDR WriteAddr)=*pBuffer;
WriteAddr ;
pBuffer ;
}
}
//在指定地址((WriteAddr Bank5_SDRAM_ADDR))开始 连续读出 n 个字节.
//pBuffer:字节指针
//ReadAddr:要读出的起始地址
//n:要写入的字节数
void FMC_SDRAM_ReadBuffer(u8 *pBuffer u32 ReadAddr u32 n)
{
for(;n!=0;n--)
{
*pBuffer =*(vu8*)(Bank5_SDRAM_ADDR ReadAddr);
ReadAddr ;
}
}
此部分代码包含 6 个函数,SDRAM_Init 函数用于初始化 FMC/SDRAM 配置,发送 SDRAM
初始化序列和设置刷新时间等,完全就是根据我们前面所说的步骤来实现的;函数
HAL_SDRAM_MspInit 是 SDRAM 的 MSP 初始化回调函数,用来初始化 IO 口和使能时钟;函
数 SDRAM_Initialization_Sequence 是单独的用来发送 SRAM 初始化序列函数,在初始化函数
SDRAM_Init 内部有调用该函数。SDRAM_Send_Cmd 函数用于给 SDRAM 发送命令,在初始化
的时候需要用到; FMC_SDRAM_WriteBuffer 和 FMC_SDRAM_ReadBuffer 这两个函数分别用
于在外部 SDRAM 的指定地址写入和读取指定长度的数据(字节数),一般用不到。
这里需要注意的是:当位宽为 16 位的时候,HADDR 右移一位同地址对其,但是 WriteAddr
/ReadAddr 我们这里却没有加 2,而是加 1,是因为我们这里用的数据位宽是 8 位,通过
FMC_NBL1 和 FMC_NBL0 来控制高低字节位,所以地址在这里是可以只加 1 的。
最后我们看看 main.c 中程序,如下:
u16 testsram[250000] __attribute__((at(0XC0000000)));//测试用数组
//SDRAM 内存测试
void fsmc_sdram_test(u16 x u16 y)
{
u32 i=0;
u32 temp=0;
u32 sval=0; //在地址 0 读到的数据
LCD_ShowString(x y 180 y 16 16 "Ex Memory Test: 0KB ");
//每隔 16K 字节 写入一个数据 总共写入 2048 个数据 刚好是 32M 字节
for(i=0;i<32*1024*1024;i =16*1024)
{
*(vu32*)(Bank5_SDRAM_ADDR i)=temp;
temp ;
}
//依次读出之前写入的数据 进行校验
for(i=0;i<32*1024*1024;i =16*1024)
{
temp=*(vu32*)(Bank5_SDRAM_ADDR i);
if(i==0)sval=temp;
else if(temp<=sval)break;//后面读出的数据一定要比第一次读到的数据大.
LCD_ShowxNum(x 15*8 y (u16)(temp-sval 1)*16 5 16 0);
//显示内存容量
printf("SDRAM Capacity:%dKB\r\n" (u16)(temp-sval 1)*16);//打印 SDRAM 容量
}
}
int main(void)
{
u8 key;
u8 i=0;
u32 ts=0;
Cache_Enable(); //打开 L1-Cache
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(432 25 2 9); //设置时钟 216Mhz
delay_init(216); //延时初始化
uart_init(115200);
//串口初始化
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //初始化 LCD
……//此处省略部分液晶显示代码
for(ts=0;ts<250000;ts )
{
testsram[ts]=ts;//预存测试数据
}
while(1)
{
key=KEY_Scan(0);//不支持连按
if(key==KEY0_PRES)fsmc_sdram_test(30 170);//测试 SRAM 容量
else if(key==KEY1_PRES)//打印预存测试数据
{
for(ts=0;ts<250000;ts )
{
LCD_ShowxNum(30 190 testsram[ts] 6 16 0);//显示测试数据
printf("testsram[%d]:%d\r\n" ts testsram[ts]);
}
}else delay_ms(10);
i ;
if(i==20)//DS0 闪烁.
{
i=0;
LED0_Toggle;
}
}
}
此部分代码除了 main 函数,还有一个 fmc_sdram_test 函数,该函数用于测试外部 SRAM
的容量大小,并显示其容量。main 函数则比较简单,我们就不细说了。
此段代码,我们定义了一个超大数组 testsram,我们指定该数组定义在外部 sdram 起始地
址(__attribute__((at(0XC0000000)))),该数组用来测试外部 SDRAM 数据的读写。注意该数组
的定义方法,是我们推荐的使用外部 SDRAM 的方法。如果想用 MDK 自动分配,那么需要用
到分散加载还需要添加汇编的 FMC 初始化代码,相对来说比较麻烦。而且外部 SDRAM 访问
速度远不如内部 SRAM,如果将一些需要快速访问的 SRAM 定义到了外部 SDRAM,将严重拖
慢程序运行速度。而如果以我们推荐的方式来分配外部 SDRAM,那么就可以控制 SDRAM 的
分配,可以针对性的选择放外部或放内部,有利于提高程序运行速度,使用起来也比较方便。
另外,fmc_sdram_test 函数和 main 函数,我们都加入了 printf 输出结果,对于没有 MCU
屏模块的朋友来说,可以打开串口调试助手,观看实验结果,软件部分就给大家介绍到这里。
19.4 下载验证
在代码编译成功之后,我们通过下载代码到 ALIENTEK 阿波罗 STM32 开发板上,得到如
图 19.4.1 所示界面:
图 19.4.1 程序运行效果图
此时,我们按下 KEY0,就可以在 LCD 上看到内存测试的画面,同样,按下 KEY1,就可
以看到 LCD 显示存放在数组 testsram 里面的测试数据,如图 19.4.2 所示:
图 19.4.2 外部 SRAM 测试界面
对于没有 MCU 屏模块的朋友,我们可以用串口来检查测试结果,如图 19.4.3 所示:
图 19.4.3 串口观看测试结果
19.5 STM32CubeMX 配置 FMC(SDRAM)
上一讲我们讲解了使用 STM32CubeMX 配置 SRAM 的方法,本小节将讲解配置 SDRAM。
使用 STM32CubeMX 配置 SDRAM 的一般步骤为:
① 进入 Pinout->FMC 配置栏,配置 FMC 基本参数。这里前面讲解过,STM32F7 FMC 接口
的 SDRAM 控制器一共有 2 个独立的 SDRAM 存储区域,这里我们使用的是区域 1,所
以我们只需要配置 SRAM1 即可。配置如下图 19.5.1 所示:
图 19.5.1 FMC 配置参数
这里我们就配置界面几个参数解释一下。Clock and chip enable 是配置时钟使能和片选
引脚,很明显我们使用的 CKE 接 FMC_SDCKE0,CS 接 FMC_SDNE0,所以选择第
一个即可。参数 Internal bank number 一共是 4 个,对于其他参数就很好理解了,地址
13 位,数据 16 位。
② 点击 Configuration->FMC 进入 FMC 配置界面,在 SDRAM1 选项卡之下配置相关参数。
这些参数的含义这里我们不累赘,在 19.1 小节讲解 HAL_SDRAM_Init 函数的时候都有讲
解。配置方法如下图 19.5.2 所示:
图 19.5.2 FMC Configuration 配置界面 SDRAM1 选项卡
在该配置界面,点击右边的 GPIO Settigns 选项卡,还可以配置相关 IO 口的信息。
经过上面配置步骤,我们就可以生成相应的初始化代码,大家生成后和本章实验工
程对比学习。