SDRAM作为内存在嵌入式中必不可少,懂了SDRAM原理,初始化就非常简单,今天手把手教你SDRAM初始化。
我用的SOC:S3C2440,SDRAM:两片EM63A165组合
在开始本文的内容前,如果不了解SDRAM的话,先阅读这篇文章:详解内存SDRAM原理(P-Bank、L-Bank、刷新、预充电等)
嵌入式Linux学习系列全部文章:嵌入式Linux学习—从裸机到应用教程大全
根据SDRAM原理,我们可以总结出:
SDRAM读操作
1. 先发出芯片有效命令(ACTIVE),并锁定相应的L-BANK地址(BA0、BA1给出)和行地址(A0~A12给出)。
2. 芯片激活命令后必须等待大于tRCD(SDRAM的RAS到CAS的延迟指标)时间后,发出读命令。
3. CL(CAS延迟值)个时钟周期后,读出数据依次出现在数据总线上。
4. 在读操作的最后,要向SDRAM发出预充电(PRECHARGE)命令,以关闭已经激活的L-BANK。等待tRP时间(PRECHAREG命令后,相隔tRP时间,才可再次访问该行)后,可以开始下一次的读、写操作。
SDRAM写操作
1.先发出芯片有效命令(ACTIVE),并锁定相应的L-BANK地址(BA0、BA1给出)和行地址(A0~A12给出)。
2. 芯片有效命令发出后必须等待大于tRCD的时间后,发出写命令数据,待写入数据依次送到DQ(数据线)上。
3. 在最后一个数据写入后,延迟tWR(写入/校正时间)时间。发出预充电命令,关闭已经激活的页。等待tRP时间后,可以展开下一次操作。
读写操作都是由S3C2440的内存控制器完成的,但是其中的部分数据需要我们根据使用的SDRAM来设置。
从上面的读写总结来看,我们需要至少需要设置一下内容:
1. tRCD(SDRAM的RAS到CAS的延迟)
2. Trp:行地址选用预充电时间
3.CL(CAS延迟值)
4.tRP(预充电到RAS的延迟)
当然,肯定还有别的要设置,但以上是重点。
STx:一次传输数据为32位,假如需要的数据小于32位,则需要掩码屏蔽功能,0时对写屏蔽,1时读写均屏蔽,而SDRAM读时,有内部屏蔽功能,所以只需要写屏蔽,设置为0
WSx:是否使用存储器的WAIT信号,通常设为0,(可以通过外部的“wait”信号延长总线的访问周期,也就是内存芯片向CPU发的,这里没有用到)
DWx:设置焊接存储器芯片的位宽,我的开发板使用两片容量为32M,位宽为16的SDRAM组成64M,32位存储器,因此DW7,DW6位设置为0b10,其它BANK不用设置采用默认值即可。
BANK0对应的是系统引导BANK,这4位比较特殊,它的设置是由硬件跳线决定的,因此不用设置
BWSCON设置结果:0x22000000
MT:设置BANK6的存储器类型,内存为SDRAM,设置为0b11,对应的应该设置Trcd和SCAN位,其它位和SDRAM无关
Trcd:RAS to CAS Delay行地址选通到列地址选通延迟,由内存芯片手册可知其Trcd为最少20ns,如果内存工作在100MHz,则该值至少要为2个时钟周期,因此设置为0b00
SCAN:SDRAM Column Address Number SDRAM的列地址数,我的内存芯片列地址数为9,设置为0b01
BANKCON6设置结果为:0x18001
SDRAM的刷新有效,刷新频率设置寄存器(刷新)
REFEN:开启/关闭刷新功能,设置为1,开启刷新
TREFMD:SDRAM刷新模式,0=CBR/AutoRefresh, 1=Self Refresh,设置为0,自动刷新
Trp:行地址选通预充电时间,查内存芯片手册,发现最小值为18ns,HCLK=100M,周期10ns,因此设置为00就行。
Tsrc:单行刷新时间,查内存芯片手册,Trc最小为60ns/63ns,Trc=Tsrc+Trp,Tsrc设置为0b01即可。
Refresh Counter:内存存储单元刷新数,它通过下面公式计算出:
Refresh Counter = 2^11 + 1 – SDRAM时钟频率(MHz)* SDRAM刷新周期(uS)
SDRAM的刷新周期,也就是内存存储单元间隔需要多久进行一次刷新,前面内存工作原理分析可知电容数据保存上限为64ms,笔者使用内存芯片每个L-Bank共有8192行,因此每次刷新最大间隔为:64ms/8192 = 7.8125uS,如果内存工作在外部晶振频率12MHz下,Refresh Counter = 1955,如果内存工作在100MHz下,那么Refresh Counter = 1269(取大整数)
REFRESH寄存器设置为:
0x840000 + 1269 = 0x008404f5(HCLK = 100MHz)
设置内存的突发传输模式,省电模式和内存容量。
BURST_EN:是否开启突发模式, 0 = ARM内核禁止突发传输 1 = 开启突发传输,设置为1,开启突发传输
SCKE_EN:是否使用SCKE信号作为省电模式控制信号, 0 = 不使用SCKE信号作为省电模式控制信号 1 = 使用SCKE信号作为省电模式控制信号,通常设置为1
SCLK_EN: 设置向存储器输入工作频率,0 = 一直输入SCLK频率,即使没有内存操作也会输入, 1 = 仅当进行内存数据操作时才输入SCLK频率,通常设置为1
BK76MAP:设置Bank6/7的内存容量,笔者使用开发板内存为两片32M内存芯片并联成64M,它们全部都外接到Bank6上,因此选择0b001
BANKSIZE寄存器设置为:0xb1
该寄存器用于设置CAS潜伏周期,可以手动设置的位只有CL[6:4]位,内存芯片手册说可以设置为2或3,CL=2,即0b010
MRSRB6设置为:0x00000020
makefile和启动代码就不给了,还不会的朋友,可以看看我之前的文章
- #include <stdio.h>
- #define BWSCON (*(volatile unsigned int *)0x48000000)
- #define BANKCON6 (*(volatile unsigned int *)0x4800001C)
- #define REFRESH (*(volatile unsigned int *)0x48000024)
- #define BANKSIZE (*(volatile unsigned int *)0x48000028)
- #define MRSRB6 (*(volatile unsigned int *)0x4800002C)
-
- #define GPFCON (*(volatile unsigned int *)0x56000050)
- #define GPFDAT (*(volatile unsigned int *)0x56000054)
- int sdram_test(void)
- {
- volatile unsigned char *p = (volatile unsigned char *)0x30000000;
- int i;
-
- // write sdram
- for (i = 0; i < 1000; i++)
- p[i] = 0x55;
-
- // read sdram
- for (i = 0; i < 1000; i++)
- if (p[i] != 0x55)
- return -1;
-
- return 0;
- }
- int SDRAM_init(void)
- {
- BWSCON = 0x22000000;
- BANKCON6 = 0x18001;
- REFRESH = 0x008404f5;
- BANKSIZE = 0xb1;
- MRSRB6 = 0x00000020;
-
- }
- int main(void)
- {
- SDRAM_init();
-
- if(sdram_test()==0)
- {
- GPFCON = 0x00000100;
- GPFDAT = 0;
- }
-
- return 0;
- }
代码功能:初始化SDRAM,进行SDRAM测试,就是向SDRAM地址写入数据,再读出,测试成功LED灯就亮。
烧到板子上,发现灯亮了,再把初始化SDRAM_init();这一句给注释了,再烧到板子上,发现led灯不亮,
SDRAM实验成功。