1. 程式人生 > >TQ2440開發板學習紀實(8)--- 從NAND Flash讀取資料,把程式碼搬運到SDRAM執行

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