STM32笔记之 SDRAM

Posted 夏沫の浅雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32笔记之 SDRAM相关的知识,希望对你有一定的参考价值。

写在前面:

本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

目录


SDRAM芯片

简知

SDRAM (Synchronous Dynamic RandomAccess Memory),同步动态随机存储器。同步是指其时钟频率与CPU的前端总线的系统时间频率相同,并且他的内部命令的发送与数据的传输都是以这个时钟为基准的,动态是指存储阵列需要不断的刷新才能保证数据的不丢失。随机是指数据不是线性存储的,是可以自由指定地址进行数据读写。

以常见的 SDRAM芯片为例,一般单片机外围搭配用 TSOP-54pin的 SDRAM,当然也有其他封装的,像 BGA/FBGA这种,这里以 TSOP封装的为例子吧,其它的信息其实都大体相同的。如下图:TSOP-54pin中 8bit data width(左边)和 16bit data width(右边)

上图两个之间的区别是(非引脚结构层面上)左边的一次操作 8bit(一个字节)的数据,而右边的一次可操作 16bit(双字节)的数据,所以从操作上,右边处理数据比左边的要快一倍;而对于 32bit data width 的 SDRAM芯片,则比前面的处理更快了。

类型

类型预取数据速率示例Banks 数量VDD
SDRAM133 MT/s/pin4 Bank3.3 V
DDR SDRAM2 预取400 MT/s/pin4 Bank2.5 V
DDR2 SDRAM4 预取800 MT/s/pin4 or 8 Bank1.8 V (DDR2L 1.5 V)
DDR3 SDRAM8 预取1600 MT/s/pin8 Bank1.5 V (DDR3L 1.35 V)

引脚定义

对于 SDRAM芯片,无论是多少数据位宽的,它的信号引脚分类都是如下几种:

引脚说明
A[0 : x]地址输入
DQ[0 : x]数据输入 / 输出
BA[0 : x]Bank选择地址,选择要控制的 Bank
CLK同步时钟信号,所有输入信号都在 CLK为上升沿时被采集
CKE时钟使能信号,禁止时钟信号时 SDRAM会启动自动刷新操作
CS#片选信号,低电平有效
RAS#行地址选通,为低电平时地址线输入信号表示为行地址
CAS#列地址选通,为低电平时地址线输入信号表示为列地址
DQM[0 : x]数据输入 / 输出掩码信号,表示 DQ信号的有效部分

note:

  • DQM[0 : x]:掩码控制位,在 SDRAM 中每个 DQM 引脚控制 8bit Data。在读操作的时候没多大影响,比如读 32bit 数据位宽的 SDRAM,但只要其中的 8bit 数据,则只须先读出 32bit 数据,再在软件里将其余的 24bit 和 0 作 “与” 操作即可,有没有 DQM 关系不大;但在执行写操作时,如果没有 DQM 就麻烦了,可能在软件上是写一个 8bit 数据,但实际上 32 根数据线是物理上连接到 SDRAM 的,只要 WR 信号一出现,这 32bit 数据就会写 SDRAM 中去,而多余的 24bit 数据将会被覆盖,如果通过使用 DQM 就可以将其对应的 24bit 屏蔽,不会因为写操作而覆盖数据了。对于 16bit 数据位宽的 SDRAM,一般描述为 LDQM 和 UDQM 或者是 DQML 和 DQMH,嘛一般看 datasheet知道对应哪个就行了。

  • BA[0 : x]:BA 信号决定了激活哪一个 Banks,一般 SDRAM多为 4-Bank。BA 信号的多少基本决定了 Banks 的多少,例如,BA[0 : 1] -> 2^2 = 4-Bank。8-Bank可以看下 https://www.micross.com/pdf/MYX4DD3256M16GB_DS_REV-1.pdf 的 datasheet,看 BA 信号有多少个引脚。

内存容量

IS42S16160J datasheet 中的 16Meg x16为例。

从右上角的方框中,我们可以直接获取到该芯片内存容量为 256Mb = 16MB(Mb表示 M bit,MB表示 M byte),但往往有些 datasheet并没有直观表示容量,那么可以考虑另一种获取的方法。

内存容量除了从 datasheet中直接获取,你也可以直接从 16M x 16 或者 4M x 16 x 4 Banks 来直接计算,单位为 bit,这都是可以的;在这里,更推荐从 SDRAM 的 Bank、行地址、列地址及数据地址数量来获取,因为内存容量由存储深度和存储宽度决定,这是任何存储芯片存储容量的定义:

  • Bank:BA[0 : 1] -> 2^2 = 4 Banks
  • Row addr:A[0 : 12] -> 2^13 = 8192 Rows
  • Column addr:A[0 : 8] -> 2^9 = 512 Coulumns

当它是 16bit data width(也有 8bit data width,即 54 pin TSOP - Type II for x8封装芯片,后面实例用到),

存储深度: IS42S16160J for x16 内部有 4个 Bank,每个 Bank 有行地址 13bit、列地址 9bit;所以每个 Bank 就有 2^13 * 2^9 = 8192 * 512 = 4194304 个存储单元,4个 Bank 就有 4 * 4194304 = 16777216 个存储单元。

存储宽度: 该 SDRAM 的数据位宽为 16bit。

存储容量: 16777216 * 16bit = 268435456bit,就是 32MB。

即:4 * 8192 * 512 * 16 = 2^28 = 256 Mbit = 32MByte

对此它的计算方法可以归算为:Bank、行地址、列地址及数据地址数量彼此之间的乘积。


硬件设计

芯片引脚对应

FMC引脚SDRAM引脚说明
SDCLKCLKSDRAM 时钟
SDCKE[1 : 0]CKESDCKE0:SDRAM 存储区域 1 时钟使能
SDCKE1:SDRAM 存储区域 2 时钟使能
SDNE[1 : 0]CS#SDNE0:SDRAM 存储区域 1 芯片使能
SDNE1:SDRAM 存储区域 2 芯片使能
A[12 : 0]A[0 : x]行 / 列地址线
D[31 : 0]DQ[0 : x]双向数据总线
BA[1 : 0]BA[0 : x]Bank地址线
NRASRAS#行地址选通
NCASCAS#列地址选通
SDNWE-写入使能(当使用两个存储区域时需要)
NBL[3 : 0]DQM[0 : x]写访问的输出字节屏蔽

特殊脚 BA(Bank地址线)

对于某些芯片来说,可能并没有标出 BA[0 : x] 引脚,对于这种,要么手册上有说明,要么给出引脚连接图,像 NXP LPC某些系列中:

可以看到并没有标出 BA[0 : x] 引脚,但手册另外有说明:

在 S3C2440芯片中则给出连接示意图:

关于数据线交换问题

能交换的原因来源于其数据访问的特性,并且命令操作不需要涉及到数据信号线,一般是用地址线配合其他控制线来访问(某些特殊的除外),因此地址万万不能交换,而且数据线交换也是有条件的,如下说明。

通常以最小访问位宽,一般八个起始连续位(一个字节)为一组;组内可交换,但不能跨组交换;整组间可交换,但同时还要交换对应控制脚 DQMx。

例如: 16位 SDRAM 有 UDQM 和 LDQM 控制高 8位和低 8位掩码。

对于组内交换,其实也挺简单,就是不管你怎么变的,CPU放进去的是 0xF0 ,读出来的也是 0xF0 就行了,你变了顺序的话,只是 0xF0 这个数在 RAM 中存的形式不同;假设你高四位和低四位换了,CPU写进去时是 0xF0 (11110000b) ,但到了 RAM 存在形式是 00001111 ,而你 CPU 再读取到寄存器后还是 0xF0,这就够了。也就是说负负得正,错错得对,明白了吧,调了线序,写进去数据会错位,但读出来也错一次,所以就没有问题了。。。只是改变了在 RAM中的存在格式。所以 DQ0 ~ DQ7 之间可以任意交换,DQ8 ~ DQ15 之间可以任意交换,但像 D8 和 D0 ~ D7 里的数据线就不能交换(即不能跨组交换)。

对于整组间交换,即 D0 ~ D7 整组与 D8 ~ D15 整组进行交换,由于最小访问位宽为 8bit,因此当以 8bit 访问读写时是没有任何数据错乱问题的,但当访问 16bit 以上多字节访问时就会出现错误,这是由于 DQMx 掩码的关系,当进行访问的时候,因为只对数据组交换,而 DQMx 掩码没交换,就会出现对端数据访问交换。


几个重要结构体

初始化结构体

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

    uint32_t FMC_Bank;                /* 选择 FMC的 SDRAM存储区域 */
    uint32_t FMC_ColumnBitsNumber;    /* 定义 SDRAM的列地址宽度 */
    uint32_t FMC_RowBitsNumber;       /* 定义 SDRAM的行地址宽度 */
    uint32_t FMC_SDMemoryDataWidth;   /* 定义 SDRAM的数据宽度 */
    uint32_t FMC_InternalBankNumber;  /* 定义 SDRAM内部的 Bank数目 */
    uint32_t FMC_CASLatency;          /*定义 CASLatency的时钟个数 */
    uint32_t FMC_WriteProtection;     /* 定义是否使能写保护模式  */
    uint32_t FMC_SDClockPeriod;       /* 配置同步时钟 SDCLK的参数 */
    uint32_t FMC_ReadBurst;           /* 是否使能突发读模式*/
    uint32_t FMC_ReadPipeDelay;       /* 定义在 CAS个延迟后再等待多少个 HCLK时钟才读取数据 */
FMC_SDRAMTimingInitTypeDef* FMC_SDRAMTimingStruct; /* 定义 SDRAM的时序参数

 FMC_SDRAMInitTypeDef;

这部分参数,一般从硬件设计及 SDRAM芯片选型中就能确认了,不需要怎么翻看 datasheet,就算需要查看,在首页产品介绍中也能看到。

  • FMC_Bank: FMC映射到 SDRAM 有两个 bank 可以选择;根据外围 SDRAM硬件连接来决定选择。选择不同的 Bank,SDRAM 需要接到 MCU 不同的

    SDNE 以及 SDCKE 引脚,并且访问的地址也不同,如下图所示:


  • FMC_ColumnBitsNumber、FMC_RowBitsNumber、FMC_SDMemoryDataWidth、FMC_InternalBankNumber: 这几项分别对应列地址、行地址、数据位宽、Bank的数量,这里可以从上面内存容量贴的那张图可以了解到,就不再过多阐述了。

  • FMC_CASLatency: 可以设置 Latency1,Latency2 和 Latency3,具体选择哪个,可从下图中选择跟自己设计相匹配的参数,并且这个参数将决定后面时序结构的参考选择

    一般来讲都是选择 Latency3,143MHz,速度等级是 -7。

  • FMC_WriteProtection: 决定是否使用写保护。

  • FMC_SDClockPeriod: 输出到 SDRAM CLK的时钟分频因子配置,同样的这个参数将决定后面时序结构的参考选择。而 FMC 的工作时钟来自 HCLK,如下图;一般来说 F429 的主频可以到 168/180M,那么 HCLK 就是 168/180M,参数可选 2 / 3分频,由于上面 FMC_CASLatency 的配置,SDRAM 支持的最大时钟频率达 143MHz,因此大多数 FMC 时钟分频比选择最小的 2分频,则在主频 180M 时,FMC 频率就是 90M。

  • FMC_ReadBurst、FMC_ReadPipeDelay: 这两个是根据实际读写情况来配置的,前者用于突发读处理,后者定义了在 CAS 延时期间预测延后多少个 SDRAM 时钟周期才读取数据。

时序结构体

/* @brief
 * 控制SDRAM的时序参数,这些参数的单位都是“周期”
 * 各个参数的值可设置为 1 - 16个 Clock cycles。
 */
typedef struct

    uint32_t FMC_LoadToActiveDelay;    /* TMRD */
    uint32_t FMC_ExitSelfRefreshDelay; /* TXSR */
    uint32_t FMC_SelfRefreshTime;      /* TRAS */
    uint32_t FMC_RowCycleDelay;        /* TRC */
    uint32_t FMC_WriteRecoveryTime;    /* TWR */
    uint32_t FMC_RPDelay;              /* TRP */
    uint32_t FMC_RCDDelay;             /* TRCD */
 FMC_SDRAMTimingInitTypeDef;

这部分的配置参数跟 datasheet 息息相关,并且在 STM32F4中,官方把这几个参数统一存放到如下的一个寄存器中:

那么这几个参数到底从何而来呢?继续使用 IS42S16160J 的 datasheet,然后逐个分析,首先得获取信息的所在位置,如下图:

第一张图,是该芯片的电气特性参数,第二张是基于第一张给出的参考配置数据(看绿框中的描述),也就是说,你可以直接用现成的第二张的数据填到属性配置中,但是,话都撂这儿了,不解释一下好像有点不好意思。。

FMC_SDRAMTimingInitTypeDef 时序结构体中,里面的参数都是以 cycle 为单位

  • FMC_LoadToActiveDelay: 对应 tMRD(TMRD)参数;TMRD 定义模式寄存器设置命令或刷新命令所需时间。
  • FMC_ExitSelfRefreshDelay: 对应 tXSR(TXSR)参数;TXSR 定义从退出自刷新命令到发出活动命令之间的时间。
  • FMC_SelfRefreshTime: 对应 tRAS(TRAS)参数;TRAS 定义从活动命令到预充电命令所需时间。
  • FMC_RowCycleDelay: 对应 tRC(TRC)参数;TRC 定义刷新命令之间或者活动命令之间的时间。
  • FMC_WriteRecoveryTime: 对应 tDAL(TWR)参数;在 SDRAM手册上并没有 TWR 这样的标识,但是根据定义的涵义,表明:从输入数据到活动命令或者刷新命令所需的时间。因此能从 datasheet 中找到相应的 tDAL 参数;这也就说明了,有时候软件配置参数中并没有清楚写出 tMRD,tRAS 等字样,而是需要自己去了解每个配置参数的意义,才能从 SDRAM 的手册中找到对应的参数填充。
  • FMC_RPDelay: 对应 tRP(TRP)参数;TRP 定义从预充电命令到活动命令所需时间,刚好跟 tRAS 相反。
  • FMC_RCDDelay: 对应 tRP(TRP)参数;TRP 定义从活动命令到读 /写命令所需时间。

好了,解释完参数后,就来计算一下怎么把时间参数转换成所需的周期参数配置吧。至于你说为什么不像前面说的那样直接用第二张给出的书呢?一是,细心的可能会发现并没有给出 tXSR;二是选取不同的设计参数,将导致不一样的周期参数配置,还记得上面初始化结构体说的 FMC_CASLatencyFMC_SDClockPeriod 将决定时序周期数吧。

怎么算?其实很简单:

这里以 HCLK 为 168MHz 为例,FMC_SDClockPeriod = FMC_SDClock_Period_2 即对 HCLK 进行二分频,得到输出给 SDRAM CLK 的频率为 84MHz;而 FMC_CASLatency = FMC_CAS_Latency_3,对应 datasheet 选为常见的 Latency3143MHz,速度等级是 -7(这里的 143MHz 为 SDRAM 最大支持频率,但实际我们就只用到了 84MHz)。然后以其中的一个时序配置参数 tRC 为例:

从上图可以看到,在 -7 等级中,tRC 的时间参数只有最小值是值得关注的,对于 配置的 FMC 驱动频率 84MHz,则一个 SDRAM 周期时间约为 11.90ns(即结构体参数的单位都是 11.90ns),那么在 datasheet 中限定 tRC > 60ns,要使得满足条件,只有 FMC_RowCycleDelay >= 6 时方可满足,因此我们只需取其最靠近的值即可,这样就能在满足条件的情况下也不会丢失时间性能(当然是在稳定的前提下),其余的值计算等同,就不再阐述了,自己验算。最终得到的数据,看下面的代码示例吧。


示例代码

驱动 IS42S16160J for x8(4-Bank & 8bit data width & 9bit column addr & 13bit row addr)

API 版本:

/* Private defines -----------------------------------------------------------*/
#define SDRAM_BANK_ADDR                 ((uint32_t)0xC0000000)

#define SDRAM_MEMORY_WIDTH            FMC_SDMemory_Width_8b
/* #define SDRAM_MEMORY_WIDTH            FMC_SDMemory_Width_16b */
/* #define SDRAM_MEMORY_WIDTH            FMC_SDMemory_Width_32b */ 

/* #define SDRAM_CAS_LATENCY             FMC_CAS_Latency_1 */
/* #define SDRAM_CAS_LATENCY             FMC_CAS_Latency_2 */
#define SDRAM_CAS_LATENCY             FMC_CAS_Latency_3

#define SDCLOCK_PERIOD                FMC_SDClock_Period_2
/* #define SDCLOCK_PERIOD                FMC_SDClock_Period_3 */

#define SDRAM_TIMEOUT     ((uint32_t)0xFFFF) 

#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) 

/**
  * @brief  FMC SDRAM Configuration
  * @param  None
  * @retval None
  */
static void FMC_Config(void)
  
  GPIO_InitTypeDef            GPIO_InitStructure;
  FMC_SDRAMInitTypeDef        FMC_SDRAMInitStructure;
  FMC_SDRAMTimingInitTypeDef  FMC_SDRAMTimingInitStructure;
  FMC_SDRAMCommandTypeDef     FMC_SDRAMCommandStructure;
  
  uint32_t tmpr = 0;
  uint32_t timeout = SDRAM_TIMEOUT;

  /* GPIO configuration ------------------------------------------------------*/ 
  /* Enable GPios clock */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE | 
                         RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH | 
                         RCC_AHB1Periph_GPIOI, ENABLE);
                         
  /* Common GPIO configuration */
  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;
  
  /* GPIOD configuration */
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FMC);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0  |GPIO_Pin_1  |GPIO_Pin_8 |GPIO_Pin_9 |
                                GPIO_Pin_10 |GPIO_Pin_14 |GPIO_Pin_15;

  GPIO_Init(GPIOD, &GPIO_InitStructure);

  /* GPIOE configuration */
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource0 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource1 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource11 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource12 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource13 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource14 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOE, GPIO_PinSource15 , GPIO_AF_FMC);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0  | GPIO_Pin_1  | GPIO_Pin_7 | GPIO_Pin_8  |
                                GPIO_Pin_9  | GPIO_Pin_10 | GPIO_Pin_11| GPIO_Pin_12 |
                                GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

  GPIO_Init(GPIOE, &GPIO_InitStructure);

  /* GPIOF configuration */
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource0 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource1 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource2 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource3 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource4 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource5 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource11 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource12 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource13 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource14 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOF, GPIO_PinSource15 , GPIO_AF_FMC);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0  | GPIO_Pin_1  | GPIO_Pin_2  | GPIO_Pin_3  |
                                GPIO_Pin_4  | GPIO_Pin_5  | GPIO_Pin_11 | GPIO_Pin_12 |
                                GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;      

  GPIO_Init(GPIOF, &GPIO_InitStructure);

  /* GPIOG configuration */
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource0 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource1 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource4 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource5 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource8 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOG, GPIO_PinSource15 , GPIO_AF_FMC);
  

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1 |GPIO_Pin_4 |GPIO_Pin_5 |
                                GPIO_Pin_8 | GPIO_Pin_15;

  GPIO_Init(GPIOG, &GPIO_InitStructure);
   
  /* GPIOH configuration */
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource2 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource3 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource5 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource8 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource9 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource10 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource11 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource12 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource13 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource14 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOH, GPIO_PinSource15 , GPIO_AF_FMC);
  

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2  | GPIO_Pin_3  | GPIO_Pin_5 | GPIO_Pin_8  | 
                                GPIO_Pin_9  | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | 
                                GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;      

  GPIO_Init(GPIOH, &GPIO_InitStructure);

  /* GPIOI configuration */
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource0 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource1 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource2 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource3 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource4 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource5 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource6 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource7 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource9 , GPIO_AF_FMC);
  GPIO_PinAFConfig(GPIOI, GPIO_PinSource10 , GPIO_AF_FMC);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | 
                                GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 |
				                        GPIO_Pin_9 | GPIO_Pin_10; 
  
  GPIO_Init(GPIOI, &GPIO_InitStructure);
      
  /* Enable FMC clock */
  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
  
  /* FMC SDRAM device initialization sequence --------------------------------*/ 
  /* Step 1 ----------------------------------------------------*/ 
  /* Timing configuration for 84 Mhz of SD clock frequency (168Mhz/2) */
  /* TMRD: min=14ns (2x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay    = 2;      
  /* TXSR: min=70ns (6x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 6;
  /* TRAS: min=42ns max=100Kns (4x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime      = 4;
  /* TRC:  min=60ns (6x11.90ns) */        
  FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay        = 6;         
  /* TWR:  min=30ns (2x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime    = 2;      
  /* TRP:  min=15ns (2x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_RPDelay              = 2;                
  /* TRCD: min=315ns (2x11.90ns) */
  FMC_SDRAMTimingInitStructure.FMC_RCDDelay             = 2;
  
  /* Step 2 ----------------------------------------------------*/
  /* FMC SDRAM control configuration */
  FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank1_SDRAM;
  
  /* Row addressing: [8:0] */
  FMC_SDRAMInitStructure.FMC_ColumnBitsNumber   = FMC_ColumnBits_Number_9b;
  /* Column addressing: [12:0] */
  FMC_SDRAMInitStructure.FMC_RowBitsNumber      = FMC_RowBits_Number_13b;
  FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth  = SDRAM_MEMORY_WIDTH;
  FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4;
  /* CL: Cas Latency = 3 clock cycles */
  FMC_SDRAMInitStructure.FMC_CASLatency         = SDRAM_CAS_LATENCY; 
  FMC_SDRAMInitStructure.FMC_WriteProtection    = FMC_Write_Protection_Disable;
  FMC_SDRAMInitStructure.FMC_SDClockPeriod      = SDCLOCK_PERIOD;  
  FMC_SDRAMInitStructure.FMC_ReadBurst          = FMC_Read_Burst_Disable;
  FMC_SDRAMInitStructure.FMC_ReadPipeDelay      = FMC_ReadPipe_Delay_0;
  FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct  = &FMC_SDRAMTimingInitStructure;
  /* FMC SDRAM bank initialization */
  FMC_SDRAMInit(&FMC_SDRAMInitStructure);
  
/* Step 3 --------------------------------------------------------------------*/
  /* Configure a clock configuration enable command */
  FMC_SDRAMCommandStructure.FMC_CommandMode            = FMC_Command_Mode_CLK_Enabled;
  FMC_SDRAMCommandStructure.FMC_CommandTarget          = FMC_Command_Target_bank1;
  FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber      = 1;
  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
  /* Wait until the SDRAM controller is ready */ 
  while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0))
  
    timeout--;
  
  /* Send the command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);  
  
/* Step 4 --------------------------------------------------------------------*/
  /* Insert 100 ms delay */
  Delay以上是关于STM32笔记之 SDRAM的主要内容,如果未能解决你的问题,请参考以下文章

STM32H7教程第49章 STM32H7的FMC总线应用之SDRAM

STM32HAL库 STM32CubeMX教程十五---FMC-SDRAM

STM32HAL库 STM32CubeMX教程十五---FMC-SDRAM

STM32HAL库 STM32CubeMX教程十五---FMC-SDRAM

如何在 SDRAM 中写入 - STM32

SDRAM 与 STM32F429BI 接口存在闪烁问题