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進行連續寫,然後連續讀,判斷數據是否正確,從而進行點燈,如果燈運行,則設置成功。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章