TQ2440開發板學習紀實(8)--- 從NAND Flash讀取資料,把程式碼搬運到SDRAM執行
因為依賴於S3C2440的開機自動從Nandflash複製資料到片內SRAM執行,目前我們的可執行程式體積仍然不能大於4KB的限制。而我們的程式目前已經非常接近這個限制大小了,為了能夠繼續開發,必須突破這個限制。為此需要實現程式碼搬運功能,把程式從Nandflash搬運到SDRAM中去,並跳轉到SDRAM執行。
本文為啥不實現NandFlash寫? 因為對於我們的實現目前尚未需求。更重要的是,寫操作涉及到擦除,更涉及到NandFlash壽命。為了減少寫次數,延長NandFlash壽命,一般會在軟體層實現寫快取。這就涉及到大量的程式碼,會偏離我們實驗的主題--程式碼搬運。等以後確實需要儲存資料到NandFlash時,再實現寫功能不遲。
1 NAND Flash基礎知識
1.1 TQ2440板載Nandflash晶片基本情況
NAND Flash作為一種儲存晶片,其讀寫操作需要特定的時序和規範。實際上也存在相關的工業標準,用來標準化Nandflash的讀寫等操作。
與RAM和ROM不同,NAND Flash的傳送資料、地址、命令時共用同一組針腳,通過不同的時序加以區分。下圖是TQ2440開發板上搭載的Nandflash介面圖:
其介面非常簡單,其中
- IO0-IO7,8個針腳用於傳送資料、地址、命令。
- WP,防寫,直接接地。
- WE,寫使能
- RE,讀使能
- CE,片選
- ALE,地址鎖存
- RDY/B,忙閒狀態
- CLE,命令鎖存
- VSS,接地
- VCC,電源
1.2 Nand flash基本術語
- 頁(Page),NandFlash以頁為單位進行讀寫,常見的頁大小為512B,1KB,2KB等。
- 塊(BLOCK),NandFlash在寫入資料之前必須先擦除(寫入1),而擦除的最小單元就是塊,常見的塊大小為64頁。
- 擦除。NandFlash寫入之前必須先擦除,也就是全部寫入1。因為寫的時候只能寫0,不能寫1。
- 資料頻寬,也就是每次傳送資料的位數(I/O針腳數),常見的有8,16。
- 地址週期,傳送一個完整的地址,需要傳送幾次資料。
1.3 S3C2440片內NandFlash控制器
(1)硬體讀取
S3C2440片內集成了NandFlash控制器,啟動時,通過外部針腳的電平高低的硬體配置來獲得頁大小,資料寬度,地址週期等要素,從而可以在啟動時能夠完成讀取Nandflash到SRAM。
(2)軟體讀取
S3C2440的片內NandFlash控制器同樣對使用者開放,允許用於以軟體操縱它來讀寫NandFlash。
2 利用S3C2440片內NandFlash控制器讀取NandFlash資料
2.1 相關暫存器
- NFCONF 配置暫存器, 用於設定NandFlash時序
- NFCONT 控制暫存器,用於控制片選等訊號
- GPACON 引腳複用,GPIO和NandFlash控制訊號切換
- NFCMD 命令暫存器,把命令寫入時,自動傳送
- NFADDR 地址暫存器,把地址寫入時,自動傳送
- NFDATA 資料暫存器,把資料寫入時,自動傳送。讀取時,自動更新。
NFSTAT 狀態暫存器,探測NANDFLASH是否忙
其他ECC相關暫存器,用於錯誤校驗,暫時可以不用。
具體如何設定,請參考S3C2440的資料手冊和Nandflash的資料手冊。
2.2 初始化
void nand_init()
{
rGPACON |= (0x3F << 17); /* GPIO */
rNFCONF &= ~(3<<12 | 7<<8 | 7<<4); /* clock */
rNFCONF |= (1<<12 | 4<<8 | 0<<4);
rNFCONT |= 1; /* enable NF controller */
rNFCONT &= ~(1<<1); /* Chip select */
rNFCONT &= ~(1<<12); /* disable soft lock */
rNFSTAT |= 1<<2; /* clear BUSY */
nand_send_cmd(CMD_RESET);
nand_wait();
}
2.3 頁內任意讀
NandFlash一般讀取的最小單位為頁。也有的NandFlash支援單位元組讀取,但是並不常用。我們可以通過先讀一頁,然後選擇性的儲存資料即可實現頁內任意大小讀取。
/* read [start,start+count) in a page */
int nand_read_page(unsigned char* buf, int iPage, int start, int count)
{
nand_chip_enable();
nand_send_cmd(CMD_PAGE_READ);
nand_send_addr(0x00);
nand_send_addr(0x00);
nand_send_addr(iPage & 0xFF);
nand_send_addr( (iPage >> 8) & 0xFF);
nand_send_addr( (iPage >> 16) & 0x1);
nand_send_cmd(CMD_PAGE_READ_CONFIRM);
nand_wait();
unsigned char * p = buf;
int i;
unsigned char c;
for (i=0; i<2048; i++) {
if ( i<start || i>=start+count ) {
c = rNFDATA8;
}else {
*p++ = rNFDATA8;
}
}
nand_chip_disable();
return count;
}
2.4 全範圍任意讀
基於頁內任意讀,我們進而可以實現全範圍任意讀。原理如下:
任何讀操作涉及的範圍無非有兩種情況:
(1)所需資料正好全部屬於同一個頁。此時執行頁內任意讀即可。
(2)所需資料跨兩個或多個頁。此時對於每一頁按順序分別執行任意讀即可。只是第一頁和最後一頁可能可能只需讀部分資料,中間的頁則是全頁讀取。
實現程式碼如下:
/* read size bytes starting from addr */
int nand_read(unsigned char* buf, unsigned int addr, int size)
{
int startPage = addr / 2048;
int endPage = (addr+size-1) / 2048;
int startByte = addr % 2048;
/* if the range is in only one page */
if (startPage == endPage) {
return nand_read_page(buf, startPage, startByte, size);
}
/* if the range is across 2 or more pages */
int n = nand_read_page(buf, startPage, startByte, 2048-startByte); /* the first page */
int page = startPage+1;
while (page < endPage) { /* middle page(s) */
n += nand_read_page(buf+n, page, 0, 2048);
page ++;
}
n += nand_read_page(buf+n, endPage, 0, (size-n)); /* last page */
return n;
}
3 程式碼搬運
3.1 需求分析
(1)程式的前4KB,要完成的基本功能:CPU時鐘初始化,SDRAM初始化,NandFlash初始化,UART0初始化。然後把NandFlash中的程式整體複製到SDRAM中,最後使能中斷,跳轉到Main函式執行。
(2)由於尚未啟動MMU,所以異常向量入口位於絕對實體地址0開始處,也就是SRAM中。
(3)4KB之後的程式完成其他硬體初始化。
3.2 關鍵技術
3.2.1 位置無關程式碼
程式的前4KB首先是被硬體自動複製到SRAM中執行,然後是被自己搬運到SDRAM中執行,這就需要這部分代是地址無關程式碼。具體來說:
- 程式跳轉需要使用相對跳轉指令,如B,BL,它們都是相對於當前PC的值,增減適當偏移量來完成跳轉。
- C語言會自動根據跳轉範圍優先選用相對跳轉指令,我們的程式跳轉範圍小於32MB,所以C編譯器會自動生成位置無關程式碼。
3.2.2 絕對地址跳轉
這個正好與相對地址跳轉相反,絕對地址跳轉指令如 BX,或者給PC直接賦值的指令如 mov pc, Rn, add pc, rn, rm等等。它們都是直接給PC強制賦值,從而時間4GB範圍的跳轉。
SRAM地址空間為[0x00000000, 0x00001000),而SDRAM的地址範圍是[0x30000000,0x34000000),顯然兩者最小距離也遠遠超過了相對跳轉的範圍。
目前需要絕對地址跳轉的地方有:
- 向Main()函式跳轉
- 向異常處理程式跳轉
3.2.3 連結過程的控制
搬運程式碼的功能需要位於可執行程式的前4KB,這可以通過連結指令碼來控制。我們的連結指令碼如下。
ENTRY(ResetEntry)
SECTIONS {
. = 0x30000000;
.text ALIGN(4): {
init.o(.*)
boot.o(.*)
uart.o(.*)
nand.o(.*)
*(.text)
}
.data ALIGN(4): {
*(.data)
*(.rodata)
}
.bss ALIGN(4): {
*(.bss)
}
}
其中需要在SRAM中執行的檔案是: init.s,boot.c,以及uart.c。
3.3 原始碼導讀
基本的流程是:
(1)init.s 完成CPU時鐘初始化,SDRAM初始化,各模式堆疊初始化,以及異常向量表的建立。
(2)然後呼叫boot.c裡的boot()函式,此函式呼叫uart.c的函式完成串列埠初始化,呼叫nand.c裡的函式完成程式碼搬運。
(3)跳轉到main.c的Main()函式。
4 完整原始碼
完整原始碼下載,V0.9。