Mini2440裸机开发之存储器控制器
一、Mini2440地址空间
1.1 存储器控制器介绍
在之前的文章中,我们已经介绍过S3C2440自带4KB SRAM和2MB的NOR FALSH。片内4KB的内存其实是很小,为了能够运行操作系统和更大的应用程序,需要在处理器存储接口上进行扩展,如SDRAM、SRAM、ROM、FLASH等。ARM提供一个存储管理器部件,为访问外部存储设备提供存储地址信号和控制信号,即存储器控制器。
这里顺带提一下RAM,RAM全名随机存储器,分为以下两种:
- DRAM:存储速度较慢,需要不断刷新,不然数据会丢失。主要包括SDRAM、DDR、DDR2、DDR3、DDR4等。
- SRAM:功率大、成本高、速度快。
1.2 存储器控制器作用
CPU在执行指令时候,CPU只会发出指令从某个地址读数据,此时存储器控制器会根据地址判断是什么设备,并负责数据操作,把CPU想操作的数据反馈给CPU。
比如指令:
COUNT ADDR 0x30000000 MOV R1,=ADDR ;R0=0x30000000 LDR R0,[R1] ;R0=*R1 加载到寄存器
从内存地址0x30000000处读取4个字节的值,写入寄存器R0。存储器控制器根据地址0x30000000知道这是片内SDRAM,然后发出nGCS6片选信号,并且会将地址转换为:
- L-Bank选择信号;
- 行地址;
- 列地址;
访问一个存储芯片,正来说需要具备以下条件:
- 地址线;
- 数据线;
- 时钟频率;
- 芯片相关特性;
1.3 地址空间分布
S3C2440对外引出了27位地址线ADDR0~ADDR26,32位数据线,最多能够寻址128MB。而S3C2440的寻址空间可以达到1GB,这是由于S3C2440将1GB的地址空间分成了8个BANKS(Bank0~Bank7),其中每一个BANK对应一根片选信号线nGCS0~nGCS7,当访问BANKx的时候,nGCSx管脚电平拉低,用来选中外接设备, S3C2440通过8根选信号线和27根地址线,就可以访问1GB。CPU系统上电将从bank0开始执行。
空间分布图如下: Bank0-Bank7的地址范围如下:
- 0x00000000-0x3FFFFFFF:Bank0-Bank7;
- 0x4000 0000-0x47FF FFFF :根据启动方式不同,作用不同;
- 0x4800 0000-0x5FFF FFFF:特殊功能寄存器;
- 0x6000 0000-0xFFFF FFFF;未被使用;
S3C2440是32位CPU,可以使用的地址范围理论达到4GB,除去上面连接外设的1GB空间外,还有一部分是CPU内部寄存器的地址,剩下的地址空间没有使用。
1.4 SDRAM工作原理
我们观察S3C2440内存空间分布图,我们可以发现Bank0~Bank5可以焊接ROM或SRAM类型存储器,Bank6~Bank7可以焊接ROM,SRAM,SDRAM类型存储器,也就是说,S3C2440的SDRAM内存应该焊接在Bank6~Bank7上,最大支持内存256M,Bank0~Bank5通常焊接一些用于引导系统启动小容量ROM,具体焊接什么样存储器,多大容量,根据每个开发板生产商不同而不同。
SDRAM的内部是一个存储阵列。阵列就如同表格一样,将数据“填”进去。在数据读写时和表格的检索原理一样,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理,如图所示。
这个单元格(存储阵列)就叫逻辑 Bank(Logical Bank,下文简称 L-Bank)。 由于技术、成本等原因,不可能只做一个全容量的 L-Bank,而且最重要的是,由于SDRAM的工作原理限制,单一的 L-Bank将会造成非常严重的寻址冲突,大幅降低内存效率。所以人们在 SDRAM内部分割成多个 L-Bank,目前基本都是 4个(这也是SDRAM规范中的最高L-Bank数量),由此可见,在进行寻址时就要先确定是哪个 L-Bank,然后在这个选定的 L-Bank中选择相应的行与列进行寻址。因此对内存的访问,一次只能是一个 L-Bank工作。
当对内存进行操作时,先要确定操作L-Bank,因此要对L-Bank进行选择。在内存芯片的外部管脚上多出了两个管脚BA0, BA1,用来片选4个L-Bank。
如前所述, 32位的地址长度由于其存储结构特点,分成了行地址和列地址。以Mini2440开发板为例:将两片32MB,16位宽SDRAM内存(型号HY57V561620FTP)焊接在Bank6,并联形成64M,32位内存。通过下面的内存原理图可知:
内存外接管脚地址线只有13根地址线A0~A12,它最多只能寻址8M内存空间,到底使用什么机制来实现对64M内存空间进行寻址的呢?
SDRAM的行地址线和列地址线是分时复用的,即地址要分两次送出,两次送到芯片上去的地址分别称为行地址和列地址。它们被锁存到芯片内部的行地址锁存器和列地址锁存器。这样,可以大幅度减少地址线的数目,提高器件的性能和制作工艺复杂度。
- 先送出行地址,行地址使用A0~A12(13条)(nSRAS是行地址锁存信号,该信号将行地址锁存在芯片内部的行地址锁存器中);
- 再送出列地址,列地址使用A0~A8(9条)(nSCAS是列地址锁存信号,该信号将列地址锁存在芯片内部的列地址锁存器中);
实际上,现在的SDRAM一般都以L-Bank为基本寻址对象的。由L-Bank地址线BA1~BA0控制L-Bank间的选择:
- BA0-BA1是L-Bank选择引脚。(L-Bank选择与行地址(row address)同时下发,在列地址下发时,有其他用途)。
13+9+2=24,相当于24个地址,每个地址2个字节,内存大小2^25=32MB。
需要注意的是LADDR2接的引脚是A0,这是因为一个地址对应4个字节,32位数据位宽。
BA0,BA1接ADDR24,ADDR25,为什么用这两根地址线呢?
-
BA0~BA1代表了SDRAM的最高地址位。因为CPU的寻址空间是以字节(byte)为单位的,本系统SDRAM容量为64MB,那就需要A25~A0(64M=2^26)地址线来寻址,所以BA1~BA0地址线应该接到S3C2440的ADDR25~ADDR24引脚上。
左侧DQ0~DQ15接接LDATA0~LDATA15,右侧DQ0~DQ15接接LDATA16~LDATA31,共计数据宽度为32位。
当地址线发出地址位0x33FFFFFC,二进制位0011 0011 1111 1111 1111 1111 1111 1100,存储器控制器会发出nGCS6(与nGCS0同一引脚)片选信号,
nGCS6片选 | ADDR27 | ADDR26 | ADDR25 | ADDR24 | ADDR23 | ADDR22 | ADDR21 | ADDR20 | ADDR19 | ADDR18 | ADDR17 | ADDR16 | |||
0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
BA1 | BA0 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 |
ADDR15 | ADDR14 | ADDR13 | ADDR12 | ADDR11 | ADDR10 | ADDR9 | ADDR8 | ADDR7 | ADDR6 | ADDR5 | ADDR4 | ADDR3 | ADDR2 | ADDR1 | ADDR0 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
A4 | A3 | A2 | A1 | A0 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
所以当行地址A0~A12全部位1,列地址A0~A8全部位1,BA0~BA1可以访问SDRAM最高内存地址,一次取出32位数据,对应0x33FFFFFC~0x33FFFFFF。
SDRAM地址空间:0x30000000~0x33FFFFFF;
1.5 HY57V561620FTP
1.5.1 引脚信息
下面信息来自该款SDRAM datasheet。54个引脚描述:
这里主要提一下DQM,DQM的作用是起到掩膜作用,就是指定输出时候,某一段是不输出的。
CLK是时钟信号,CKE是时钟使能信号。
1.5.2 功能框图
1.5.3 模式寄存器
1) 突发长度
从上图可以看到A0~A2是突发长度的控制,突发长度的控制如上图,有突发长度1,2,4,8,还有全页长度。
突发长度:突发就是连续读写的操作,如果没有突发操作,对SDRAM内部连续地址进行读写则要发送指令和地址,然后读取或写入数据,循环操作,有了突发操作后,就可以指定突发长度,发送读写的起始地址然后对SDRAM进行连续的读写,突发的长度可以设置,突发全页就是连续操作一行,就是512(2^9)个存储单元。
突发读写实现的是连续的读写操作,在结束完一次突发读写前,不需要进行地址的索引,但需要注意的是,这个连续的读写操作是操作的连续,不是地址的连续。
2) 突发类型
A3位用于控制突发的类型,包括顺序读写和交错读写。一般情况下都采用顺序读写操作。
3) 读写方式
A9位用于控制读写方式,包括突发读/写,以及突发读/单一写。
4) CAS延时
CAS潜伏期:即发出读指令之后,经过多少个时钟周期才可以读取数据,而在写数据中是没有这个概念的。这个后面会介绍。
1.5.4 命令真值表
通过对上面指令的总结,简化出要用到的指令如下:
指令 |
常量名 |
CKE |
CSn |
RAS |
CASn |
WEn |
备注 |
空操作 |
NOP |
1 |
0 |
1 |
1 |
1 |
|
行激活 |
ACTIVE |
1 |
0 |
0 |
1 |
1 |
|
读操作 |
READ |
1 |
0 |
1 |
0 |
1 |
|
写操作 |
WRITE |
1 |
0 |
1 |
0 |
0 |
|
预充电 |
PR |
1 |
0 |
0 |
1 |
0 |
|
自刷新 |
AR |
1 |
0 |
0 |
0 |
1 |
|
设置寄存器 |
LMR |
1 |
0 |
0 |
0 |
0 |
|
突发停止 |
BURST_STOP |
1 |
0 |
1 |
1 |
0 |
1 |
二、SDRAM HY57V561620FTP 指令
2.1 行激活(ACTIVE)
Active 用于激活特定存储区中的行以进行后续读取或写入访问。$\overline{RAS}$处于有效状态,BA0、BA1 选择Bank,A0-A12 上提供的行地址。该行保持激活(或打开)以供访问,直到向该L-Bank发出 PRECHARGE 命令。
2.2 读/写命令(READ/WRITE COMMAND)
- 在之前读/写操作之前,必须先执行行激活ACTIVE,以选中L-Bank和行地址。
- 在地址线上送完行地址之后,要等到行地址稳定定位后再送出列地址,这个间隔被定义为 tRCD,即RAS to CAS Delay(RAS至 CAS延迟),tRCD是SDRAM的一个重要时序参数,相关数值参看对应芯片硬件手册。通常tRCD以时钟周期(tCK,Clock Time)数为单位,我们支款芯片,官方给的值tRCD为20ns,如果内存工作在100MHz,那么RCD至少要为2个时钟周期, RCD=2。
读命令:
- READ 命令用于启动对活动行的突发读取。 $\overline{CAS}$处于有效状态,BA0、BA1 选择Bank,A0-A8 上提供的列地址。A10 的值决定是否使用自动预充电。 如果选择自动预充电,被访问的行将在读突发结束时预充电; 如果未选择自动预充电,该行将保持打开以供后续访问。 发出 READ 命令后,有效的数据输出元素将在 CAS 延迟后可用。
写命令:
- WRITE 命令用于启动对活动行的突发写入访问。 $\overline{CAS}$处于有效状态, BA0、BA1 选择Bank,A0-A8 上提供的列地址。A10 的值决定是否使用自动预充电。 如果选择自动预充电,被访问的行将在写突发结束时预充电; 如果未选择自动预充电,该行将保持打开以供后续访问。
2.3 读操作(READ)
读操作的时序图,官方使用手册上的时序是比较简单,因为使用手册给的时序比是以读命令作为输入的。这样我们还需要结合之前介绍的两个时序来看这个时序,比较麻烦。
SDRAM 可以执行突发读操作。突发长度可以设置为 1、2、4 和 8。突发读取的起始地址由读取命令设置周期的列地址和Bank选择地址指定。 在读操作中,数据输出在读命令之后,经过CAS Latency(简称CL)时间才开始。 CAS Latency可以设置为 2 或 3。当突发长度为 1、2、4 和 8 时,DOUT 缓冲器在连续突发长度数据输出后的下一个时钟自动变为高阻态。CAS 延迟和突发长度必须在模式寄存器中指定。上图的突发长度是4,Do0、Do1、Do2、Do3。
下面是一张比较完整的读操作时序图:
- CPU发出片选信号nSCS0(与nGCS6是同一引脚)有效,选中SDRAM芯片;
- 对被选中的芯片进行同一的行/列(存储单元)寻址:
- 根据SDRAM芯片的列地址线数目设置CPU相关寄存器后,CPU会从32位地址中自动分出L-BANK选择信号、行地址信号、列地址信号,然后先后发出行地址信号、列地址信号。
- 确定Bank,通过ADDR24、ADDR25信号线选定,总共4个Bank;
- 确定行地址:A0~A12地址线发送具体的行地址,共有13根地址线(可表示8192行),A0~A12的不同数值就确定了具体的行地址;
- 确定列地址:行地址确定之后,就要对列地址进行寻址了,行地址与列地址线是复用的。列地址复用了A0~A8,共9根(可表示512列);
- 找到存储单元后,被选中的芯片就要进行统一的数据传输;
2.4 写操作(WRITE)
下面是手册原文,这里就不翻译了,图中的BL是突发长度。
Input data appearing on the data bus, is written to the memory array subject to the DM input logic level appearing coincident with the data. If a given DM signal is registered Low, the corresponding data will be written to the memory; if the DM signal is registered High, the corresponding data inputs will be ignored, and a write will not be executed to that byte / column location.
During WRITE bursts, the first valild data-in element will be registered coincident with the WRITE command. Subsequent data elements will be registered on each successive positive clock edge. Upon completion of a fixed-length burst, assuming no other commands have been initiated, the DQ will remain High-Z and any additional input data will be ignored. A full-page burst will continue until terminated.
Data for any WRITE burst may be truncated with a subsequent WRITE command, and data for a fixed-length WRITE burst may be immediately followed by data for a WRITE command. The new WRITE command can be issued on any clock following the previous WRITE command, and the data provided coincident with the new command applies to the new command.
2.5 预充电(PRECHANRGE)
原本逻辑状态为1的电容在读取操作后,会因放电而变为逻辑0。由于SDRAM的寻址具有独占性,所以在进行完读写操作后,如果要对同一L-Bank的另一行进行寻址,就要将原先操作行关闭,重新发送行/列地址。
在对原先操作行进行关闭时,SDRAM为了在关闭当前行时保持数据,要对存储体中原有的信息进行重写,这个充电重写和关闭操作行过程叫做预充电,发送预充电信号时,意味着先执行存储体充电,然后关闭当前L-Bank操作行。预充电中重写的操作与刷新操作一样,只不过预充电不是定期的,而只是在读操作以后执行的。
预充电用于关闭L-Bank中选中的行,在预充电时间(tRP)到达之前,不可以向该L-Bank发出命令。如果对某一L-Bandk进行预充电,需要指定Bank地址,如果要对所有L-Bandk预充电,A10应与预充电命令一起设置位高电平。
2.6 自动刷新和自我刷新 (AUTO REFRESH AND SELF REFRESH)
存储体中电容的数据有效保存期上限是64ms,也就是说每一行刷新的循环周期是64ms。这样刷新速度就是:行数量/64ms 。
我们在看内存规格时,经常会看到4096 Refresh Cycles/64ms 或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个L-Bank的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096行时为15.625 μs,8192行时就为7.8125 μs。
刷新操作分为两种:
- Auto Refresh,简称AR;
- Self Refresh,简称SR;
不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。
对于AR,SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址,或者说CAS在RAS 之前有效。所以,AR又称CBR(CAS Before RAS,列提前于行定位)式刷新。由于刷新涉及到所有L-Bank,因此在刷新过程中,所有L-Bank都停止工作,而每次刷新所占用的时间为9个时钟周期(PC133 标准),之后就可进入正常的工作状态,也就是说在这9个时钟期间内,所有工作指令只能等待而无法执行。64ms之后则再次对同一行进行刷新,如此周而复始进行循环刷新。显然,刷新操作肯定会对SDRAM 的性能造成影响,但这是没办法的事情,也是DRAM 相对于SRAM (静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。
2.7 模式寄存器设置(MODE REGISTER SET)
模式寄存器通过地址位加载。BA0 和 BA1 用于选择模式寄存器。 参见寄存器描述。 MODE REGISTER SET 命令只能在所有L-Bank 空闲且没有突发的情况下发出,并且在满足 tMRD 之前不能发出,并且在满足 tMRD 之前不能发出后续的可执行命令。
三、寄存器
存储控制器一共有13个寄存器,6种寄存器,对BANK0~BANK5进行访问时,只需要配置BWSCON和BANKCONx寄存器,但是对SDRAM访问,不仅仅需要对这俩个寄存器进行配置,还需要额外配置4个寄存器。
3.1 总线宽度和等待控制寄存器(BWSCON)
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
BWSCON | 0x48000000 | R/W | 总线宽度和等待控制寄存器 | 0x000000 |
寄存器位信息:
BWSCON | 位 | 描述 | 初始状态 |
ST7 | [31] |
决定SRAM 是否对Bank 7 使用UB/LB 0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS7 | [30] |
决定Bank 7 的WAIT 状态 0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW7 | [29:28] |
决定Bank 7 的数据总线宽度 00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST6 | [27] |
决定SRAM 是否对Bank 6 使用UB/LB 0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS6 | [26] |
决定Bank 6 的WAIT 状态 0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW6 | [25:24] |
决定Bank 6 的数据总线宽度 00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST5 |
[23] |
决定SRAM 是否对Bank 5 使用UB/LB 0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0
|
WS5 | [22] | 决定Bank 5 的WAIT 状态
0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW5 | [21:20] | 决定Bank 5 的数据总线宽度
00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST4 |
[19] | 决定SRAM 是否对Bank 4 使用UB/LB
0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS4 |
[18] | 决定Bank 4 的WAIT 状态
0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW4 | [17:16] | 决定Bank 4 的数据总线宽度
00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST3 | [15] | 决定SRAM 是否对Bank 3 使用UB/LB
0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS3 | [14] | 决定Bank 3 的WAIT 状态
0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW3 | [13:12] | 决定Bank 3 的数据总线宽度
00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST2 | [11] | 决定SRAM 是否对Bank 2使用UB/LB
0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS2 | [10] | 决定Bank 2 的WAIT 状态
0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW2 | [9:8] | 决定Bank 2 的数据总线宽度
00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
ST1 | [7] | 决定SRAM 是否对Bank 1使用UB/LB
0 = 未使用UB/LB(引脚对应nWBE[3:0]) 1 = 使用UB/LB(引脚对应nBE[3:0]) |
0 |
WS1 | [6] | 决定Bank 1 的WAIT 状态
0 = WAIT 禁止 1 = WAIT 使能 |
0 |
DW1 | [5:4] | 决定Bank 1 的数据总线宽度
00 = 8 位 01 = 16 位 10 = 32 位 11 = 保留 |
0 |
DW0 | [2:1] |
表明Bank 1 的数据总线宽度(只读) 01 = 16位 10 =32位 该状态由OM[1:0]引脚决定 |
- |
保留 | [0] | 保留为0 | 0 |
- ST6:决定是否使用SDRAM的数据掩码,对SDRAM时为0,对SRAM时为1。
- WS6:决定是否使用WAIT信号,一般不使用。(WAIT信号就是在SDRAM没准备好的时候,由SDRAM发给CPU,请求延迟一段时间)
- DW6:决定BANK位宽,自然是32位。
所以,BWSCON寄存器要或运算的值为:0x02000000。如果你参考了其他文章,很多把这个值设置为0x22011110,这也是没问题,因为他设置了其他BANK,但是我们只关心BANK6相关的配置。
3.2 BANK 控制寄存器(BANKCONn:nGCS0 至nGCS5)
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
BANKCON0 | 0x48000004 | R/W | Bank0 控制寄存器 | 0x0700 |
BANKCON1 | 0x48000008 | R/W | Bank1 控制寄存器 | 0x0700 |
BANKCON2 | 0x4800000C | R/W | Bank2 控制寄存器 | 0x0700 |
BANKCON3 | 0x48000010 | R/W | Bank3 控制寄存器 | 0x0700 |
BANKCON4 | 0x48000014 | R/W | Bank4 控制寄存器 | 0x0700 |
BANKCON5 | 0x48000018 | R/W | Bank5 控制寄存器 | 0x0700 |
寄存器位信息:
BANKCONn | 位 | 描述 | 初始状态 |
Tacs | [14:13] |
nGCSn 前的地址建立时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tcos | [12:11] |
nOE 前的片选建立时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tacc | [10:8] |
访问周期 000 = 1 个时钟 001 = 2 个时钟 注意:当使用nWAIT 信号时,Tacc ≥ 4 个时钟 |
111 |
Tcoh | [7:6] |
nOE 后的片选保持时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tcah | [5:4] |
nGCSn 后的地址保持时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tacp | [3:2] |
Page 模式下的Page 模式访问周期 00 = 0 个时钟 01 = 1个时钟 |
00 |
PMC |
[1:0] |
Page 模式配置 00 = 正常(1个数据) 01 = 4个数据 |
00
|
3.3 BANK 控制寄存器(BANKCONn:nGCS6 至nGCS7)
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
BANKCON6 | 0x4800001C | R/W | Bank6 控制寄存器 | 0x18008 |
BANKCON7 | 0x48000020 | R/W | Bank7 控制寄存器 | 0x18008 |
寄存器位信息:
BANKCONn | 位 | 描述 | 初始状态 |
MT | [16:15] |
决定Bank6 和Bank7 的存储器类型 00 = ROM或SRAM 01 = 保留(不要使用) |
11 |
存储器类型 = ROM 或SRAM [MT=00] |
|||
Tacs | [14:13] |
nGCSn 前的地址建立时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tcos | [12:11] |
nOE 前的片选建立时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tacc | [10:8] |
访问周期 000 = 1 个时钟 001 = 2 个时钟 |
111 |
Tcoh | [7:6] |
nOE 后的片选保持时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tcah | [5:4] |
nGCSn 后的地址保持时间 00 = 0 个时钟 01 = 1个时钟 |
00 |
Tacp | [3:2] |
Page 模式下的Page 模式访问周期 00 = 2 个时钟 01 = 3个时钟 |
00 |
PMC | [1:0] |
Page 模式配置 00 = 正常(1个数据) 01 = 4个数据 |
00 |
存储器类型 = SDRAM [MT=11] | |||
Trcd | [3:2] |
RAS 到CAS 的延迟 00 = 2 个时钟 01 = 3 个时钟 10 = 4 个时钟 |
00 |
SCAN |
[1:0] |
列地址数 00 = 8 位 01 = 9 位 10 = 10 位 |
00
|
对于SDRAM的访问,我们需要配置的就是图中蓝色标注地方:
-
MT:决定BANK外接的是SDRAM还是SRAM,SDRAM选择11;
-
Trcd:行列信号之间的延迟时间tRCD,根据芯片手册得知最小为20ns,而我们的HCLK时钟为100MHz,一个clock 10ns,所以保险起见选择01,3个时钟;
- SCAN:设置列地址位,这里使用的SDRAM列地址一共9位,所以为:01;
所以,BANKCON6寄存器要或运算的值为:0x00018005。
3.4 刷新控制寄存器
刷新控制寄存器,用来控制SDRAM的刷新模式和刷新频率。我们知道,SDRAM中的存储阵列需要不断的刷新来保证数据不丢失,所以就要配置刷新控制寄存器。
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
REFRESH | 0x48000024 | R/W | SDRAM 刷新控制寄存器 | 0xAC0000 |
寄存器位信息:
REFRESH | 位 | 描述 | 初始状态 |
REFEN | [23] |
SDRAM 刷新使能 0 = 禁止 1 = 使能(自或CBR/自动刷新) |
1 |
TREFMD | [22] |
SDRAM 刷新模式 在自刷新期间,驱动SDRAM 控制信号为适当电平 0=自动刷新 1=自刷新 |
0 |
Trp | [21:20] |
SDRAM RAS 预充电时间 00 = 2 个时钟 01 = 3 个时钟 |
10 |
Tsrc | [19:18] |
SDRAM 半行周期时间 00 = 4 个时钟 01 = 5 个时钟 |
11 |
保留 | [17:16] |
未使用 |
00 |
保留 | [15:11] | 未使用 | 0000 |
刷新计数器 | [10:0] | SDRAM刷新计数值。 参阅第6 章SDRAM刷新控制器总线优先级部分。
$$refresh_period = (2^11 - refrest_count + 1) / HCLK$$ |
0 |
- REFEN:决定使能刷新功能,当然是开启了,值为:1;
- TREFMD:刷新的模式,一般自动刷新,值为:0;
- Trp:行地址选通预充电时间tRP,根据芯片手册得知20ns,对应2个时钟,值为:00;
- Tsrc:单行刷新时间,设置为0b11即可;
- Refresh Counter:刷新计数的值,查SDRAM的手册可得8192个刷新周期为64ms,则每一个刷新周期为$64*10^6/8192/1000=7.8125μs$,如果内存工作在100MHz下,那么通过公式计算可得:
$$refrest\_count =2^{11}+1-100*7.8125=1269(取大整数)$$
所以,REFRESH寄存器要或运算的值为:0x8C0000 + 1269 = 0x008C04F5(HCLK = 100MHz)
3.5 Bank 大小寄存器
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
BANKSIZE | 0x48000028 | R/W | 可变Bank 大小寄存器 | 0x00 |
寄存器位信息:
BANKSIZE | 位 | 描述 | 初始状态 |
BURST_EN | [7] |
ARM 核突发(Burst)操作使能 0 = 禁止突发操作 1 = 使能突发操作 |
0 |
保留 | [6] |
未使用 |
0 |
SCKE_EN | [5] |
SDRAM 省电模式使能SCKE 控制 0 = 禁止SDRAM省电模式 1 = 使能SDRAM省电模式 |
0 |
SCLK_EN | [4] |
只在SDRAM访问周期期间SCLK使能,以降低功耗。当未访问SDRAM,SCLK变为低电平 0 = SCLK一直有效 1 = SCLK 只在访问期间有效(推荐) |
0 |
保留 | [3] |
未使用 |
0 |
BK76MAP | [2:0] |
Bank6/7 存储器映射 010 = 128MB/128MB 001 = 64MB/64MB 100 = 2M/2M |
010 |
设置内存的突发传输模式,省电模式和内存容量。
- BURST_EN:决定是否允许突发操作,这里我们可以开启突发操作,也可以禁止突发操作;
- SCKE_EN:决定是否使用SCKE信号来决定省电模式,值为:1;
- SCLK_EN:决定SCLK时钟信号的产生模式,值为:1;
- BK76MAP:决定BANK6、BANK7的大小,这里SDRAM是64M,所以值为:001;
所以,开启突发操作操作,BANKSIZE寄存器要或运算的值为:0x000000B1,否则设置为0x00000031。
3.6 模式设置寄存器(MRSR)
通过模式设置寄存器可以设置SDRAM的模式寄存器。
寄存器信息:
寄存器 | 地址 | R/W | 描述 | 复位值 |
MRSRB6 | 0x4800002C | R/W | 模式寄存器组寄存器Bank6 | xxx |
MRSRB7 | 0x48000030 | R/W | 模式寄存器组寄存器Bank7 | xxx |
寄存器位信息:
MRSR | 位 | 描述 | 初始状态 |
保留 | [11:10] |
未使用 |
- |
WBL | [9] |
写突发长度 0 = 突发(固定) 1 = 保留 |
x |
TM | [8:7] |
测试模式 |
xx |
CL | [6:4] |
CAS 等待时间(latency) |
xxx |
BT | [3] |
突发类型 |
x |
BL | [2:0] | 突发长度
000 = 1(固定) 其它 = 保留 |
xxx |
- CL[6:4]:查询芯片手册设置CL=3,即0b011,
- BT、BL:如果开启了突发操作,可以参考HY57V561620FTP 模式寄存器进行设置;
这里我们MRSR6,MRSR7设置为:0x00000030.
注意:
- 当代码在SDRAM 中运行时一定不要改变MRSR寄存器;
- 睡眠模式中,SDRAM 必须使能SDRAM 自刷新模式;
四、SDRAM初始化代码
S3C2240在NAND启动的时候,会把NAND前4KB的复制到片内SRAM,然后重映射到0x00,从该处开始执行。超过4KB的存放到SDRAM中,在访问之前,需要先初始化SDRAM。
4.1 初始化SDRA
/* SDRAM初始化 */ .text .global memory_init #define BWSCON 0x48000000 /* 总线宽度和等待控制寄存器 0x00*/ #define BANKCON0 0x48000004 /* Bank0 控制寄存器 0x0700 */ #define BANKCON1 0x48000008 /* Bank1 控制寄存器 0x0700 */ #define BANKCON2 0x4800000C /* Bank2 控制寄存器 0x0700 */ #define BANKCON3 0x48000010 /* Bank3 控制寄存器 0x0700 */ #define BANKCON4 0x48000014 /* Bank4 控制寄存器 0x0700 */ #define BANKCON5 0x48000018 /* Bank5 控制寄存器 0x0700 */ #define BANKCON6 0x4800001C /* Bank6 控制寄存器 0x0700 */ #define BANKCON7 0x48000020 /* Bank7 控制寄存器 0x0700 */ #define REFRESH 0x48000024 /* SDRAM 刷新控制寄存器 0xAC0000 */ #define BANKSIZE 0x48000028 /* 可变Bank大小寄存器 0x00 */ #define MRSRB6 0x4800002C /* 模式寄存器组寄存器Bank6 */ #define MRSRB7 0x48000030 /* 模式寄存器组寄存器Bank7 */ memory_init: /* 初始化BWSCON */ ldr r0,=BWSCON ldr r1,=0x02000000 str r1,[r0] /* 初始化BANKCON0 */ ldr r1,=0x00000700 str r1,[r0,#0x04] /* 初始化BANKCON1 */ ldr r1,=0x00000700 str r1,[r0,#0x08] /* 初始化BANKCON2 */ ldr r1,=0x00000700 str r1,[r0,#0x0C] /* 初始化BANKCON3 */ ldr r1,=0x00000700 str r1,[r0,#0x10] /* 初始化BANKCON4 */ ldr r1,=0x00000700 str r1,[r0,#0x14] /* 初始化BANKCON5 */ ldr r1,=0x00000700 str r1,[r0,#0x18] /* 初始化BANKCON6 */ ldr r1,=0x00018005 str r1,[r0,#0x1C] /* 初始化BANKCON7 */ ldr r1,=0x00018005 str r1,[r0,#0x20] /* 初始化REFRESH */ ldr r1,=0x008C04F5 str r1,[r0,#0x24] /* 初始化BANKSIZE */ ldr r1,=0x000000B1 str r1,[r0,#0x28] /* 初始化MRSRB6 */ ldr r1,=0x00000030 str r1,[r0,#0x2C] /* 初始化MRSRB7 */ ldr r1,=0x00000030 str r1,[r0,#0x30] mov pc,lr /* bl指令将下一条指令地址复制到了lr,子程序返回 */
4.2 从NAND拷贝代码到SDRAM
当我们的程序大于4kb之后,我们以NAND方式启动运行,我们就需要在这前4KB的代码中实现NAND代码到SDRAM的拷贝。
初始化SDRAM后,就可以拷贝NAND代码到SDRAM中:
void copy_nand_to_sdram(void) { /* 要从lds文件中获得 __code_start, __bss_start 然后从0地址把数据复制到__code_start */ extern int __code_start, __bss_start; volatile u32 *dest = (volatile u32*)&__code_start; volatile u32 *end = (volatile u32*)&__bss_start; volatile u32*src = (volatile u32*)0; u32 len = (u32)(&__bss_start) - (u32)(&__code_start); u32 val; // 读取pc寄存器的值 并通过串口输出 //asm("mov %0,r14":"=r"(val)); //uart_send_num(val); /* nor falsh boot: nor flash address 0x00 */ if (is_boot_from_nor_flash()) { /* 把nor flash的内容全部copy到sdram */ while (dest < end) { *dest++ = *src++; } } else { // 将nand flash内容复制到SDRAM nand_init(); nand_chip.nand_read_data(dest, (u32)src, len); } }
拷贝完之后,我们就可以利用位置相关码跳转到main函数运行:
/* 内存初始化 */
bl memory_init
/* 复制片内SRAM到SDRAM */
@bl copy_to_sdram
/* 初始化栈 */
bl stack_init
/* bss段初始化 */
bl bss_init
/* 复制Nand Flash代码到内存、c代码 依赖于栈 */
bl copy_nand_to_sdram
/* 跳转到main执行 */
ldr pc,=main /* 跳转到SDRAM */
需要注意的是:
(1) 我们要确保ldr pc,=main之前执行的所有指令不能超过4KB的存储范围:
(2) 尤其需要注意有没有使用到rodata、data段的数据,比如我们使用了rodata数据段的数据,如果rodata段数据肯定超过了4KB,并且rodata数据还没有从NAND拷贝到SDRAM,那么去访问这些数据肯定取到的是错误的,比如使用串口去输出一些数据,输出的可能就是乱码,甚至程序会跑飞;
(3) 此外我们在nand小节中,定义了全局变量nand_chip,该变量位于bss段,bss段是未初始化数据,由于已经初始化了SDRAM,程序运行时会初始化bss段数据;
(4) 代码必须都是位置无关码,不然程序会跑飞;
五、代码下载
Young / s3c2440_project【9.sdram】
这个代码已经远超过4kb,并判断是NOR还是NAND启动,如果是NAND启动会将NAND代码拷贝到SDRAM中,并跳转到SDRAM中运行,这个代码只可以下载到NAND,以NAND方式启动运行。
参考文章
[5]SDRAM学习笔记(二)