2440裸机-12-3内存控制器与SDRAM_SDRAM设置

转载:初识SDRAM

1.什么是SDRAM

SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。

2.SDRAM内存芯片的内部结构

2.1.逻辑Bank与芯片位宽:

现在进行深入了解SDRAM的内部结构。这里主要的概念就是逻辑Bank。简单的说,SDRAM的内部是一个存储阵列。因为如果是管道式存储,就很难做的随机访问了。

阵列就如表格一样,将数据"填"进去,你可以把它想象成一张表格。和表格的检索原理一样,先指定一行(Row),再指定一列(Column),我们就可以准确的找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格叫什么呢?它就是逻辑Bank(简称L-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中的存储单元称为Word。

而SDRAM内存芯片一次传输的数据量就是芯片的位宽,那么这个存储单元的容量就是芯片的位宽,但是要主要,这种关系仅对SDRAM有效。

2.2.SDRAM存储原理

L-Bank中的存储单元是基础的存储单位,它的容量是若干bit(对于SDRAM而言,就是芯片的位宽),而每个bit则是存放于一个单独的存储中。这些存储体就是内存中最小的存储单元。例如,位宽为32bit时,一次访问4字节的数据.

你可以用硬盘操作中的簇与扇区的关系来理解内存中的存储形式。扇区是硬盘中的最小存储单元,而每个簇则包含有多个扇区,数据的交换都是一个簇为单位进行(一次传输一个存储单元的数据)。

在这里插入图片描述

2.3.芯片的存储容量:

现在我们应该清除内存芯片的基本组织结构了。那么内存的容量怎么计算呢?显然,内存芯片的容量就是所有L-Bank中的存储单元的容量总和。计算有多少个存储单元和计算表格中单元数量的方法是一样的:

存储单元数量 = 行数 * 列数 * L-Bank的数量

在很多内存产品介绍文档中,都会用MW的方式来表示芯片的容量。M是给芯片中存储单元的总数,单位是兆(应为写M),W代表每个存储单元的容量,也就是SDRAM芯片的位宽(Width),单位是bit。计算出来的芯片容量也是以bit为单位,但用户可以采用除以8的方法换算为字节(Byte)。比如8M8,就是一个8bit位宽芯片,有8M个存储单元,总容量是64Mbit(8MB)。

3.读写SDRAM分析的时序分析

从上面几个章节的内容知道,CPU只负责发出地址给内存控制器,接着内存控制器根据发出的地址范围确定是哪个片选信号,比如SDRAM是接在片选6(nGCS6)上,那他的地址就是从 0x30000000 开始,使用了17位地址线那么寻址范围就是 2^17=128k

那么地址范围从:0x30000000 到 0x30020000

2440芯片手册:

在这里插入图片描述
原理图:
在这里插入图片描述
查看一下SDRAM型号为:EM63A165TS-6G 的数据手册:

这款芯片是一个位宽为16位,容量为32M的芯片。

在这里插入图片描述

内存芯片的容量
现在我们应该清楚内存芯片的基本组织结构了。那么内存的容量怎么计算呢?显然,内存芯片的容量就是所有 L-Bank 中的存储单元的容量总合。计算有多少个存储单元和计算表格中的单元数量的方法一样:

存储单元数量=行数×列数(得到一个 L-Bank 的存储单元数量)×L-Bank的数量

在很多内存产品介绍文档中,都会用 M×W 的方式来表示芯片的容量(或者说是芯片的规格/ 组织结构)。M 是该芯片中存储单元的总数,单位是兆(英文简写 M,精确值是 1048576,而不是 1000000),W 代表每个存储单元的容量,也就是 SDRAM芯片的位宽(Width),单位是 bit 。计算出来的芯片容量也是以 bit为单位,但用户可以采用除以 8 的方法换算为字节(Byte)。比如 8M×8,这是一个 8bit 位宽芯片,有 8M个存储单元,总容量是 64Mbit(8MB)。

在外部通过把两块16bit的芯片拼接起来组成32bit的芯片,容量为64M,原理图如下:

在这里插入图片描述

因此数据在读写的时候CPU依然只是发送一个地址,根据地址发出片选信号,但是内存控制器做的事就很多了,它需要根据你外接的SDRAM进行设置,比如,外接芯片的位宽,容量,等,还需要分出几个逻辑块(L-BANK)以及分行(ROW)列(Colum),定义了这些通信格式之后才能找到相应的地址进行存储。

那么逻辑块、行地址、列地址如何拆分?

设置内存控制器的相关寄存器。

找到2440的芯片数据手册,查看内存控制这一章节

3.1.设置 SWSCON 设置为 : 0x02000000

在这里插入图片描述

其中 ST6这个位表示的含义:0 = Not using UB/LB (The pins are dedicated nWBE[3:0 )
内存是32位宽度的,如果指向修改其中某个字节,但是,我仍然提供四个字节的数据,那如何只操作其中的某一个字节呢?
这时需要用到SDRAM的 nWBE[3:0],设置某个内存单元是否会被写。
在这里插入图片描述
如果只想读某个字节,我还是一次性读32bit(4字节数据),通过通过程序字节挑出想要的字节就行了。不需要屏蔽某个字节。(类似于nor flash)

3.2.设置 BANKCON6

设置BANKCON6 = 0x18001

重要参数:

  • SCAN: 列位数
    在这里插入图片描述
    在这里插入图片描述
  • Trcd参数:
    内存控制器是先发出行地址,再发出列地址,他们发送必须要有时间间隔,而这个参数就是规定行列地址发送的时间延时周期的。

举例:
列寻址信号与读写命令是同时发出的。虽然地址线与行寻址共用,但 CAS(Column Address Strobe,列地址选通脉冲)信号则可以区分开行与列寻址的不同,配合 A0-A9,A11(本例)来确定具体的列地址。
在这里插入图片描述
然而,在发送列读写命令时必须要与行有效命令有一个间隔, 这个间隔被定义为 tRCD,即 RAS to CAS Delay(RAS 至 CAS延迟),大家也可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响应时间 (从一种状态到另一种状态变化的过程)所制定的延迟。tRCD 是 SDRAM的一个重要时序参数,可以通过主板 BIOS经过北桥芯片进行调整,但不能超过厂商的预定范围。广义的 tRCD以时钟周期(tCK,Clock Time)数为单位,比如 tRCD=2 ,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,对于 PC100 SDRAM ,tRCD=2 ,代表 20ns 的延迟,对于 PC133则为 15ns。
然而,在发送列读写命令时必须要与行有效命令有一个间隔, 这个间隔被定义为 tRCD,即 RAS to CAS Delay(RAS 至 CAS延迟),大家也可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响应时间 (从一种状态到另一种状态变化的过程)所制定的延迟。tRCD 是 SDRAM的一个重要时序参数,可以通过主板 BIOS经过北桥芯片进行调整,但不能超过厂商的预定范围。广义的 tRCD以时钟周期(tCK,Clock Time)数为单位,比如 tRCD=2 ,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,对于 PC100 SDRAM ,tRCD=2 ,代表 20ns 的延迟,对于 PC133则为 15ns。
在这里插入图片描述

Trcd的具体数值,需要需要根据芯片数据手册来决定:

芯片手册规定,最大为20ns,而我们的时钟是 100M,那么一个时钟周期 t = 10ns,那么只需要两个时钟周期。

在这里插入图片描述

  • 2440寄存器描述:
    在这里插入图片描述

3.3.REFRESH寄存器

设置寄存器:REFRESH = 0x8404F5

重要概念和参数:

刷新
之所以称为 DRAM ,就是因为它要不断进行刷新 (Refresh)才能保留住数据,因此它是 DRAM最重要的操作。刷新操作与预充电中重写的操作一样, 都是用 S-AMP先读再写。
但为什么有预充电操作还要进行刷新呢?

因为预充电是对一个或所有 L-Bank 中的工作行操作,并且是不定期的,而刷新则是有固定的周期,依次对所有行进行操作,以保留那些久久没经历重写的存储体中的数据。但与所有 L-Bank 预充电不同的是,这里的行是指所有 L-Bank 中地址相同的行,而预充电中各 L-Bank 中的工作行地址并不是一定是相同的。

那么要隔多长时间重复一次刷新呢?
目前公认的标准是, 存储体中电容的数据有效保存期上限是 64ms (毫秒,1/1000 秒),也就是说每一行刷新的循环周期是 64ms 。这样刷新速度就是:行数量/64ms。
例如:
我们在看内存规格时,经常会看到 4096 Refresh Cycles/64ms 或 8192 Refresh Cycles/64ms 的标识,这里的4096与 8192就代表这个芯片中每个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096 行时为 15.625μs(微秒,1/1000 毫秒),8192行时就为 7.8125μs。

刷新操作分为两种:自动刷新(Auto Refresh ,简称 AR)与自刷新(SelfRefresh,简称 SR)。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。对于 AR, SDRAM内部有一个行地址生成器(也称20刷新计数器) 用来自动的依次生成行地址。 由于刷新是针对一行中的所有存储体进行,所以无需列寻址,或者说 CAS在RAS之前有效。所以,AR又称 CBR (CAS BeforeRAS,列提前于行定位)式刷新。由于刷新涉及到所有 L-Bank,因此在刷新过程中,所有 L-Bank 都停止工作, 而每次刷新所占用的时间为 9 个时钟周期(PC133标准),之后就可进入正常的工作状态,也就是说在这 9 个时钟期间内,所有工作指令只能等待而无法执行。64ms 之后则再次对同一行进行刷新,如此周而
复始进行循环刷新。 显然,刷新操作肯定会对 SDRAM的性能造成影响, 但这是没办法的事情,也是 DRAM相对于 SRAM (静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。SR则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是 STR(Suspend to RAM,休眠挂起于内存)。在发出 AR命令时,将 CKE置于无效状态,就进入了 SR模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在 SR期间除了 CKE之外的所有外部信号都是无效的(无需外部提供刷新指令) ,只有重新使 CKE有效才能退出自刷新模式并进入正常操作状态。

  • Refresh Counter:

查看数据手册:

查看芯片手册的刷新周期是为:7.8 us

其实也就是说一共有8192 行(芯片中每个 L-Bank 的行数),必须在64ms之内刷新所有的行,所以刷新周期是:64ms/8192=7.8125us.

在上面知道列数一共有 9 bit,L-BANK 总共是4个,那么存储块的个数是: 2^981924=16777216.

每个存储块的大小是32bit(4字节),那么总容量就是:16777216 * 4 = 67108864 byte = 64M
在这里插入图片描述
根据下面的公式计算 Refresh Counter 的值:

此处HCLK为100M,那么Refresh count = 2 11 + 1 - 100x7.8 = 1269 = 0x4F5

在这里插入图片描述

  • Trp:行预先充电时间
    在这里插入图片描述
  • Trc:Row cycle time 行周期时间
    在这里插入图片描述
    而在数据手册中有 这样条公式:Trc=Tsrc+Trp,因为Trp = 20ns,因此可以设置 Tsrc = 45ns,也就是5个HCLK.
    设置2440的寄存器,如下:

在这里插入图片描述

3.4.BANKSIZE 寄存器

设置 BANKSIZE 寄存器为: 0xb1

在这里插入图片描述

3.5.MRSRB6

**设置MRSRB6 = 0x20**

重要参数:

  • CL:CAS 潜伏期

数据输出(读)
在选定列地址后, 就已经确定了具体的存储单元, 剩下的事情就是数据通过数据 I/O 通道(DQ)输出到内存总线上了。但是在 CAS(列地址)发出之后,仍要经过一定的时间才能有数据输出,从 CAS与读取命令发出到第一笔数据输出的这段时间,被定义为 CL(CAS Latency,CAS 潜伏期)。由于 CL只在读取时出现,所以 CL又被称为读取潜伏期(RL,Read Latency)。CL 的单位与 tRCD一样,为时钟周期数,具体耗时由时钟频率决定。不过,CAS并不是在经过 CL周期之后才送达存储单元。实际上 CAS与 RAS一样是瞬间到达的, 但 CAS的响应时间要更快一些。

为什么呢?

假设芯片位宽为n 个 bit ,列数为 c,那么一个行地址要选通 n×c 个存储体,而一个列地址只需选通 n 个存储体。但存储体中晶体管的反应时间仍会造成数据不可能与 CAS在同一上升沿触发,肯定要延后至少一个时钟周期。由于芯片体积的原因, 存储单元中的电容容量很小, 所以信号要经过放大来保证其有效的识别性,这个放大/ 驱动工作由 S-AMP负责,一个存储体对应一个S-AMP通道。但它要有一个准备时间才能保证信号的发送强度(事前还要进行电压比较以进行逻辑电平的判断) ,因此从数据 I/O 总线上有数据输出之前的一个时钟上升沿开始,数据即已传向 S-AMP ,也就是说此时数据已经被触发,经过一定的驱动时间最终传向数据 I/O 总线进行输出,这段时间我们称之为 tAC(Access14 Time from CLK,时钟触发后的访问时间)。tAC 的单位是 ns,对于不同的频率各有不同的明确规定, 但必须要小于一个时钟周期, 否则会因访问时过长而使效率降低。比如 PC133的时钟周期为 7.5ns,tAC 则是 5.4ns。需要强调的是,每个数据在读取时都有 tAC,包括在连续读取中,只是在进行第一个数据传输的同时就开始了第二个数据的 tAC。
在这里插入图片描述
查看SDRAM芯片手册,查看具体可以设置的数值:
在这里插入图片描述
此处设置为第2个clock,表示CAS发出后,第二个clock,SDRM就会提供数据,这个值是会写入SDRAM芯片的一个寄存器(MR,mode regisiter)中保存起来的。
在这里插入图片描述

备注:本文的部分内容是从《高手进阶,终极内存技术指南——完整 / 进阶版》摘抄。

4.编程读写SDRAM

从第五节可知,设置SDRAM需要设置的寄存器有5个,如下:
1).设置 SWSCON 设置 BWSCON= 0x02000000
2).设置 BANKCON6 设置 BANKCON6 =0x18001
3).设置 REFRESH 设置 REFRESH = 0x8404F5
4).设置 BANKSIZE 设置 BANKSIZE = 0xb1
5).设置 MRSRB6 设置 MRSRB6 = 0x20​​​​​​​

4.1.新建一个C语言文件,sdram_init.c和 sdram_init.h

内容分别如下:

sdram_init.c:

#include "s3c2440_soc.h"
#include "myprintf.h"
 
 
void sdram_init(void)
{
	BWSCON	=0x02000000;
	BANKCON6=0x18001;
	REFRESH =0x8404f5;
	BANKSIZE=0xB1;
	MRSRB6	=0x20;
 
}
 
int sdram_test(void)
{
	//定义一个unsigned char 类型的指针,其地址是SRAM的起始地址
	volatile unsigned char *p =(volatile unsigned char *)0x30000000;
	int i;
	//写sdram,从地址0x3000000 到0x300003E8 写入值2000 到 0的值,间隔为2
	for(i =0;i<256;i++)
		p[i] = 255 -i;
	//读sdram
	for(i=0;i<256;i++)
	{
		//打印出写入的值,如果打印的值和我们写入的值是一致的那么写入成功
		myprintf("p[%d] = %d\n\r",i,p[i]);
		//如果地址0x3000000a的值不为 245,说明写入失败,返回 -1
		if(p[10] != 245)
			return -1;
	}
	//否则写入成功,返回 2 
	return 2;
}

解释:
sram_init函数是对SRAM进行初始化的配置,使2440发出的读写的时序能满足外部接的SDRAM的要求。
sram_test函数是对SRAM读写进行测试:
先定义一个指针变量p,使它指向SRAM的起始地址,接着p所指向的地址开始写256个数据,每个字节存储一个数据,分别是从255到0.
接着以p为其实地址从SDRAM读取256个字节的数据,然后使用myprintf打印出来(这个函数是自己在ARM平台上实现的,具体查看:点我查看
然后直接读取 p[10]的值,看如果这个值不等于254说明刚刚没有写入成功,否则写入成功。写入成功返回2,否则返回0.

sdram_init.h: 对初始化函数和测试函数的声明

#ifndef __SDRAM_INIT_H
#define __SDRAM_INIT_H
 
void sdram_init(void);
int sdram_test(void);
 
#endif

4.2编写主函数main.c

#include "s3c2440_soc.h"
#include "uart.h"
#include "sdram_init.h"
#include "led.h"
#include "myprintf.h"
 
int main()
{
	
	uart0_init();
	sdram_init();
	myprintf(
"Hello world!\r\n");
	while(1)
	{
		if(sdram_test() == 2)
		{
			
			led_test();
		}
		else return 0;
	}
	return 0;
 
 
}

备注:

1.初始化串口,然后初始sdram,接着打印Hello world 的字符串。

2.在循环中,调用sdram_test函数,如果返回值为2说明,sdram读写成功。则执行 led_test函数。这个是依次点灯的函数(详细点我:点我查看),只要sdram读写成功,就会执行这个点灯的程序,否则程序不运行。

4.3编写Makefile文件:

把所有的c文件和汇编文件生成目标文件(.o)然后连接在一起生成 sdram.bin 文件:

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o lib1funcs.o lib1funcs.S
	arm-linux-gcc -c -o myprintf.o myprintf.c
	arm-linux-gcc -c -o sdram_init.o sdram_init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 -Tdata 0xbc0 start.o led.o uart.o sdram_init.o main.o myprintf.o lib1funcs.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin	
	arm-linux-objdump -D sdram.elf > sdram.dis
 
clean:
	rm *.bin *.o *.elf *.dis

4.4.把所有文件上传到Linux系统,进行编译:

使用命令:make
在这里插入图片描述

4.5.把sdram.bin 文件传回window系统使用oflash进行烧录:

注意:此处做的事NOR FLASH 实验,必须烧录到NOR FLASH,并从NOR FLASH 启动。

烧录完成,打开串口,串口有数据打印,如下,共打印出256个数据,数值从255依次递减。可以看到p[10] = 254

那么sdram_test()函数执行就会返回 2
在这里插入图片描述
主函数再收到返回值为 2 ,则执行led_test()函数,可以看到开发版的灯在闪烁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值