JZ2440裸板开发练习#5 内存控制器 & NorFlash & SDRAM

内存控制器

一个存储芯片,如果要访问它,需要地址线,数据线,片选线,写使能,读使能等控制线,当然可以自己用 IO模拟时序进行控制,但是过于麻烦,现在的S3C2440存在内存控制器可以帮我们搞定控制的逻辑,而我们只需要根据使用的存储器性能,对比时序图设置好内存控制器的相关寄存器即可。

查看S3C2440手册,可以看到有关特性如图,包含但不限于:

1.大小端可软件设置 

2.8个bank每个128MB共1GB即可以控制的内存最大可以到1GB(如果全部用上的话)

3.除了bank0外,访问位数可编程设为8/16/32bit,bank0也可以设置为16/32

4.8个bank中,6个可用于ROM、SRAM等,2个可用于ROM、SRAM、SDRAM等

5.8个bank有7个固定的起始地址,可以作为偏移进行访问

6.一个可灵活调整起始地址和大小的bank,指bank7

7.支持自刷新和低功耗模式用于SDRAM。

从内存映射框图可以看出,S3C2440存在NandFlash和NorFlash两种启动方式。当以NorFlash方式启动时,2440寄存器的初始值足够慢使得norflash可以直接驱动,此时norflash映射为bank0,而内置SRAM则映射到bank7后。当以NandFlash方式启动时,2440将会将NandFlash的前4KB内存的内容复制到内置SRAM中,且此时SRAM映射为起始地址,此时nGCS0不可见,即NorFlash不可用。具体由哪种方式启动由引脚OM0,OM1决定,且应该在第一次访问内存前确定好。

 结合JZ2440的原理图看,OM1已经被接到GND地电平0,另一端由一个开关控制接地活着3.3v电源,从而决定是NandFlash启动,还是16bit NorFlash方式启动。

顺便也可以看到原理图上的norflash,用到型号为MX29LV160DBTI的norflash,可以看到图中所用到的引脚包含:电源引脚,地址线,数据线,读使能,写使能,片选(nXXX表示低有效),从数据线可以看出该芯片为16位芯片,一次提供16位宽数据 ,使用到20根数据线,则为2^20=1M*16bit=16Mbits大小。但是很奇怪的是地址线A1接到了A0,而非A0对应A0的接法。这点在S3C2440芯片手册中有例子:

这里我的理解为,S3C2440的地址线设计为访问8位数据宽度的芯片的地址寻址,当使用8位宽的flash时,A0对接A0,寻址的每一个地址都会对准。而当使用16位宽的flash时,A1与A0对接,即把地址/2,也可理解为二进一,举个例子,当需要寻址地址为0,A0为0,A1为0,此时访问的是地址0的数据,读出16位数据,当地址为1时,A0为1,A1为0,此时仍然为读出地址0数据,而当地址到达2整数倍,A0为0,A1为1,此时读出第二个16位数据。当然A0并非无用,A0可以用来区分需要的是16位数据中的高8位还是低8位。32位的Flash以此类推,A2接A0,用A1和A0来确定需要用到读取到的32位数据中的哪个8位数据,当然这里取值位宽取决于我们的指令用到的取指令宽度,如果LDR,则直接取32位,如果LDRB,则取其中的8位数据。

看过芯片手册的会知道,寄存器设置中有设置我们使用的是多少位的芯片,我觉得这个多少位是用来设置接收缓存用的,并且也根据这个来使用上述的A0或者A1来取出期望位宽的数据。

内存控制器除了上述分拣数据功能外,还有处理硬件信号逻辑的功能,即片选、写使能和读使能等控制信号,我们只需要根据使用的芯片的性能设置好各个信号间的时间间隔,内存控制器会在我们上层逻辑使用到的时候发出对应的控制信号,从而减轻开发负担,具体的时间需要参考时序图和芯片手册,到NorFlash和SDRAM时对比讲。另外,内存控制器8个Bank映射的地址与寄存器的地址统一编址,具体可以查看芯片手册,设置好相关寄存器后,直接往内存控制器映射地址读写即可。

 NorFlash

本节以JZ2440使用到的MX29LV160DBTI介绍NorFlash的参数和时序图以及控制需要知道的那些事。

在芯片手册feature页可以看到芯片最关键的参数已经给出,大小16Mbits,可以根据需要设置为8位(2M存储单元)或者16位(1M存储单元),3V电源供电。

 往下翻,可以看到芯片引脚逻辑符号表,表中表示,该NorFlash有地址线,输入输出用的数据线,片选、写使能、word/byte输入选择、复位、读使能、忙状态输出、电源线和硬件写保护和加速引脚。其中加速引脚存在内部上拉,如果不需要使用需要悬空或者接高电平使其处于无效状态。

 Q15/A-1一开始没有领会到什么意思,参考后文可以知道该A-1是A 负1,即AM-A0后再加一个A-1地址位。芯片位宽为16bits时,存储单元1M,可以用A0-A19共20根地址线完成所有存储单元的寻址,而位宽为8bits时,需要寻址的存储单元增加一倍,需要多一个地址线,此时,空闲数据线可以当做地址线来复用,从而由表中的描述。

 再往下是芯片的框图,与我们控制直接相关的是控制引脚相连的逻辑控制输入块、地址锁存和缓存块、数据IO缓存。逻辑控制模块会将外界的控制信号传递给内部的其他模块,如写状态机等。地址块输入地址将会被拆分输出到X、Y解码,X解码出word行地址,Y解码出bit地址,从而寻址到flash阵列某位地址处,这里有点像程序中数组的array[x][y]二级数组,即将我们的地址解码为芯片内部可识别的地址。数据通过Y-pass门电路,通过放大器达到合适电压输出到数据IO缓存中。我们知道NorFlash不能像常规一样直接写,需要特殊的命令才能进行写操作,而但我们发出WE控制信号时,IO数据将会被锁存到数据命令锁存器(command data latch)中,随后同样地进行解码,转化为芯片的“内部语言”,输出到状态寄存器中,而状态寄存器驱动着状态机进行相应状态下的命令操作,如我们需要的写数据,可以看出来状态机果然通用且好用。Program/ERASE high Voltage这个模块看手册的描述我理解为给对应模块提供控制逻辑高电平的器件(可能理解有误),从而满足相关器件充放电来实现逻辑清除或者置位(bit 0/1)功能的电势。

 

 下表列出了命令和相应的操作以及各引脚的状态,对应上框图中的控制单元,这里其实可以用普通引脚GPIO模拟命令达到控制的目的,但是现在我们有了内存控制器,它会帮我们处理好这些。

同时关注下面这个图,表示某些指令需要在固定时间周期内输入的地址和数据,从而让芯片内部的状态机能够运转到我们期望的命令上。 这一层属于逻辑实现层,前置需要处于写使能状态由内存控制器保证,剩下的就需要程序来实现对应的功能了。

需要 重点 关注的是时序图上相关时间的解释,后面会根据此表来查看时序图。

 norflash芯片手册上的时序图很多,这里我们可以参考S3C2440芯片手册使用到的原理图,提取出NorFlash对应的时序图来简化工作。

首先找到S3c2440上,与NorFlash相关的就只有读和写两个时序图,首先是 

同时找到NorFlash中的读时序图 :

 对照上文中的时间表描述可以知道其中出现的时间意义为:

Tce:片选信号CE发出多久后数据有效

Tsrw:读和写状态转换之间的时间间隔

Toeh:输出使能保持位,即需要写使能WE无效后经过Toeh时间才可以使能OE

Toe:OE使能后经过Toe时间数据才有效

Taa:地址信号发出后经过Taa时间数据才有效

Trc:读周期时间,在此时间内完成读操作

Toh:在OE和CE以及地址无效后数据保持的时间

Tdf:在OE和CE无效后输出数据浮空的时间,即此段时间内数据具有不确定性

而S3C2440中,Tacs为地址信号需要在片选使能前多长时间发出,这里NorFlash并没有要求,因此设置为0;Tcos为CE使能需要在OE使能前多长时间发出,这里也没有要求,设置为0,即S3C2440将同时发出地址信号,片选信号CE,输出使能信号OE。Tacc是S3C2440输出控制信号后多长时间后数据能够有效,与NorFlash中的Taa,Tce,Toe有关,由于我们同时发出三个信号,因此选择最长Taa的即可,由于是实验,我们选择最长的70ns,即输出三个控制信号后70ns能够读到数据。Tacp在NorFlash中没有标出,因此设为最小值即可。Tach和Tcoh又是表示三个控制信号依次无效的时间间隔,这里也不需要,设置为0。需要关注的是NorFlash在数据输出后有一段时间浮空,芯片手册给出的是30ns,这里我们取巧了一下,因为下一次读总是需要在输出三个控制信号70ns后才能读,因此不需要关注该浮空时间。写到这里发现读的时序图涉及到的时间也是一样,因此类比解读即可,此处不做展开。

综上,可以开始设置S3C2440寄存器了,涉及到的是BWSCON,BANKCON0,因为使用到的是bank0,且为NorFlash,因此其它与SDRAM相关的可以不用设置。

第一个BWSCON用来设置位宽,这里bank0作为boot区,开机时已经由OM0和OM1确定为16bits,因此此处寄存器为只读,所以不需要设置。

 其次是BANKCON0,也就是设置时间相关的,这里同样只需要设置与NorFlash相关的即可。这里的时间全部用时间周期来表示,而内存控制器查看S3C2440时钟框图可以知道是挂在HCLK总线上的,而前面的练习中,HCLK被设置为100MHz,因此此处一个clock为10ns,所以Tacs和Tcos设为0,Tacs则设置为8clock(很保守的选项,其实可以更低,因为芯片手册并没有最低值,只要不为0,应该都可以,可以进行测试)。

输出代码如下:

s3c2440.h
-----------------------------
#ifndef __S3C2440_H
#define __S3C2440_H

#include <stdint.h>

.....

//memory controller 
#define BWSCON (*((volatile uint32_t*)0x48000000))

#define BANKCON0 (*((volatile uint32_t*)0x48000004))

....


void HardwareInitAll(void);
void Delay(uint32_t time);
void MemoryControllerInit(uint32_t val);


#endif

s3c2440.c
----------------------------

......

void MemoryControllerInit(uint32_t val)
{
	BANKCON0 = (val<<8);        //此处不能像之前那样清零控制位后再设置,置位0时NorFlash读取异常,将死机
}

void HardwareInitAll(void)
{
	WatchDogDisable();
    /*调试过程发现norFlash启动时,ClockDevideConfig必须在MPLLConfig前设置
    *应该与MPLL设置时的locktime相关,在norflash启动时速度较慢,分频来不及在
    *locktime结束前设置,而nandFlash则足够时间*/
	ClockDevideConfig();    
	ChangeModeToAsynchronous();
	MPLLConfig();
	MemoryControllerInit(7);  //8 clock
}

#include <stdint.h>

#include "s3c2440.h"
#include "led.h"
#include "uart.h"

int main()
{
	HardwareInitAll();
	LedInitAll();
	UartInit();
	uint8_t led_now=kLed1;
	uint8_t i=0;
	
	uint8_t tmp;

	while(1)
	{
		tmp = getc();
		putc(tmp);
                //实验证明到3的时候已经停止运行了,推测因为30ns浮空时间的限制
		if(tmp <= '7' && tmp >= '0'){ tmp -='0'; }
		MemoryControllerInit(tmp);
		
		i=6;	
		while(i--)
		{
			SingleLedOFF(led_now++);
			if(led_now > kLed3) { led_now =kLed1; }
			SingleLedON(led_now);
			Delay(100000);
		}
	}
}

SDRAM

有了前面知识的铺垫,了解SDRAM会更加容易。首先先原理图看下JZ2440上板载的SDRAM,数据线共有32根,使用了两个SDRAM组成了32位存储器,因此地址线A2接到了SDRAM的A0。从SDRAM的芯片手册可以看到一片SDRAM的容量为16M*16bits,因此需要寻址的存储单元有16M个,则地址线应该为24根,但是图中只使用了A2-A14,且用A24-A25连接到了BA0和BA1,这是由SDRAM的访问方式决定的。

 SDRAM框图如下,可以看到,SDRAM存储的地方分成了4个bank,每个bank 4M*16bits,需要row和col地址来访问其中的存储单元。所以访问SDRAM需要依次提供bank地址确定使用的bank,提供row确定行,col确定列,而row和col之间需要一定时间,这是一个控制参数Trcd。框图的内容比较简单就不赘述了,类比NorFlash可以很清晰地看懂。

 

需要设置的寄存器如下:

从原理图可以看出来,SDRAM使用的是S3C2440内存控制器的bank6,因此只需要设置bank6相关的寄存器,板子上将两个16bits的SDRAM并联为32bits内存,因此DW6设置为10。WS6位等待使能位,当S3C2440读但是SDRAM正忙还不能回复数据时,SDRAM可以设置该位让2440再等待一段时间,这里没有用到,直接disable。ST6是设置SDRAM多字节读或者写时仅对其种变化的字节操作,设置为0则仅对写有该效果,设置为1则读和写都有该效果,此处我们的读已经有内存控制器帮忙挑拣数据,因此设置为0即可。

 MT设置为11表示我们使用的是SDRAM,中间一段Tacs-PMC针对非SDRAM因此也不用理会。Trcd即为上文提到的Row和Col地址的时间间隔,搜索SDRAM芯片手册,可以知道最低为18ns,因此使用00,即20ns。SCAN表示SDRAM的列地址位数,查看SDRAM芯片手册,搜索column address,即可知道SDRAM使用的列地址位为A0-A8,即9位,因此设置为01。

 

 SDRAM为动态存储器,需要进行刷新保持数据,否则可能会丢失数据,这里的刷新其实就是给存储的电容重新充电。REFEN使用默认值使能刷新。TREFMD设置为自动刷新模式。Trp为片选信号发出后多长时间行地址才能使能,或者访问从一行调到另外一行的预充电时间,直接搜索SDRAM芯片手册,可以查到可设置为20ns,即设置为00。Tsrc可以通过Trc-Trp得到,Trc为访问一行跳到另一行的总需要时间,除了与充电的Trp外,还需要Tsrc,搜索Trc,取最小值60ns,则Tsrc为40ns,即设置为00。Refresh Counter查看SDRAM数据手册feature页面,可以看到8192 refresh cycles/64ms,即Refresh_period=64ms/8192=7.8us,则refresh_Couter可以根据公式算出来为1269.

依次往下,使能突发访问模式(可以一次访问多个存储单元,即发出起始地址、长度,即可返回连续存储单元的值),使能power down模式(通过SCKE进入节电模式),使能SCLK_EN(同样处于省电考虑),BK76MAP根据我们使用的64MB选择001。

 Fixed字样的为固定选项,直接选中即可,特殊的是CL,CL为发出列地址后数据能够得到的时间,查看SDRAM数据手册,发现CL可以为2或者3,压榨一下设置为2。该寄存器设置的是SDRAM的mode register,从上文SDRAM中框图可以看到。

 综上,输出代码如下:

s3c2440.h
----------------------------------
#ifndef __S3C2440_H
#define __S3C2440_H

#include <stdint.h>

...


//memory controller 
#define BWSCON (*((volatile uint32_t*)0x48000000))

#define BANKCON0 (*((volatile uint32_t*)0x48000004))
#define BANKCON6 (*((volatile uint32_t*)0x4800001C))

#define REFRESH (*((volatile uint32_t*)0x48000024))
#define BANKSIZE (*((volatile uint32_t*)0x48000028))
#define MRSRB6 (*((volatile uint32_t*)0x4800002C))

void HardwareInitAll(void);
void Delay(uint32_t time);

#endif

s3c2440.c
------------------------
#include "s3c2440.h"

...

static void MemoryControllerInit(void)
{
	BWSCON = (2<<24);
	BANKCON0 = (4<<8);
	BANKCON6 = (3<<15)|(0<<2)|(1<<0);
	REFRESH  = (1<<23) | (1269 << 0);
	BANKSIZE = (1<<7) | (1<<5) | (1<<4)|(1<<0);
	MRSRB6   = (2<<4);
}

void HardwareInitAll(void)
{
	WatchDogDisable();
	ClockDevideConfig();
	ChangeModeToAsynchronous();
	MPLLConfig();
	MemoryControllerInit();
}


#include <stdint.h>

#include "s3c2440.h"
#include "led.h"
#include "uart.h"

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 main()
{
	HardwareInitAll();
	LedInitAll();
	uint8_t led_now=kLed1;

	while(1)
	{
		if (sdram_test() == 0)
		{
			SingleLedOFF(led_now++);
			if(led_now > kLed3) { led_now =kLed1; }
			SingleLedON(led_now);
			Delay(100000);
		}		
	}


}

测试代码中对SDRAM进行连续写,然后连续读,判断数据是否正确,从而进行点灯,如果灯运行,则设置成功。

 
发布了19 篇原创文章 · 获赞 7 · 访问量 6924

猜你喜欢

转载自blog.csdn.net/G_METHOD/article/details/104419712