赞
踩
SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
引脚名称 | 功能 | 描述 |
---|---|---|
A 0 − A 12 A0-A12 A0−A12 | 地址线 | 可作为行地址和列地址线, 行地址:A0-A12 列地址:A0-A8 A10在预充电阶段也会被采样,其值决定是否所有的banks都进行预充电;也可以通过BS0,BS1信号线选择banks |
B S 0 − B S 1 BS0-BS1 BS0−BS1 | Bank选择 | 在行地址锁存时或在读写时地址锁存了选择对应的Bank |
D Q 0 − D Q 15 DQ0-DQ15 DQ0−DQ15 | 数据线 | 用于数据输入输出的多路数据线 |
C S ‾ \overline{CS} CS | 片选 | 使能或失能命令解码器 |
R A S ‾ \overline{RAS} RAS | 行地址选通 | 信号有效时,地址线表示的为行地址 |
C A S ‾ \overline{CAS} CAS | 列地址选通 | 信号有效时,地址线表示的为列地址 |
W E ‾ \overline{WE} WE | 写使能 | 数据写入时能 |
L D Q M ‾ , U D Q M ‾ \overline{LDQM},\overline{UDQM} LDQM,UDQM | 输入/输出掩码 | 表示数据线的有效部分 |
C K E CKE CKE | 时钟输入 | 时钟失能信号,失能时SDRAM会开启自刷新 |
C L K CLK CLK | 时钟使能 | 同步时钟信号,所有的信号都在CLK的上升沿时采样 |
SDRAM的存储结构为存储阵列,而非管道式存储,如上图所示,存储阵列类似为一张表格,通过指定行列地址就可以定位到最基本的存储单元,一个存储单元的大小通常就为数据通道数。这也是SDRAM可以实现随机访问的原因。一个存储阵列称之为一个逻辑Bank,为了在保证性能的前提下提升存储空间,一般整片SDRAM又会以若干个逻辑Banks组成,通常为2个或4个。单一Bank在访问时可能会造成非常严重的寻址冲突。
寻址时,只需要指定对应的Bank,然后确定其行地址(Row address),再确定列地址(Cloumn address),就可以对应到具体的存储单元。
因此,整个SDRAM的存储空间就算为:Size = n × R × C × D
,n为Bank数,R和C分别为存储阵列行列数,D为存储单元的容量。
整个SDRAM结构如图一所示,地址线一共有A0~A12,BS0~BS1。通讯时,RAS有效时,行地址选通器被选通,地址线A0~A12表示的地址数据会送入到行地址译码锁存器中译码锁存,作为寻址的行地址,同时BS0~BS1用于锁定对应的Bank;然后释放RAS,CAS有效时,列地址选通器被选通,A0~A9表征的列地址数据被送入列地址译码器中作为寻址的列地址,确定到对应的存储单元,完成整个寻址过程。
图一所示SDRAM的数据宽度为16bit,其存储单元容量亦为2Bytes = 16bits。当寻址完成后,可根据DQ0~DQ15数据传输数据。写数据时,经过响应时间后,DQ数据线上的数据被采集暂存到DQ Buffer中,然后通过数据控制电路写入到对应的地址存储单元;同理,读数据即为逆过程。在读写数据时,如果不需要一次性操作16bits有效数据时,可以直接通过UDQM,LDQM选择数据的有效部分,实现字节访问。
命令禁止
当器件未被选中时,此时控制器无法与SDRAM进行数据传输与通讯,防止设备执行新命令,即为命令禁止状态。即拉高CS引脚,但是不能通知SDRAM正在执行的命令。
无操作
无操作命令(No-operation),也称为空命令、NOP命令。不论 SDRAM 处于何种状态,此命令均可被写 入,该命令给被选中的 SDRAM 芯片传递一个空操作信息,目的是为了防止 SDRAM 处于空闲或等待状态时,其他命令被写入,已在进行的操作不受影响。命令禁止的反操作。
激活
ACTIVE
命令用于激活特定Bank中的行,以便后续访问。BA0~BA1的值选择Bank,A0~A12的值选择行。该行对于访问保持活跃状态,直到向该Bank发出预充电命令。在打开同一Bank中的不同行之前,必须发出PRECHARGE
命令。
READ
命令用于对激活的行进行突发读访问。通过BA0~BA1选择对应的Bank,地址线选择起始读列地址。A10确定是否使能自动预充电,使能后将在读脉冲串结束后对访问行进行预充电;失能后读访问的行将会保留打开状态供后续访问。WRITE
命令用于对激活的行进行突发写访问。通过BA0~BA1选择对应的Bank,地址线选择起始读列地址。A10确定是否使能自动预充电,使能后将在写脉冲串结束后对访问行进行预充电;失能后写访问的行将会保留打开状态供后续访问。PRECHARGE
命令用于关闭已选中Bank中选中的行或所有Bank中选中的行。在发出PRECHARGE
命令经过指定的时间(tRP)后,Bank可用于后续访问。即SDRAM寻址具有独占性,在激活命令后,如果需要同一Bank的其他行进行寻址访问时,需要关闭原来选中的行,重新发送新要访问的地址。Bank关闭当前工作行,准备打开新行的操作就是预充电。Bank预充电后处于空闲状态,需要访问该Bank时必须先激活。由于SDRAM作为动态存储器,数据存储原理是通过电容电荷的多与少来对数据进行保存,而电容会放电,因此在某个时间内需要对电容进行不断的充电来保证数据的准确性,此过程为刷新操作。区别于预充电,刷新具有固定的周期,依次对所有行进行操作。
模式寄存器用于定义SDRAM的特定操作模式。该定义包括选择突发长度、突发类型、CAS延迟、操作模式和写入突发模式,如模式寄存器定义所示。模式寄存器通过LOAD MODE REGISTER
(加载模式寄存器)命令进行编程,并保留存储的信息,直到再次编程或设备断电。
模式寄存器位 M0:M2 指定突发长度,M3 指定突发类型(顺序或交错),M4:M6 指定CAS延迟,M7:M8 指定操作模式,M9 指定写入突发模式,M10:M11 保留供将来使用。当所有Banks空闲时,必须加载模式寄存器,并且控制器必须在启动后续操作之前等待指定的时间。违反这些要求之一将导致未指定的操作。
突发及突发长度
突发(Burst)是指对在同一行中相邻的存储单元连续进行数据传输,连续传输存储单元的数量就是突发长度。硬件上通过地址线读写寄存器内容。
上文讲到的读/写操作,都是一次对一个存储单元进行寻址,如果要连续读/写就还要对当前存储单元的下一个单元进行寻址,也就是要不断的发送列地址与读写命令。虽然读/写延迟相同可以让数据的传输在I/O端是连续的,但它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。为此,人们开发了突发传输技术,只有指定起始地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一笔数据的传输需要若干个周期外,其后每个数据只需一个周期的即可获得。
突发类型
给定突发内的访问可以被编程为顺序的或交错的;这被称为突发类型,并通过位 M3 选择。
CAS Latency
列地址选通延迟,CAS延迟是读命令发出与数据线输出数据可用时之间的延迟,以时钟周期为单位。延迟可以设置为两个或三个时钟周期。下图分别为华邦和芯城SDRAM手册上的CAS延时示意。
如果在第n
个时钟边沿发出读命令,并且CAS延迟为m
个时钟,则数据将在n+m
时钟边沿时可用。DQ在第n+m-1
时钟边沿驱动输出,如果满足相关访问时间,则数据在第n+m
个时钟边沿有效。
SDRAM作为动态器件上电后不能像SRAM一般可以直接进行读写,还需要对SDRAM器件进行一定的初始化,以配置SDRAM的上述特性参数。上图为芯城和镁光公司SDRAM器件手册上的初始化时序图,从上电开始共有5个阶段。
COMMAND INHIBIT
或NOP
命令PRECHARGE ALL
命令,对所有Banks进行预充电后。至少等待TRP时间后,所有Banks将完成预充电,然后设备处于空闲状态AUTO REFRESH
命令并等待至少等待TRC时间MODE REGISTER SET
命令,根据实际应用配置寄存器对应字段。由于模式寄存器将在未知状态下上电,因此在发送其他操作命令之前,应加载模式寄存器。然后等待至少TMRD时间总线接口时钟:总线时钟与外设相连的总线接口时钟,可为APB,AHB,AXI时钟。
内核时钟:外设处理接口功能的专用时钟。可精确生成特定的主时钟频率,可更改总线时钟。
待补充完整。
可驱动SDRAM、SRAM、NAND/NOR Flash,FMC主要作为内部AXI总线桥数据与FMC外部连接器件的协议转换器,可以将AXI总线传递过来的信息(数据或命令)转换协议后送入外部器件。所有外部存储器共享地址、数据和控制信号,但有各自的片选信号。 FMC 一次只能访问一个外部器件。
1. FMC内部时钟与数据接口
AHB接口:内部CPU通过AHB总线配置FMC寄存器,而AHB时钟(fmc_hclk)是访问FMC寄存器的参考时钟。
AXI接口:CPU或其他AXI总线主设备(如DMA)可通过AXI从设备接口访问外部存储器。AXI 事务会转换为外部器件协议。由于 AXI 数据总线为 64 位宽,因此 AXI 事务可根据数据大小访问拆分成几个连续的 32 位、 16 位或 8 位访问。
CPU通过AHB总线根据总线接口时钟hclk访问配置FMC的寄存器,而FMC外设的独立运行由内核时钟ker_ck提供,AXI主设备通过AXI总线桥对FMC外部存储器的读写时序由FMC控制器进行协调。
2. FMC存储控制器
针对于不同类型的存储设备,有不同的协议和时序要求,因此针对每一种类型的存储设备FMC都设计了专有的存储控制器。SDRAM区别于其他类型的控制器,为同步通信,需要控制器向存储设备提供时间参考以同步其他信号的采集和匹配;而SRAM,Nor FLash和NAND Flash均为异步通讯,其中两种Flash最大的区别是,Nand Flash依然采用并口通讯,但是是复用的地址数据线。
针对于不同类型控制器,各自的协议不同,因此都有各自的信号线,如独立的片选信号,而针对于数据和地址线,则是通用的IO,其中,FMC一共有26位地址线 FMC_A[25:0],32位数据线 FMC_D[31:0]。
根据上图所示,FMC外设的内核时钟源可来自于RCC的hclk3
,PLL1的Q通道,PLL2的R通道或者per_ck
。
fmc_hclk
:FMC总线接口时钟(pclk,hclk)
fmc_ker_ck
:FMC内核时钟
根据上面的时钟作用分析,配置FMC外设及其功能时,对于总线内核时钟配置如上图所示,系统时钟来源于PLL1的P1时钟,为480MHz,PLL1的时钟源自HSE,为25MHz。而FMC内核时钟为240MHz,源自HCLK3,HCLK3同样源自系统时钟sys_ck二分频。根据上述时钟配置,可以看到AXI外设总线接口时钟为240MHz,且FMC外设挂载在AXI总线上。
通过阅读芯片参考手册,可以知道整个FMC的内存可拓展外置地址空间范围为1GB,而FMC将其划分为4个等大的存储区,分别为256MB,其中存储区1作为NOR/SRAM的拓展区域,存储区2作为SDRAM可选的地址映射范围,存储区3作为NAND Flash存储器的拓展区域,4作为保留区域FMC并未使用。而SDRAM的默认地址映射区域不在FMC的地址映射范围内。
通过查阅Cortex-M7内核通用用户指南Memory Model章节的内核地址映射,可以更完整的了解整个Cortex-M7内核的内存分配。见下图,内核为代码区分配了0.5GB的地址空间,为片内SRAM也预留了0.5GB的内存区,为各种其他外设的寄存器预留了0.5GB的地址空间,而预留的外部RAM空间为1GB,在STM32H7系列中此处整个空间作为FMC的外存设备映射空间。但是把SDRAM的默认映射空间放在了外部设备映射地址范围的末端。其中,外部RAM区域为数据可执行区域,意味着此空间内的可存储执行代码。而外部设备存储区域则不支持此功能。
External RAM
FMC存储区域地址映射修改
由于SRAM的存储区域默认在外部设备区域,不支持XIP。为解决此问题,可通过 FMC_BCR1 寄存器中的 BMAP[1:0] 位修改FMC 存储区域映射。可交换 NOR/PSRAM 存储区域与 SDRAM 存储区域,或者重映射 SDRAM 存储区域 2 的配置,从而允许在两个不同的地址映射中访问 SDRAM 存储区域。
根据上图所示,SDRAM1存储区域在默认映射下有两个地址,分别为0xC0000000
和0x70000000
。即同时支持上述地址的访问写入,在MCU内部的地址处理部分虽然为两个地址,实际对于外部SDRAM操作的均是内部的存储单元,其中SDRAM的实际片内访问地址为偏移地址,上述映射地址为访问基地址。
在HAL库中,初始化SDRAM完成后,直接调用HAL_SetFMCMemorySwappingConfig()
函数即可配置FMC的存储器地址映射。
XIP:Execute in place,即芯片内执行、就地执行,是指CPU直接从存储器中读取程序代码执行,而不用再读到内存中。应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。
所谓片内执行,CPU可以直接从Nor Flash中取指令,供译码器和执行器使用。为实现该功能,Flash存储器需支持:
- 需提供与RAM相似的访问接口
- 接口能够满足足够快的读取速度,且支持随机访问
- 程序不能修改已加载影像中的数据
- 程序链接时需知道存储器的地址或地址与位置无关
上述为野火基于STM32H743XIB6 Pro核心开发板的SDRAM拓展部分原理图,其中SDRAM选型为华邦(Winbond)的W9825GKH-6,其内存规格为4M×4Banks×16 Bits = 32 Bytes,最大时钟频率支持166MHz。引脚分布如下图所示,一共有13位地址线A0~A12,其中行地址线13位,列地址线9位,数据通道宽度为16位DQ0~DQ15,并支持高低位字节选通读写。
原理图中使用两片SDRAM芯片,共用一组地址线和时钟线,单片连接不同的数据线,共同组成32bit的外部SDRAM存储组合,其大小为2Pcs×4M×4Banks×16 Bits = 64 Bytes。
根据实际硬件设计和芯片手册数据,SDRAM与MCU的引脚连接复用对应如下:
/** FMC GPIO Configuration |-------------------SDRAM_DQ0~31-------------------| PD14 ------> FMC_D0 | PH8 ------> FMC_D16 PD15 ------> FMC_D1 | PH9 ------> FMC_D17 PD0 ------> FMC_D2 | PH10 ------> FMC_D18 PD1 ------> FMC_D3 | PH11 ------> FMC_D19 PE7 ------> FMC_D4 | PH12 ------> FMC_D20 PE8 ------> FMC_D5 | PH13 ------> FMC_D21 PE9 ------> FMC_D6 | PH14 ------> FMC_D22 PE10 ------> FMC_D7 | PH15 ------> FMC_D23 PE11 ------> FMC_D8 | PI0 ------> FMC_D24 PE12 ------> FMC_D9 | PI1 ------> FMC_D25 PE13 ------> FMC_D10 | PI2 ------> FMC_D26 PE14 ------> FMC_D11 | PI3 ------> FMC_D27 PE15 ------> FMC_D12 | PI6 ------> FMC_D28 PD8 ------> FMC_D13 | PI7 ------> FMC_D29 PD9 ------> FMC_D14 | PI9 ------> FMC_D30 PD10 ------> FMC_D15 | PI10 ------> FMC_D31 |--------------------SDRAM_A0~12--------------------| PF0 ------> FMC_A0 | PF13 ------> FMC_A7 PF1 ------> FMC_A1 | PF14 ------> FMC_A8 PF2 ------> FMC_A2 | PF15 ------> FMC_A9 PF3 ------> FMC_A3 | PG0 ------> FMC_A10 PF4 ------> FMC_A4 | PG1 ------> FMC_A11 PF5 ------> FMC_A5 | PG2 ------> FMC_A12 PF12 ------> FMC_A6 |--------------------SDRAM_Control------------------| PG4 ------> FMC_BA0 | PG15 ------> FMC_SDNCAS PG5 ------> FMC_BA1 | PF11 ------> FMC_SDNRAS | PC0 ------> FMC_SDNWE PE0 ------> FMC_NBL0 | PH6 ------> FMC_SDNE1 PE1 ------> FMC_NBL1 | PG8 ------> FMC_SDCLK PI4 ------> FMC_NBL2 | PH7 ------> FMC_SDCKE1 PI5 ------> FMC_NBL3 **/
其中一共使用了32根数据线,13根地址线,12根控制线,分别为Bank选择、高低位4字节选择、行地址选通、列地址选通、写使能、片选、同步时钟与时钟使能信号。一共占用MCU 47个IO资源,因此并口通讯的速度高,但是硬件资源占用也很多。
根据数据手册的引脚定义,配置相关引脚状态为复用功能推挽输出模式GPIO_MODE_AF_PP
,无需上下拉,输出速度为最高等级,复用功能选择GPIO_AF12_FMC
。
GPIO_InitStruct.Pin = GPIO_PIN_6;//复用引脚号
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
HAL通过定义SDRAM_HandleTypeDef
结构体对FMC所有寄存器的字段进行位定义,根据所需配置填充后通过HAL_SDRAM_Init()
函数将填充的字段写入对应寄存器。SDRAM_HandleTypeDef->Instance
成员就是选择FMC的Bank5_6作为SDRAM控制器,实质就是将指针指向SDRAM相关寄存器基地址,方便寄存器参数配置与初始化,让代码可读性大大提高。SDRAM_HandleTypeDef->Init
成员结构体对FMC的SDRAM控制寄存器字段进行位定义,通过填充其内容配置寄存器。
FMC在作为SDRAM控制器时,需要配置FMC有关SDRAM的控制寄存器、时序寄存器,然后初始化SDRAM器件后就可以读写数据。
1. 配置控制寄存器(FMC_SDCR2)
FMC控制寄存器主要配置FMC与外部实际SDRAM器件通讯硬件上的基本参数,主要为列地址/行地址位数、存储器数据总线宽度、存储器内部的Bank数量、存储器CAS、FMC写保护使能、SDRAM时钟配置、突发读使能以及存储器读管道延时。
SDRAM_HandleTypeDef hsdram2;//定义控制寄存器参数配置结构体
hsdram2.Instance = FMC_SDRAM_DEVICE;//指向FMC的SDRAM寄存器基地址
/*选择FMC的SDRAM存储区2(即SDRAM的Bank2),实质就是将地址指向SDRAM2的寄存器地址*/
hsdram2.Init.SDBank = FMC_SDRAM_BANK2;
hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_32;
hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram2.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
hsdram2.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram2.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_2;
注意上述的SDClockPeriod选择为FMC_SDRAM_CLOCK_PERIOD_2,此处定义了两个SDRAM存储区域的SDRAM时钟周期为两个fmc_ker_ck周期,即SDRAM运行频率为120MHz。
根据实际器件选型和硬件连接,选择SDRAM存储区域2作为SDRAM的拓展区域,行列地址线分别为13/9位,单片内部包括4个Banks,CAS延迟为3,失能写保护,SDRAM时钟2分频,使能突发读,读管道延时为2个时钟周期。
2. 配置时序寄存器(FMC_SDTR2)
FMCSD时序寄存器配置FMC与SDRAM通讯的时序,由于不同厂商生产的器件时序存在差异,可灵活配置,以兼容不同型号器件的通讯时序要求。主要配置器件的加载模式寄存器到激活时间TMRD、退出自刷新延时TXSR、自刷新时间TRAS、行循环延时TRC、恢复延时TWR、行预充电延时TRCD。
HAL库根据SDRAM时序寄存器FMC_SDTR2[31:0]
定义了FMC_SDRAM_TimingTypeDef
时序初始化结构体,将寄存器根据不同内容字段按结构体形式列出,将寄存器配置项参数通过宏定义进行声明定义,配置寄存器时,只需要定义对应的初始化结构体,然后根据宏定义,可以填鸭式的填充定义好的寄存器字段,填充完毕后,调用HAL_SDRAM_Init()
将填充内容写入寄存器。
/** * @简要: FMC SDRAM 时序参数结构体定义 */ typedef struct { uint32_t LoadToActiveDelay;//加载模式寄存器到激活TMRD /*定义加载模式寄存器命令与激活或刷新命令之间的延迟,按存储器时钟周期数计。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t ExitSelfRefreshDelay;//退出自刷新延迟TXSR[3:0] /*定义从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t SelfRefreshTime;//自刷新时间TRAS[3:0] /*定义最短的自刷新周期,按存储器时钟周期数计。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t RowCycleDelay;//行循环延迟TRC[3:0] /*定义刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟,以存储器时钟周期数表示。仅在 FMC_SDTR1寄存器中配置TRC时序。如果使用了两个SDRAM设备,则必须使用最慢设备的时序配置TRC。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t WriteRecoveryTime;//恢复延迟TWR[3:0] /*定义写命令和预充电命令之间的延迟,按存储器时钟周期数计。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t RPDelay;//行预充电延迟TRP[3:0] /*定义预充电命令与其它命令之间的延迟,按存储器时钟周期数计。仅在FMC_SDTR1寄存器中配置TRP时序。 如果使用了两个SDRAM设备,则必须使用最慢设备的时序配置TRP。 此参数应该介于Min_Data=1和Max_Data=16之间 */ uint32_t RCDDelay;//行到列延迟TRCD[3:0] /*定义激活命令与读/写命令之间的延迟,按存储器时钟周期数计。 此参数应该介于Min_Data=1和Max_Data=16之间 */ } FMC_SDRAM_TimingTypeDef;
根据芯片参考手册,可知STM32H743支持同时扩展两片SDRAM,由于SDRAM两个存储区域可独立配置,每个存储区都有自己的时序寄存器FMC_SDTR1,FMC_SDTR2
,但是行预充电延迟*TRP[3:0]和行循环延迟TRC[3:0]*共用一个寄存器FMC_SDTR1
。假设我们硬件上在SDRAM存储区2扩展RAM,但是上述两个参数依然是在SDRAM存储区1寄存器配置的。此部分已经由HAL库内部初始化函数区分定义好。对于我们配置使用,并无影响。
查询器件数据手册后,得到对应的参数为:
根据FMC存储器时钟周期(tck = 1/120M = 8.33ns
)进行计算,tMRD 最小延时为两倍的 tck, LoadToActiveDelay = 2
;tRC 最小延时为60ns,对应具体配置值为RowCycleDelay = 60/8.33 ≈ 8
;tXSR 最小值为72ns,ExitSelfRefreshDelay = 72/8.33 ≈ 9
;tRC 最小为60ns,RowCycleDelay = 60/8.33 ≈ 8
;tRP 最小为15 ns,RPDelay = 15/8.33 ≈ 2
;同样tRCD 最小为15 ns,RCDDelay = 2
。
/* 定义FMC时序初始化结构体 */
FMC_SDRAM_TimingTypeDef SdramTiming = {0};
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 9;
SdramTiming.SelfRefreshTime = 5;//华邦手册无此项
SdramTiming.RowCycleDelay = 8;
SdramTiming.WriteRecoveryTime = 4;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 2;
3.初始化器件
经过前三个阶段的初始化,分别配置引脚的复用功能,配置FMC的SDRAM控制器,完成通讯上的硬件参数基本配置;然后配置通讯过程中的时序协议,已经可以实现CPU和FMC之间的通讯了;但是,SDRAM作为动态的存储器件,建立通讯后,还必须对SDRAM存储器进行初始化,此初始化有严格的步骤和时间要求。
SDRAM器件的配置通过命令下发实现,因此有专用的命令模式寄存器FMC_SDCMR,该寄存器包含访问 SDRAM 设备时所发出的命令。该寄存器用于初始化 SDRAM 设备、激活自刷新模式和掉电模式。
同样地,HAL库根据SDRAM命令模式寄存器 FMC_SDCMR 定义了FMC_SDRAM_CommandTypeDef
命令参数结构体,将寄存器根据不同内容字段按结构体形式列出,将寄存器配置项参数通过宏定义进行声明定义,配置寄存器时,只需要定义对应的初始化结构体,然后根据宏定义,填鸭式的填充定义好的寄存器字段,填充完毕后,调用HAL_SDRAM_SendCommand()
函数将填充内容写入对应寄存器。
FMC_SDRAM_CommandTypeDef SDRAM_INIT_HANDLER; //step1:时钟配使能 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; //此处SDRAM数据宽度为32bit,共用时钟线为FMC_SDCLK1,即SDRAM_BANK2的信号线 SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1; SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step2:延时100us以上 HAL_Delay(1); //step3:给SDRAM所有Bank预充电 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_PALL; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1; SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step4:设置SDRAM自动刷新 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 8;//定义在自动刷新模式下发出的连续自动刷新命令的数量 SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step5:设置SDRAM加载模式寄存器 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1;//定义在自动刷新模式下发出的连续自动刷新命令的数量 SDRAM_INIT_HANDLER.ModeRegisterDefinition = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1| SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | SDRAM_MODEREG_OPERATING_MODE_STANDARD | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step6:设置SDRAM自动刷新速率 HAL_SDRAM_ProgramRefreshRate(&hsdram2, 918);
当上述初始化过程完成以后,可以直接以指针方式对拓展区域的内存进行读写:
void SDRAM_Test(void) { volatile uint32_t data_write_arr = 0xD0000000; for(uint32_t i = 0; i < 2*4*4*1024*1024/4; i++) { *(uint32_t *) data_write_arr = i; data_write_arr += 4; } data_write_arr = 0xD0000000; for(uint32_t i = 0; i < 2*4*4*1024*1024/4; i++) { if(*(uint32_t *) data_write_arr != i) { printf("数据读写有误\r\n"); error_handler(); } } printf("数据读写测试成功\r\n"); }
上述程序部分,将data_write_arr
变量强制转换成32位指针变量,并依次填充数据i,填冲16M个数据,填充完毕后;依次读出来与i对比,如果不匹配,则数据读写不正确,实验失败。
完整驱动程序编写如下:
/* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "fmc.h" /* USER CODE BEGIN 0 */ SDRAM_HandleTypeDef hsdram2; /* @intro:SDRAM存储器初始化 @param:no param @retur:no return */ void SDRAM_Pre_Config(void) { FMC_SDRAM_CommandTypeDef SDRAM_INIT_HANDLER; //step1:时钟配使能 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; //此处SDRAM数据宽度为32bit,共用时钟线为FMC_SDCLK1,即SDRAM_BANK2的信号线 SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1; SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step2:延时100us以上 HAL_Delay(1); //step3:给SDRAM所有Bank预充电 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_PALL; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1; SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step4:设置SDRAM自动刷新 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 8; SDRAM_INIT_HANDLER.ModeRegisterDefinition = 0x0; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step5:设置SDRAM加载模式寄存器 SDRAM_INIT_HANDLER.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; SDRAM_INIT_HANDLER.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; SDRAM_INIT_HANDLER.AutoRefreshNumber = 1; SDRAM_INIT_HANDLER.ModeRegisterDefinition = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | SDRAM_MODEREG_CAS_LATENCY_3 | SDRAM_MODEREG_OPERATING_MODE_STANDARD| SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; HAL_SDRAM_SendCommand(&hsdram2, &SDRAM_INIT_HANDLER, 0xff); //step6:设置SDRAM自动刷新速率 HAL_SDRAM_ProgramRefreshRate(&hsdram2, 918); } uint8_t BSP_FMC_Sdram_Word_Write(uint32_t * write_data_buffer, uint32_t arr_write, uint32_t write_count) { __IO uint32_t *arr = (uint32_t *)arr_write; if((*arr + EXT_SDRAM_START_ARR) > EXT_SDRAM_END_ARR || (arr_write < EXT_SDRAM_START_ARR)) { return 0; }else{ while(HAL_SDRAM_GetState(&hsdram2) != HAL_SDRAM_STATE_READY); for(; write_count > 0; write_count--) { *arr = *write_data_buffer++; arr += 4; } } return 1; } void BSP_FMC_Sdram_Word_Read(uint32_t * read_data_buffer, uint32_t arr_read, uint32_t read_count) { __IO uint32_t *arr = (uint32_t *)arr_read; while(HAL_SDRAM_GetState(&hsdram2) != HAL_SDRAM_STATE_READY); for(; read_count > 0; read_count--) { *read_data_buffer++ = *arr; arr += 4; } } /* USER CODE END 0 */ /* FMC initialization function */ void MX_FMC_Init(void) { /* USER CODE BEGIN FMC_Init 0 */ /* USER CODE END FMC_Init 0 */ FMC_SDRAM_TimingTypeDef SdramTiming = {0}; /* USER CODE BEGIN FMC_Init 1 */ /* USER CODE END FMC_Init 1 */ /** Perform the SDRAM2 memory initialization sequence */ hsdram2.Instance = FMC_SDRAM_DEVICE; /* hsdram2.Init */ hsdram2.Init.SDBank = FMC_SDRAM_BANK2; hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9; hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13; hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_32; hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; hsdram2.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3; hsdram2.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; hsdram2.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_2; /* SdramTiming */ SdramTiming.LoadToActiveDelay = 2; SdramTiming.ExitSelfRefreshDelay = 9; SdramTiming.SelfRefreshTime = 5; SdramTiming.RowCycleDelay = 8; SdramTiming.WriteRecoveryTime = 4; SdramTiming.RPDelay = 2; SdramTiming.RCDDelay = 2; if (HAL_SDRAM_Init(&hsdram2, &SdramTiming) != RESET) { Error_Handler( ); } /* USER CODE BEGIN FMC_Init 2 */ SDRAM_Pre_Config();//初始化SDRAM设备 /* USER CODE END FMC_Init 2 */ } static uint32_t FMC_Initialized = 0; static void HAL_FMC_MspInit(void){ /* USER CODE BEGIN FMC_MspInit 0 */ /* USER CODE END FMC_MspInit 0 */ GPIO_InitTypeDef GPIO_InitStruct = {0}; if (FMC_Initialized) { return; } FMC_Initialized = 1; /* Peripheral clock enable */ __HAL_RCC_FMC_CLK_ENABLE(); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_1 |GPIO_PIN_0|GPIO_PIN_7|GPIO_PIN_2|GPIO_PIN_3 |GPIO_PIN_9|GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF12_FMC; HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_10|GPIO_PIN_9 |GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_8 |GPIO_PIN_13|GPIO_PIN_7|GPIO_PIN_14; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_15|GPIO_PIN_14|GPIO_PIN_13|GPIO_PIN_10 |GPIO_PIN_11|GPIO_PIN_9|GPIO_PIN_12|GPIO_PIN_6 |GPIO_PIN_8|GPIO_PIN_7; HAL_GPIO_Init(GPIOH, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_15|GPIO_PIN_8|GPIO_PIN_5|GPIO_PIN_4 |GPIO_PIN_2|GPIO_PIN_0|GPIO_PIN_1; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_15|GPIO_PIN_14 |GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_3 |GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_13|GPIO_PIN_14 |GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_11; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); /* GPIO_InitStruct */ GPIO_InitStruct.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /* USER CODE BEGIN FMC_MspInit 1 */ /* USER CODE END FMC_MspInit 1 */ } void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef* sdramHandle){ /* USER CODE BEGIN SDRAM_MspInit 0 */ /* USER CODE END SDRAM_MspInit 0 */ HAL_FMC_MspInit(); /* USER CODE BEGIN SDRAM_MspInit 1 */ /* USER CODE END SDRAM_MspInit 1 */ } static uint32_t FMC_DeInitialized = 0; static void HAL_FMC_MspDeInit(void){ /* USER CODE BEGIN FMC_MspDeInit 0 */ /* USER CODE END FMC_MspDeInit 0 */ if (FMC_DeInitialized) { return; } FMC_DeInitialized = 1; /* Peripheral clock enable */ __HAL_RCC_FMC_CLK_DISABLE(); HAL_GPIO_DeInit(GPIOI, GPIO_PIN_6|GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_1 |GPIO_PIN_0|GPIO_PIN_7|GPIO_PIN_2|GPIO_PIN_3 |GPIO_PIN_9|GPIO_PIN_10); HAL_GPIO_DeInit(GPIOE, GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_10|GPIO_PIN_9 |GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_8 |GPIO_PIN_13|GPIO_PIN_7|GPIO_PIN_14); HAL_GPIO_DeInit(GPIOH, GPIO_PIN_15|GPIO_PIN_14|GPIO_PIN_13|GPIO_PIN_10 |GPIO_PIN_11|GPIO_PIN_9|GPIO_PIN_12|GPIO_PIN_6 |GPIO_PIN_8|GPIO_PIN_7); HAL_GPIO_DeInit(GPIOG, GPIO_PIN_15|GPIO_PIN_8|GPIO_PIN_5|GPIO_PIN_4 |GPIO_PIN_2|GPIO_PIN_0|GPIO_PIN_1); HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_15|GPIO_PIN_14 |GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8); HAL_GPIO_DeInit(GPIOF, GPIO_PIN_2|GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_3 |GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_13|GPIO_PIN_14 |GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_11); HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0); /* USER CODE BEGIN FMC_MspDeInit 1 */ /* USER CODE END FMC_MspDeInit 1 */ } void HAL_SDRAM_MspDeInit(SDRAM_HandleTypeDef* sdramHandle){ /* USER CODE BEGIN SDRAM_MspDeInit 0 */ /* USER CODE END SDRAM_MspDeInit 0 */ HAL_FMC_MspDeInit(); /* USER CODE BEGIN SDRAM_MspDeInit 1 */ /* USER CODE END SDRAM_MspDeInit 1 */ } /** * @} */ /** * @} */
头文件定义:
/* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __FMC_H #define __FMC_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include "main.h" /* USER CODE BEGIN Includes */ #define EXT_SDRAM_START_ARR 0xD0000000 #define EXT_SDRAM_TOTAL_SIZE 0x04000000//2*4*4*2*1024*1024 = 0x04000000 #define EXT_SDRAM_END_ARR 0xD4000000 /* USER CODE END Includes */ extern SDRAM_HandleTypeDef hsdram2; /* USER CODE BEGIN Private defines */ /** * @brief FMC SDRAM 模式配置的寄存器相关定义 */ #define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000) #define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001) #define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002) #define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004) #define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) #define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) #define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020) #define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030) #define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000) #define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) #define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) /* USER CODE END Private defines */ void MX_FMC_Init(void); void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef* hsdram); void HAL_SDRAM_MspDeInit(SDRAM_HandleTypeDef* hsdram); /* USER CODE BEGIN Prototypes */ void SDRAM_Pre_Config(void); uint8_t BSP_FMC_Sdram_Word_Write(uint32_t * write_data_buffer, uint32_t arr_write, uint32_t write_count); void BSP_FMC_Sdram_Word_Read(uint32_t * read_data_buffer, uint32_t arr_read, uint32_t read_count); /* USER CODE END Prototypes */ #ifdef __cplusplus } #endif #endif /*__FMC_H */ /** * @} */ /** * @} */
参考资料
[1]: RM0433 STM32H7x3基于ARM内核的32位高级MCU
[2]: DS12110 32-bit Arm® Cortex®-M7 480MHz MCUs, up to 2MB Flash, up to 1MB RAM, 46 com.
[3]: Cortex_M7内核技术参考手册
[4]: MT48LC16M16A2P-6A数据手册
[5]: W9825G6KH-6数据手册
[6]: IS42S32800G数据手册
[7]: STM32 HAL库开发实战指南——基于野火F7与H7系列开发板
[8]: 安富莱STM32-V7 开发板用户手册
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。