T1_STM32F429_FMC_SDRAM
STM32F429IGT6-FMC-SDRAM配置
本文只记录学习内容,仅当笔记使用。
1. SDRAM IS42S16400J-7TLI 简介
1.1 逻辑Bank与芯片位宽
SDRAM内部是一个存储阵列,就如表格一样。和表格的检索原理一样,先指定一行(Row),在指定一列(Column),我们就可以准确的找到说需要的单元格,这既是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,表格可称为逻辑Bank(简称L-Bank)。

在进行寻址时要先确定是哪个L-Bank,然后再在这个选定的L-Bank中选择对应的行与列进行寻址。可见对内存的访问,一次只能一个L-Bank工作,而每次与内存控制器交换的数据就是L-Bank存储阵列中一个“存储单元”的容量。在某些厂商的表述中,将L-Bank中的存储单元称为Word。
而SDRAM内存芯片一次传输的数据量就是芯片的位宽,那么这个存储单元的容量就是芯片的位宽。L-Bank中的存储单元是基础的存储单元,它的容量是若干bit(SDRAM芯片的位宽),而每个bit则是存放于一个单独的存储中。这些存储体就是内存中最小的存储单元。
1.2 芯片的存储容量
内存芯片的容量就是所有L-Bank中的存储单元的容量总和。计算有多少个存储单元和计算表格中单元数量的方法是一样的:
存储单元数量 = 行数 * 列数 * L-Bank的数量
在很多内存产品介绍文档中,都会用(MW)的方式来表示芯片的容量。(M)是芯片中存储单元的总数,单位是兆(应为写 M),(W)代表每个存储单元的容量,也就是SDRAM芯片的位宽(Width),单位是bit。计算出来的芯片容量也是以bit为单位,但用户可以采用除以8的方法换算为字节(Byte)。比如4M16,就是一个16bit位宽芯片,有8M个存储单元,总容量是64Mbit(8MB)。
1.3 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:12] | I | 地址输入 |
| DQ[0:15] | I | 数据输入输出信号 |
2. SDRAM的基本操作于工作时序

-
命令禁止 (COMMAND INHBIT)
只要CS引脚为高电平,即表示“命令禁止”(COMMAND INHBIT),它用于禁止SDRAM执行新的命令,但它不能停止当前正在执行的命令。 -
空操作 (NO OPERATION)
“空操作”(NO OPERATION),“命令禁止”的反操作,用于选中SDRAM,以便接下来发送命令 -
行有效 (ACTIVE COMMAND)
进行存储单元寻址时,需要先选中要访问的Bank和行,使它处于激活状态。该操作通过“行有效”(ACTIVE)命令实现,发送行有效命令时,RAS线为低电平,同时通过BA线以及A线发送Bank地址和行地址。


TRCD:行有效命令到列读写命令之间的延迟时间。 -
列读读数据 (READ)
行地址通过“行有效”命令确定后,就要对列地址进行寻址了。“读命令”(READ)和“写命令”(WRITE)的时序很相似,通过共用的地址线A发送列地址,同时使用WE引脚表示读/写方向,WE为低电平时表示写,高电平时表示读。数据读写时,使用DQM线表示有效的DQ数据线。
如果选择了自动预充电模式,那么该行就会直接进入充电。如果没有选择此模式,那么该行将保持打开状态,供后续访问。
自动预充电模式的选择与A10的值有关,A10为高时为自动预充电命令模式。


CAS Latency,CAS 潜伏期:CAS与读取命令发出到第一笔数据输出的时间。只有读数据时有。
- 列写数据(WRITE)
行地址通过“行有效”命令确定后,就要对列地址进行寻址了。“读命令”(READ)和“写命令”(WRITE)的时序很相似,通过共用的地址线A发送列地址,同时使用WE引脚表示读/写方向,WE为低电平时表示写,高电平时表示读。数据读写时,使用DQM线表示有效的DQ数据线。
如果选择了自动预充电模式,那么该行就会直接进入充电。如果没有选择此模式,那么该行将保持打开状态,供后续访问。
自动预充电模式的选择与A10的值有关,A10为高时为自动预充电命令模式。


-
预充电(PRECHARGE)
读写时地址线A10控制着是否进行在读或写操作之后当前L-Bank自动进行预充电。
SDRAM 的寻址具有独占性,所以在进行完读写操作后,如果要对同一个Bank 的另一行进行寻址,就要将原来有效(ACTIVE)的行关闭,重新发送行/列地址。Bank 关闭当前工作行,准备打开新行的操作就是预充电(Precharge)。
预充电可以通过独立的命令控制,也可以在每次发送读写命令的同时使用“A10”线控制自动进行预充电。实际上,预充电是一种对工作行中所有存储阵列进行数据重写,并对行地址进行复位,以准备新行的工作。
独立的预充电命令时序见图。该命令配合使用A10线控制,若A10为高电平时,所有Bank都预充电,BA线无效;A10为低电平时,使用BA线选择要预充电的Bank。

-
刷新(Refresh)
-
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 有效才能退出自我刷新模式并进入正常操作状态。
-
Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个Bank的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096行时为15.625μs(微秒,1/1000毫秒),8192行时就为7.8125μs。HY57V561620为8192 refresh cycles / 64ms。
-
加载模式寄存器
前面提到SDRAM的控制逻辑是根据它的模式寄存器来管理整个系统的,而这个寄存器的参数就是通过“加载模式寄存器”命令(LOAD MODE REGISTER)来配置的。

-
数据掩码(QDM)
DQM就是掩码控制位,在sdram中每个DQM控制8bit Data。LDQM和UDQM控制I/O缓冲区的低位字节和高位字节。在读取UDQM模式下,LDQM和UDQM控制输出缓冲区。当LDQM或UDQM为低时,相应的缓冲区字节被启用,而当LDQM或UDQM为高时,相应的缓冲区字节被禁用。当LDQM/UDQM高时,输出变为高阻抗状态。此功能对应于传统DRAM中的OE。在写入模式下,LDQM和UDQM控制输入缓冲区。当LDQM或UDQM为低时,相应的缓冲区字节被启用,数据可以写入设备。当LDQM或UDQM为高时,输入数据被屏蔽,无法写入设备。

3. 代码实现
3.1 SDRAM初始化步骤
-
SDRAM芯片初始化
4.1 开启时钟
4.2 对所有bank预充电
4.3 自动刷新配置
4.4 SDRAM寄存器配置
4.5 刷新计数器配置
3.2 引脚配置
| 地址信号线 | 数据信号线 | 控制信号线 | 备注 |
|---|---|---|---|
| A0 -> PF0 | D0 -> PD14 | CKE -> PH7 | |
| A1 -> PF1 | D1 -> PD15 | CLK -> PG8 | |
| A2 -> PF2 | D2 -> PD0 | CAS#-> PG15 | |
| A3 -> PF3 | D3 -> PD1 | RAS#-> PF11 | |
| A4 -> PF4 | D4 -> PE7 | WE# -> PC0 | |
| A5 -> PF5 | D5 -> PE8 | BA0 -> PG4 | FMC_BA0 |
| A6 -> PF12 | D6 -> PE9 | BA1 -> PG5 | FMC_BA1 |
| A7 -> PF13 | D7 -> PE10 | CS# -> PH6 | |
| A8 -> PF14 | D8 -> PE11 | UDQM-> PE1 | FMC_NBL1 |
| A9 -> PF15 | D9 -> PE12 | LDQM-> PE0 | FMC_NBL0 |
| A10 -> PG0 | D10 -> PE13 | ||
| A11 -> PG1 | D11 -> PE14 | ||
| D12 -> PE15 | |||
| D13 -> PD8 | |||
| D14 -> PD9 | |||
| D15 -> PD10 |
配置引脚,开启对应FMC复用功能
点击查看折叠代码块
/**
* @brief 初始化控制SDRAM的IO
* @param 无
* @retval 无
*/
static void SDRAM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能SDRAM相关的GPIO时钟 */
/*地址信号线*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC |
RCC_AHB1Periph_GPIOD |
RCC_AHB1Periph_GPIOE |
RCC_AHB1Periph_GPIOF |
RCC_AHB1Periph_GPIOG |
RCC_AHB1Periph_GPIOH ,ENABLE);
/*-- GPIO 配置 -----------------------------------------------------*/
/* 通用 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;
/*地址信号线 针对引脚配置*/
//A0 GPIOF^0 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource0 , GPIO_AF_FMC);
//A1 GPIOF^1 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource1 , GPIO_AF_FMC);
//A2 GPIOF^2 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource2 , GPIO_AF_FMC);
//A3 GPIOF^3 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource3 , GPIO_AF_FMC);
//A4 GPIOF^4 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource4 , GPIO_AF_FMC);
//A5 GPIOF^5 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource5 , GPIO_AF_FMC);
//A6 GPIOF^12 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource12 , GPIO_AF_FMC);
//A7 GPIOF^13 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource13 , GPIO_AF_FMC);
//A8 GPIOF^14 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource14 , GPIO_AF_FMC);
//A9 GPIOF^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource15 , GPIO_AF_FMC);
//A9 GPIOF^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource2 , GPIO_AF_FMC);
//A9 GPIOF^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource2 , GPIO_AF_FMC);
/*数据信号线 针对引脚配置*/
//D0 GPIOD^14 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14 , GPIO_AF_FMC);
//D1 GPIOD^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15 , GPIO_AF_FMC);
//D2 GPIOD^0 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource0 , GPIO_AF_FMC);
//D3 GPIOD^1 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource1 , GPIO_AF_FMC);
//D4 GPIOE^7 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FMC);
//D5 GPIOE^8 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FMC);
//D6 GPIOE^9 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FMC);
//D7 GPIOE^10 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FMC);
//D8 GPIOE^11 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource11 , GPIO_AF_FMC);
//D9 GPIOE^12 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource12 , GPIO_AF_FMC);
//D10 GPIOE^13 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource13 , GPIO_AF_FMC);
//D11 GPIOE^14 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource14 , GPIO_AF_FMC);
//D12 GPIOE^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource15 , GPIO_AF_FMC);
//D13 GPIOD^8 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource8 , GPIO_AF_FMC);
//D14 GPIOD^9 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource9 , GPIO_AF_FMC);
//D15 GPIOD^10 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource10 , GPIO_AF_FMC);
/*控制信号线*/
//CKE GPIOH^7 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOH, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOH, GPIO_PinSource7 , GPIO_AF_FMC);
//CLK GPIOG^8 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource8 , GPIO_AF_FMC);
//CAS GPIOG^15 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource15 , GPIO_AF_FMC);
//RAS GPIOF^11 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource11 , GPIO_AF_FMC);
//WE GPIOC^0 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource0 , GPIO_AF_FMC);
//BA0 GPIOG^4 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource4 , GPIO_AF_FMC);
//BA1 GPIOG^5 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource5 , GPIO_AF_FMC);
//CS GPIOH^6 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOH, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOH, GPIO_PinSource6 , GPIO_AF_FMC);
//UDQM GPIOE^1 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource1 , GPIO_AF_FMC);
//LDQM GPIOE^0 -> AF12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource0 , GPIO_AF_FMC);
}
3.3 时序配置
由于采用主芯片是STM32F429IGT6主频为HCLK=180Hz,配置FMC输出的SDCLK时钟频率为HCLK的1/2(在后面的程序里配置的),即FSDCLK=90MHz,可得1个SDCLK时钟周期长度为TSDCLK=1/FSDCLK =11.11ns,然后设置各个成员的时候,只要保证时间大于以上SDRAM延时参数表的要求即可。如trc要求大于63ns,而11.11ns x 7=77.77ns,所以FMC_RowCycleDelay(TRC)成员值被设置为7个时钟周期,依葫芦画瓢完成时序参数的设置。
所有成员参数值的单位是周期,参数值大小都可以设置成“1-16”。
-
TMRD:发送加载模式寄存器命令后要等待的时间,过了这段时间才可以发送行有效或刷新命令。
-
TXSR:退出自我刷新命令后要等待的时间,过了这段时间才可以发送行有效命令。
-
TRAS:发送行有效命令后要等待的时间,过了这段时间才执行预充电命令。
-
TRC:两个行有效命令之间的延迟,以及两个相邻刷新命令之间的延迟。
-
TWR:写命令和预充电命令之间的延迟,等待这段时间后才开始执行预充电命令。
-
TRP:预充电命令与其他命令之间的延迟。
-
TRCD:行有效命令到列读写命令之间的延迟。





点击查看折叠代码块
/* 配置 FMC 相关参数 ---------------------------------------------------------*/
/* SDCLK: 90 Mhz (HCLK/2 :180Mhz/2) */
/* TMRD: 2 Clock cycles */
FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;
/* TXSR: min=70ns (7x11.11ns) */
FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;
/* TRAS: min=42ns (4x11.11ns) max=120k (ns) */
FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;
/* TRC: min=70 (7x11.11ns) */
FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7;
/* TWR: min=1+ 7ns (1+1x11.11ns) */
FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;
/* TRP: 20ns => 2x11.11ns */
FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;
/* TRCD: 20ns => 2x11.11ns */
FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;
参考图表
- SDRAM_IS42S16400J-7TLI数据手册——21页
允许操作的最大时钟设置,由于采用主芯片是STM32F429IGT6主频为HCLK=180Hz,配置FMC输出的SDCLK时钟频率为HCLK的1/2(在后面的程序里配置的),即FSDCLK=90MHz,所以只需配置CAS Latency=2得到最大允许时钟为133Mhz,大于输出时钟频率就可以了,即90MHz < 133MHz。IS42S16400J-6TLI对应的速度为6。


2. STM32F4XX中文参考手册2-171页


- SDRAM_IS42S16400J-7TLI数据手册——17页
SDRAM的型号为IS42S16400J-7TLI对应的速度为-7,芯片型号为IS42S16400J-6TLI对应速度为-6,本项目采用的芯片为IS42S16400J-7TLI,因此只看对应芯片型号的速度 “-7” 列,其他“-6”,“-5”列不需要关注。


3.4 控制配置
-
FMC_Bank:选择FMC映射的SDRAM存储区域,可选择存储区域1或2 (FMC_Bank1_SDRAM/FMC_Bank2_SDRAM)
-
FMC_ColumnBitsNumber:设置要控制的SDRAM的列地址宽度,可选择8-11位(FMC_ColumnBits_Number_8b/9/10/11b)
-
FMC_RowBitsNumber:设置要控制的SDRAM的行地址宽度,可选择设置成11-13位(FMC_RowBits_Number_11b/12/13b)
-
FMC_SDMemoryDataWidth:设置要控制的SDRAM的数据宽度,可选择设置成8、16或32位(FMC_SDMemory_Width_8b/16/32b)
-
FMC_InternalBankNumber: 设置要控制的SDRAM的内部Bank数目,可选择设置成2或4个Bank数目(FMC_InternalBank_Number_2/4),请注意区分这个结构体成员与Bank的区别
-
FMC_CASLatency:设置CASLatency即CL的时钟数目,可选择设置为1、2或3个时钟周期(FMC_CAS_Latency_1/2/3)
-
FMC_WriteProtection:设置是否使能写保护模式,如果使能了写保护则不能向SDRAM写入数据,正常使用都是禁止写保护的
-
FMC_SDClockPeriod:设置FMC与外部SDRAM通讯时的同步时钟参数,可以设置成STM32的HCLK时钟频率的1/2、1/3或禁止输出时钟(FMC_SDClock_Period_2/FMC_SDClock_Disable)
-
FMC_ReadBurst:设置是否使能突发读取模式,禁止时等效于BL=1(突发长度=1),使能时BL的值等于模式寄存器中的配置
-
FMC_ReadPipeDelay:配置在CASLatency个时钟周期后,再等待多少个HCLK时钟周期才进行数据采样,在确保正确的前提下,这个值设置为越短越好,可选择设置的参数值为0、1或2个HCLK时钟周期(FMC_ReadPipe_Delay_0/1/2)。
配置完SDRAM初始化结构体后,调用FMC_SDRAMInit函数把这些配置写入到FMC的SDRAM控制寄存器及时序寄存器,实现FMC的初始化
点击查看折叠代码块
/* FMC SDRAM 控制配置 */
FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;
/* 行地址线宽度: [7:0] */
FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
/* 列地址线宽度: [11:0] */
FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b;
/* 数据线宽度 */
FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = FMC_SDMemory_Width_16b;
/* SDRAM内部bank数量*/
FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4;
/* CAS潜伏期 */
FMC_SDRAMInitStructure.FMC_CASLatency = FMC_CAS_Latency_2;
/* 禁止写保护*/
FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable;
/* SDCLK时钟分频因子,SDCLK = HCLK/SDCLOCK_PERIOD*/
FMC_SDRAMInitStructure.FMC_SDClockPeriod = FMC_SDClock_Period_2;
/* 突发读模式设置*/
FMC_SDRAMInitStructure.FMC_ReadBurst = FMC_Read_Burst_Enable;
/* 读延迟配置 */
FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_0;
/* SDRAM时序参数 */
FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure;
参考图表
- SDRAM_IS42S16400J-7TLI数据手册——21页
允许操作的最大时钟设置,由于采用主芯片是STM32F429IGT6主频为HCLK=180Hz,配置FMC输出的SDCLK时钟频率为HCLK的1/2(SDCLK时钟分频因子,FMC_SDClock_Period_2),即FSDCLK=90MHz,所以只需配置CAS Latency=2得到最大允许时钟为133Mhz,大于输出时钟频率就可以了,即90MHz < 133MHz。

- SDRAM_IS42S16400J-7TLI数据手册——1页
存储单元数量 = 行数(存储单元总数) * 列数(每个存储单元容量) * L-Bank的数量
整个的SDRAM的容量=存储单元的总数(1M)每个存储单元的容量(16bit) =4M * 16bit =64 Mbit = 8 MByte


截取地址https://blog.csdn.net/caihaitao2000/article/details/79875609
- STM32F4XX中文参考手册2-161页
- FMC_Bank2_SDRAM ——》SDRAM芯片连接的是STM32主芯片的FMC_SDCKE1和FMC_SDNE1两个引脚
- FMC_Bank2_SDRAM ——》SDRAM芯片连接的是STM32主芯片的FMC_SDCKE0和FMC_SDNE0两个引脚
因为硬件连接的是bank2,所以软件FMC_Bank选择FMC_Bank2_SDRAM

3.5 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) 初始化流程完毕,可以开始读写数据。
其中tRP、tRFC、tMRD等时间参数跟具体的SDRAM有关,可查阅其数据手册获知,STM32 FMC访问时配置需要这些参数。
typedef struct
{
uint32_t FMC_CommandMode;//发送的命令
uint32_t FMC_CommandTarget;//目标存储区域 bank1 bank2
uint32_t FMC_AutoRefreshNumber;//自动刷新次数,其他命令无效
uint32_t FMC_ModeRegisterDefinition;//加载模式寄存器,其他命令无效
}FMC_SDRAMCommandTypeDef;
3.5.1 开启时钟
点击查看折叠代码块
/* 配置命令:开启提供给SDRAM的时钟 */
FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled;
FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2; //选择目标存储器区域
FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 0; //自动刷新次数 其他命令用不到
FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; //加载模式寄存器 其他命令用不到
3.5.2 对所有bank预充电
点击查看折叠代码块
/* 配置命令:对所有的bank预充电 */
FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL;
FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 0;
FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
3.5.3 自动刷新配置
点击查看折叠代码块
/* 配置命令:自动刷新 */
FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh;
FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 2;//2个自动刷新命令
FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
3.5.4 SDRAM寄存器配置
点击查看折叠代码块
/* 设置sdram寄存器配置 */
uint32_t tmpr = 0;
tmpr = (uint32_t) SDRAM_MODEREG_BURST_LENGTH_4 | //突发长度为4
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //突发模式为顺序模式和FMC控制寄存器配置相同
SDRAM_MODEREG_CAS_LATENCY_2 | //CASLatency 2周期
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //正常模式
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //写为非突发模式
/* 配置命令:设置SDRAM寄存器 */
FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode;
FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr;

寄存器各个参数定义
(1) Burst Length: 突发长度,下面简称BL。突发是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。BL可用的选项是 1、2、4、8,常见的设定是 4 和8。
(2) Burst Type: 设置突发模式。突发模式分为顺序(Sequential)与间隔(Interleaved)两种。在顺序方式中,操作按地址的顺序连续执行,如果是间隔模式,则操作地址是跳跃的。
(3) Latency Mode: 设置CASLatency列地址选通延迟,简称CL。在发出读命令(命令同时包含列地址)后,需要等待几个时钟周期数据线DQ才会输出有效数据,这之间的时钟周期就是指CL,CL一般可以设置为2或3个时钟周期。只有列读数据模式才有,写数据没有CASLatency。
(4) Operating Mode: SDRAM的工作模式。当它被配置为“00”的时候表示工作在正常模式,其它值是测试模式或被保留的设定。实际使用时必须配置成正常模式。
(5) Write Burst Mode: 配置写操作的突发特性。当M9=0时。根据BL设置读和写的突发长度;当M9=1时,根据BL设置读的突发长度,写设置为非突发模式
3.5.5 刷新计数器配置
点击查看折叠代码块
/* 设置刷新计数器 */
/*刷新速率 = (COUNT + 1) x SDRAM 频率时钟
COUNT =( SDRAM 刷新周期/行数) - 20*/
/* 64ms/4096=15.625us (15.625 us x FSDCLK) - 20 =1386 */
FMC_SetRefreshCount(1386);
参考图表
- SDRAM_IS42S16400J-7TLI数据手册——1页
刷新周期64ms,4096行。Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个Bank的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096行时为15.625μs(微秒,1/1000毫秒),8192行时就为7.8125μs。HY57V561620为8192 refresh cycles / 64ms。

3.6 读写操作
由于SDRAM的存储空间是被映射到内核的寻址区域的,我们可以通过映射的地址直接访问SDRAM,访问这些地址时,FMC外设自动读写SDRAM,程序上无需额外操作。
(1)通过地址访问内存。最直接的方式就是使用C语言的指针方式了
点击查看折叠代码块
/*SDRAM起始地址存储空间2的起始地址*/
#define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)
/*SDRAM 大小,8M字节*/
#define IS42S164J_SIZE 0X800000
uint32_t temp;
/*向SDRAM写入数据*/
*(uint8_t*) SDRAM_BANK_ADDR = (uint8_t )0xAA;
/*向SDRAM读取数据*/
temp = *(uint8_t *)(SDRAM_BANK_ADDR);
/*写/读 16位数据*/
*( uint16_t*) (SDRAM_BANK_ADDR+10 ) = (uint16_t)0xBBBB;
temp = *( uint16_t*) (SDRAM_BANK_ADDR+10 );
/*写/读 32位数据*/
*( uint32_t*) (SDRAM_BANK_ADDR+20 ) = (uint32_t)0xCCCCCCCC;
temp = *( uint32_t*) (SDRAM_BANK_ADDR+20 );
(2)直接指定变量存储到SDRAM空间
点击查看折叠代码块
/*SDRAM起始地址存储空间2的起始地址*/
#define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)
/*绝对定位方式访问SDRAM,这种方式必须定义成全局变量*/
uint8_t testValue __attribute__((at(SDRAM_BANK_ADDR)));
testValue = 0xDD;
这种方式使用关键字"attribute((at()))"来指定变量的地址,代码中指定testValue存储到SDRAM的起始地址,从而实现把变量存储到SDRAM上。要注意使用这种方法定义变量时,必须在函数外把它定义成全局变量,才可以存储到指定地址上。

浙公网安备 33010602011771号