T1_STM32F429_FMC_SDRAM

STM32F429IGT6-FMC-SDRAM配置

本文只记录学习内容,仅当笔记使用。

1. SDRAM IS42S16400J-7TLI 简介

1.1 逻辑Bank与芯片位宽

SDRAM内部是一个存储阵列,就如表格一样。和表格的检索原理一样,先指定一行(Row),在指定一列(Column),我们就可以准确的找到说需要的单元格,这既是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,表格可称为逻辑Bank(简称L-Bank)。
image
在进行寻址时要先确定是哪个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引脚于封装

image

信号线 类型 说明
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的基本操作于工作时序

image

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

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

  3. 行有效 (ACTIVE COMMAND)
    进行存储单元寻址时,需要先选中要访问的Bank和行,使它处于激活状态。该操作通过“行有效”(ACTIVE)命令实现,发送行有效命令时,RAS线为低电平,同时通过BA线以及A线发送Bank地址和行地址。
    image
    image
    TRCD:行有效命令到列读写命令之间的延迟时间。

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

image
image
CAS Latency,CAS 潜伏期:CAS与读取命令发出到第一笔数据输出的时间。只有读数据时有。

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

image
image

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

  2. 刷新(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。

  1. 加载模式寄存器
    前面提到SDRAM的控制逻辑是根据它的模式寄存器来管理整个系统的,而这个寄存器的参数就是通过“加载模式寄存器”命令(LOAD MODE REGISTER)来配置的。
    image

  2. 数据掩码(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为高时,输入数据被屏蔽,无法写入设备。
    image

3. 代码实现

3.1 SDRAM初始化步骤

  1. 引脚配置

  2. 时序配置

  3. 控制配置

  4. SDRAM芯片初始化
    4.1 开启时钟
    4.2 对所有bank预充电
    4.3 自动刷新配置
    4.4 SDRAM寄存器配置
    4.5 刷新计数器配置

  5. 读写操作

  6. 代码下载

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

image
image
image
image
image

点击查看折叠代码块
/* 配置 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;

参考图表

  1. SDRAM_IS42S16400J-7TLI数据手册——21页
    允许操作的最大时钟设置,由于采用主芯片是STM32F429IGT6主频为HCLK=180Hz,配置FMC输出的SDCLK时钟频率为HCLK的1/2(在后面的程序里配置的),即FSDCLK=90MHz,所以只需配置CAS Latency=2得到最大允许时钟为133Mhz,大于输出时钟频率就可以了,即90MHz < 133MHz。IS42S16400J-6TLI对应的速度为6。

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

image
image

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

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;

参考图表

  1. 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。
    image
  2. SDRAM_IS42S16400J-7TLI数据手册——1页
    存储单元数量 = 行数(存储单元总数) * 列数(每个存储单元容量) * L-Bank的数量
    整个的SDRAM的容量=存储单元的总数(1M)每个存储单元的容量(16bit) =4M * 16bit =64 Mbit = 8 MByte

image
image
截取地址https://blog.csdn.net/caihaitao2000/article/details/79875609

  1. 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

image

3.5 SDRAM芯片初始化

image

(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;

image

寄存器各个参数定义
(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);

参考图表

  1. 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。
    image

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上。要注意使用这种方法定义变量时,必须在函数外把它定义成全局变量,才可以存储到指定地址上。

posted @ 2021-08-11 17:08  kj~  阅读(937)  评论(0)    收藏  举报