驱动例子使用的型号为STM32H750XBH6,SDRAM的型号为W9812G6KH-6I。
SDRAM的引脚主要有以下这些。
主要包含的功能可以分类为逻辑控制线,地址线和数据线。
SDRAM 内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址和列地址,就可以精确地找到目标单元格,这是SDRAM 芯片寻址的基本原理。这样的每个单元格被称为存储单元,而这样的表则被称为存储阵列(Bank),目前设计的 SDRAM 芯片基本上内部都包含有 4 个这样的 Bank,寻址时指定 Bank 号以及行地址,然后再指定列地址即可寻找到目标存储单元。
在寻址时行和列地址都公用一组地址线,行、列地址选通信号线控制当前地址线表示的行地址还是列地址,参考下面的数据手册,使用的芯片的列地址线宽度是12,行地址线宽度是9。
SDRAM的数据线宽度是16bit,但是进行数据传输的时候不一定是按照16位进行传输的,在传输8位数据的时候只有低8位的数据是有效的,因此还需要DQM数据掩码信号来配合使用,每根DQM信号线代表8位数据,高电平表示数据有效,低电平表示数据无效。
操作SDRAM需要一系列命令,如下表。
SDRAM要不断进行刷新(Refresh)才能保留住数据,因此它是 DRAM 最重要的操作刷新操作与预充电中重写的操作本质是一样的。
但因为预充电是对一个或所有 Bank 中的工作行操作,并且不定期,而刷新则是有固定的周期,依次对所有行进行操作,以保证那些久久没被访问的存储单元数据正确。刷新操作分为两种:“自动刷新” (Auto Refresh) 与“自我刷新”(SelfRefresh),发送命令后 CKE 时钟为有效时(低电平),使用自动刷新操作,否则使用自我刷新操作。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。
对于“自动刷新”,SDRAM 内部有一个行地址生成器(也称刷新计数器) 用来自动地依次生成行地址,每收到一次命令刷新一行。在刷新过程中,所有 Bank 都停止工作,而每次刷新所占用的时间为N个时钟周期(视 SDRAM型号而定,通常为 N=9),刷新结束之后才可进入正常的工作状态,也就是说在这N个时钟期间内,所有工作指令只能等待而无法执行。一次次地按行刷新,刷新完所有行后,将再次对第一行重新进行刷新操作,这人对同一行刷新操作的时间间隔,称为 SDRAM的刷新周期,通常为 64ms。显然刷新会对SDRAM的性能造成影响,但这是它的 DRAM 的特性决定的,也是 DRAM 相对于 SRAM取得成本优势的同时所付出的代价。
“自我刷新”则主要用于休眠模式低功耗状态下的数据保存,也就是说即使外部控制器不工作了,SDRAM都能自己确保数据正常。在发出“自我刷新”命令后,将 CKE 置于无效状态(低电平),就进入自我刷新模式,此时不再依靠外部时钟工作,而是根据 SDRAM内部的时钟进行刷新操作。在自我刷新期间除了 CKE 之外的所有外部信号都是无效的,只有重新使 CKE 有效才能退出自我刷新模式并进入正常操作状态。
SDRAM上电之后需要进行初始化才能正常工作,初始化的流程如下:
更详细的关于SDRAM的内容可以去搜索其他资料。
W9812G6KH-6I的数据手册截图
由上图可以得到使用的SDRAM的速度等级是166MHz。最快的驱动SDRAM的时钟不能超过这个数。
SDRAM的驱动需要用到FMC外设,首先需要确定外设的时钟来源,参考STM32H750数据手册和CUBEMX的时钟配置,FMC的时钟来源有这几个,一般就选默认的HCLK3就可以。
现在240M的时钟是输入到FMC外设的时钟,而FMC外设驱动SDRAM的的时钟是240M经过分频之后得到的。必须对这个时钟进行二分频或者三分频之后才能用于驱动SDRAM。
二分频之后是120M,单个时钟的周期是8.333ns。需要记住这个时间。后面需要进行计算。
SDRAM控制器的引脚功能如下:
进行初始化需要配置SDRAM的基本参数
hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
1.使用bank1,这里的bank和SDRAM芯片中的bank不是一个概念,这里的bank是指stm32可以用两个SDRAM,这里使用的是第一个。
2.3.根据上面的数据手册可以知道列地址线宽度是12,行地址线宽度是9
4.SDRAM芯片使用的数据位宽是16位的
5.SDRAM芯片内部有4个bank
6.CAS Latency 可以设置 Latency1,Latency2 和 Latency3,实际测试 Latency3 稳定。
7.关闭写保护
8.控制驱动SDRAM的时钟分频,这里设置的是2分频
9.使能突发读传输
10.此位定义 CAS 延时后延后多少个 SDRAM 时钟周期读取数据,实际测此位可以设置延迟不延迟都行
驱动SDRAM需要获得一些重要的时序参数,
/* SdramTiming */
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 7;
SdramTiming.SelfRefreshTime = 4;
SdramTiming.RowCycleDelay = 7;
SdramTiming.WriteRecoveryTime = 3;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 2;
SdramTiming.LoadToActiveDelay
TMRD参数,TMRD 定义加载模式寄存器的命令与激活命令或刷新命令之间的延迟
这个没在手册中找到,姑且参考别的程序设置为2
SdramTiming.ExitSelfRefreshDelay
TXSR参数
TXSR 定义从发出自刷新命令到发出激活命令之间的延迟。
参考后面表格为72ns,一个时钟周期是8.3ns,所以需要8.6个时钟周期,设置为9个
SdramTiming.SelfRefreshTime
TRAS
TRAS 定义最短的自刷新周期
参考后面的表格,42ns,需要6个时钟周期
SdramTiming.RowCycleDelay
TRC
TRC 定义刷新命令和激活命令之间的延迟。
参考后面的表格,60ns,需要8个时钟周期
SdramTiming.WriteRecoveryTime
TWR
TWR 定义在写命令和预充电命令之间的延迟
根据后面的表格,需要2个时钟周期,芯片的最大时钟周期是166M,因此单个周期的时间是6.02ns,2个时钟周期是12ns,我们的周期是8.3,所以也要设置为2个周期才能满足
SdramTiming.RPDelay
TRP
TRP 定义预充电命令与其它命令之间的延迟。
根据后面的表格,需要15ns,因此设置为2
SdramTiming.RCDDelay
TRCD
TRCD 定义激活命令与读/写命令之间的延迟
根据后面的表格,需要15ns,因此设置为2
SDRAM的时序要求可以从数据手册中获得
配置好FMC之后还需要对SDRAM进行初始化操作。
#define SDRAM_BANK_ADDR ((uint32_t)0xC0000000) // FMC SDRAM 数据基地址
#define FMC_COMMAND_TARGET_BANK FMC_SDRAM_CMD_TARGET_BANK1 // SDRAM 的bank选择
#define SDRAM_TIMEOUT ((uint32_t)0x1000) // 超时判断时间
#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)
/******************************************************************************************************
* 函 数 名: SDRAM_Initialization_Sequence
* 入口参数: hsdram - SDRAM_HandleTypeDef定义的变量,即表示定义的sdram
* Command - 控制指令
* 返 回 值: 无
* 函数功能: SDRAM 参数配置
* 说 明: 配置SDRAM相关时序和控制方式
*******************************************************************************************************/
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command)
{
__IO uint32_t tmpmrd = 0;
/* Configure a clock configuration enable command */
Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; // 开启SDRAM时钟
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
HAL_Delay(1); // 延时等待
/* Configure a PALL (precharge all) command */
Command->CommandMode = FMC_SDRAM_CMD_PALL; // 预充电命令
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
/* Configure a Auto-Refresh command */
Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; // 使用自动刷新
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 8; // 自动刷新次数
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
/* Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE; // 加载模式寄存器命令
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = tmpmrd;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
HAL_SDRAM_ProgramRefreshRate(hsdram, 1855); // 配置刷新率
}
15-20行发送时钟使能命令
21.延时等待,必不可少
24-29发送整个SDRAM预充电命令
32-37发送自动刷新命令
40-51配置SDRAM模式寄存器
53配置SDRAM刷新率
关于刷新频率的数值是这么得到的。目前公认的标准是SDRAM 中电容保存数据的上限是64ms,也就是说每一行刷新的循环周期是64ms。这样刷新速度就是:64ms /行数量。我们在看内存规格时,经常会看到4096 Refresh Gycles/64ms 或 8192 RefreshCycles/64ms的标识,这里的4096与8192就代表这个芯片中每个L-Bank的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096行时为15.625μs,8192行时就为7.8125μs。
刷新计数 =(SDRAM refresh period/Number of rows)*SDRAM时钟速度-20=(64/1000/4096) * 120 000 000 - 20 = 1855
实际上这个数值稍差点,在使用 SDRAM 时,基本都没有影响的。
上面这一套操作下来,SDRAM就算初始化完毕了,可以正常使用了。
首先配置好时钟等基础外设,不再赘述
按照上面的配置的顺序选就行,第一个红框的时钟使能信号是有两组的,看用的是哪组就选哪组。下面的选项就按照配置即可。
hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
/* SdramTiming */
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 7;
SdramTiming.SelfRefreshTime = 4;
SdramTiming.RowCycleDelay = 7;
SdramTiming.WriteRecoveryTime = 3;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 2;
配置SdramTiming.WriteRecoveryTime = 3;时可能cubemx说这个太小了,不让设置。但是在生成工程之后在代码里改了,也没出现问题。
cubemx生成的工程是只有fmc的初始化,没有用命令初始化SDRAM,因此在生成工程之后还应该在fmc初始化之后还要加上SDRAM初始化的代码。
工程文件也上传了,有需要的可以参考一下。
工程文件下载