STM32CubeMX系列教程21:SDRAM
在看下面教程之前,如果你之前没有使用过SDRAM,建议先看以下文档,以对SDRAM的原理和控制有一定的了解。
一、SDRAM简介
SDRAM(Synchronous Dynamic Random Access Memory)同步动态随机存取存储器·同步是指存储器工作需要同步时钟,内部命令的发送与数据传输都以它为基准·动态是指存储阵列需要不断的刷新来保证数据不丢失·随机存取是指存储器的内容可以以任意顺序访问,而不管前一次访问的是哪一个位置开发板使用的SDRAM型号是IC42S16400J-7TL或IS42S16400J-7TL(两个型号仅产地不同,性能相同),它是一颗8M字节(1 Meg Bits × 16Bits × 4 Banks = 67108864 bits = 64-Mbit)内存芯片。顺便说下:“-7TL”中,7表示速度等级,T表示封装 TSOPII,L表示无铅(符合RoHS标准),属于商用系列,工作温度:0℃ ~ 70℃。如下为芯片数据手册:
SDRAM的内部是一个存储阵列,阵列就如同表格一样,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格就是逻辑Bank(Logical Bank,下文简称L-Bank)。SDRAM内部分割成多个L-Bank。IC42S16400J分为四个Bank。
二、SDRAM硬件简介
上图是SDRAM内部结构图,引脚(Pin)所对应的功能简单翻译如下:
A0~A11:时分复用地址总线(发送地址时,先行后列,12行,8列)DQ0~DQ15:双向数据总线BA0/BA1:Bank地址(两条总线,可以选通4个Bank)CS#:片选信号(低电平有效)WE#:写使能信号(低电平有效)RAS#:行地址信号(低电平有效)CAS#:列地址信号(低电平有效)CLK:同步时钟CKE:时钟使能信号UDQM/LDQM:数据掩码VDD/VDDQ:工作电压/DQ电压GND/GNDQ:相应电压接地SDRAM与Open746I-C开发板连接如下图:
对于SDRAM来说,有三个参数对其性能影响至关重要,它们是tRCD、CL和tRP,对于这三个参数的详细描述可以查看“高手进阶,终极内存技术指南——完整.doc”。下面是对三个参数的一些简要摘录(顺便加多了一个tWR):
tRCD:
在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟),可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响
应时间(从一种状态到另一种状态变化的过程)所制定的延迟。广义的tRCD以时钟周期(tCK,Clock Time)数为单位,比如tRCD=2,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,对于IS42S16400J-7TL,tRCD为15ns。
CL(CAS Latency):在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时由时钟频率决定。
数据写入的操作也是在tRCD之后进行,但此时没有了CL(CL只出现在读取操作中)。对于IS42S16400J-7TL,CL可取2或3个周期。tRP:在发出预充电命令之后,要经过一段时间才能允许发送RAS行有效命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期)。和tRCD、CL一样,tRP的单位也是时钟周期数,具体值视时钟频率而定。对于IS42S16400J-7TL,tRP为15ns。
tWR:数据并不是即时地写入存储电容,因为选通三极管(就如读取时一样)与电容的充电必须要有一段时间,所以数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR,WriteRecovery Time),这个操作也被称作写回(Write Back)。对于IS42S16400J-7TL,tWR为2个周期。
三、FMC之SDRAM控制器简介
这里贴一下SDRAM控制器的特性,详细可查看数据手册:
操作SDRAM,需要先知道它的地址映射区域。从下图可知,SDRAM在STM32内部是连续的地址空间,我们使用的是区域2,所以地址应该在0xD0000000 ~ 0xDFFFFFFF之间。
由于我们的SDRAM是4Bank,12行,8列,由下图知,对于32位的地址空间,其23~31位的地址是固定了。为0xD0000000。0~22位的地址是实际的SDRAM可变地址,即地址映射为:0xD0000000 ~ 0xD07FFFFF
控制SDRAM涉及到以下几个寄存器,之后在对SDRAM配置时,使用到的位会有简单的介绍,详细描述请查看数据手册。SDRAM控制寄存器1,2(FMC_SDCR1,FMC_SDCR2)SDRAM时序寄存器1,2(FMC_SDTR1,FMC_SDTR2)SDRAM命令模式寄存器(FMC_SDCMR)SDRAM刷新定时器寄存器(FMC_SDRTR)总的SDRAM初始化步骤如下,读者可以在对SDRAM配置完成后,返回对照下面的步骤在看一下相应的代码,相信会有一定的收获。
前面对SDRAM和STM32的SDRAM控制器有了一些简单的介绍,对于使用cube库的用户来说,基本是足够使用了。由于有SDRAM控制器,我们只需要简单的对控制器相应位进行配置后,就可以像操作内部的SRAM一样去操作SDRAM。
四、stm32CubeMX配置与说明 复制串口printf的工程,修改文件夹名。击STM32F746I.ioc打开STM32cubeMX的工程文件重新配置,选择SDRAM 1,配置为4 banks,地址线12 bits,数据线 16 bits。
FMC之SDRAM引脚映射配置如下(注意PH5是FMC_SDNWE):
FMC配置如下
下面详细说明,各个选项的配置:1.Bank 由硬件连接决定需要选择SDRAM bank 22.Column bit number表示列数,8位3.Row bit number表示行数,12位4.CAS latency表示CAS潜伏期,即上面说的CL,该配置需要与之后的SDRAM模式寄存器的配置相同,这里先配置为2 memory clock cycles(对于SDRAM时钟超过133MHz的,则需要配置为3 memory clock cycles)5.Write protection 表示写保护,一般配置为Disabled6.SDRAM common clock为SDRAM 时钟配置,可选HCLK的2分频\3分频\不使能SDCLK时钟前面主频配置为216MHz,SDRAM common clock设置为2分频,那SDCLK时钟为108MHz,每个时钟周期为9.25ns
7.SDRAM common burst read 表示突发读,这里选择使能8.SDRAM common read pipe delay 表示CAS潜伏期后延迟多少个时钟在进行读数据,这里选择0 HCLK clock cycle
前面这8项主要是对SDRAM控制寄存器1,2(FMC_SDCR1,FMC_SDCR2)相关位进行的配置
接下来的7项是对SDRAM时序寄存器1,2(FMC_SDTR1,FMC_SDTR2)相关位的配置9.Load mode register to active delay : 加载模式寄存器命令和激活或刷新命令之间的延迟,按存储器时钟周期计
10.Exit self-refresh delay : 从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为70ns,由于我们每个时钟周期为9.25ns,所以设为8 (70÷9.25,向上取整)
11.Self refresh time : 最短的自刷新周期,按存储器时钟周期数计查数据手册知道其最小值为42ns,最大值为100000ns,由于我们每个时钟周期为9.25ns,所以设为5 (40÷9.25,向上取整)
12.SDRAM common row cycle delay : 刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟, 以存储器时钟周期数表示查数据手册知道其最小值为63ns,由于我们每个时钟周期为9.25ns,所以设为7 (63÷9.25,向上取整)
13.Write recovery time : 写命令和预充电命令之间的延迟,按存储器时钟周期数计
14.SDRAM common row precharge delay : 预充电命令与其它命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以设为2 (15÷9.25,向上取整)
15.Row to column delay : 激活命令与读/写命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以这里本应该设为2 (15÷9.25,向上取整)但要注意,时序必须满足以下式子:TWR ≥ TRAS - TRCDTWR ≥ TRC - TRCD - TRP其中:TWR = Write recovery time = 2TRAS = Self refresh time = 5TRC = SDRAM common row cycle delay = 7TRP = SDRAM common row precharge delay = 2TRCD = Row to column delay
所以这里Row to column delay应该取3
生成报告以及代码,编译程序。在fmc.c文件中可以看到初始化函数。在stm32f7xx_hal_sdram.h头文件中可以看到sdram的操作函数。
五、应用程序编写
下载这个应用文件解压并添加到工程中:
在main.c中包含头文件"stm32746g_sdram.h"
/* USER CODE BEGIN Includes */ #include "stm32746g_sdram.h" /* USER CODE END Includes */
添加变量,aRxBuffer,aTxbuffer为读写缓存,我uwWriteReadStatus存储读写状态。
/* USER CODE BEGIN PFP */ /* Private function prototypes -----------------------------------------------*/ #define BUFFER_SIZE ((uint32_t)0x0100) #define WRITE_READ_ADDR ((uint32_t)0x0800) /* Read/Write Buffers */ uint32_t aTxBuffer[BUFFER_SIZE]; uint32_t aRxBuffer[BUFFER_SIZE]; /* Status variables */ __IO uint32_t uwWriteReadStatus = 0; int i; /* USER CODE END PFP */
在main函数中添加以下测试代码:
/* USER CODE BEGIN 2 */ BSP_SDRAM_Initialization_sequence(&hsdram1, REFRESH_COUNT); /*##-2- SDRAM memory read/write access #####################################*/ /* Fill the buffer to write */ for(i=0; i<BUFFER_SIZE; i++) { aTxBuffer[i]=0xC178A562+i; /* TxBuffer init */ } /* Write data to the SDRAM memory */ BSP_SDRAM_WriteData(&hsdram1, SDRAM_DEVICE_ADDR+WRITE_READ_ADDR,aTxBuffer, BUFFER_SIZE); printf("\r\n/* Write data to the SDRAM memory */\r\n\r\n"); for(i=0;i< BUFFER_SIZE;i++) { printf("%02X:0x%08X ",i,aTxBuffer[i]); } printf("\r\n"); /* Read back data from the SDRAM memory */ BSP_SDRAM_ReadData(&hsdram1, SDRAM_DEVICE_ADDR+WRITE_READ_ADDR, aRxBuffer, BUFFER_SIZE); printf("\r\n/* Read back data from the SDRAM memory */\r\n\r\n"); for(i=0;i< BUFFER_SIZE;i++) { printf("%02X:0x%08X ",i,aRxBuffer[i]); } printf("\r\n"); /*##-3- Checking data integrity ############################################*/ for (i = 0; (i < BUFFER_SIZE); i++) { if (aRxBuffer[i] != aTxBuffer[i]) uwWriteReadStatus++; } if(uwWriteReadStatus == 0 ) /* check date */ printf("\r\n SDRAM Test OK\r\n"); else printf("\r\n SDRAM Test False\r\n"); /* USER CODE END 2 */
这里详细说一下BSP_SDRAM_Initialization_sequence函数,主要是实现上SDRAM初始化的步骤3~8
void BSP_SDRAM_Initialization_sequence(uint32_t RefreshCount) { __IO uint32_t tmpmrd = 0; /* 时钟配置使能,对应STM32初始化SDRAM步骤3 */ Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0; /* Send the command */ HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT); /* 等待指定延迟周期,对应STM32初始化SDRAM步骤4 */ /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */ HAL_Delay(1); /* PALL(“预充电所有存储区域”)命令,对应STM32初始化SDRAM步骤5 */ Command.CommandMode = FMC_SDRAM_CMD_PALL; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0; /* Send the command */ HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT); /* 自刷新命令,8个自刷新周期,对应STM32初始化SDRAM步骤6 */ Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 8; Command.ModeRegisterDefinition = 0; /* Send the command */ HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT); /* 配置SDRAM模式寄存器,对应STM32初始化SDRAM步骤7 */ /* 突发长度:1 突发传输方式:顺序 CAS潜伏期:2 操作模式:标准 操作模式:突发读/单一写 */ tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\ SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\ SDRAM_MODEREG_CAS_LATENCY_2 |\ SDRAM_MODEREG_OPERATING_MODE_STANDARD |\ SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = tmpmrd; /* Send the command */ HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT); /* 刷新率设置,对应STM32初始化SDRAM步骤8 */ HAL_SDRAM_ProgramRefreshRate(&sdramHandle, RefreshCount); }
步骤7对应于设置下图中的各个位,以配置SDRAM模式寄存器
步骤8刷新率设置由于我们使用的SDRAM芯片是4096行,所以这里的刷新率是64ms ÷(4096行) = 15.7us SDRAM使用108MHz,刷新周期为:15.7us × 108MHz = 1695.6COUNT = 1695.6 - 20 = 1675