Nor Flash工作原理


http://blog.chinaunix.net/uid-26876150-id-3723678.html

 Nor Flash 具有像內存一樣的接口,它可以像內存一樣讀,卻不可以像內存一樣寫,Nor Flash 的寫、擦除都需要發出特定的命令。談到 Nor Flash 通常就會涉及到 CFI ([Common Flash Interface) 接口,一般 Nor Flash 都支持發命令來讀取廠家 ID 和 設備 ID 等基本信息,但並不是所有的 Nor Flash 都支持發命令來獲取和芯片本身容量大小、扇區數、擦除塊大小等信息。為了讓將來的 Nor Flash 兼容性更好,引進了 CFI 接口,將芯片有關的信息都寫入芯片內部,通過 CFI 命令就可以獲取這些信息。
    Linux 內核中對各種型號的 Nor Flash 都有很好的支持 ,但是其組織復雜,不利於分析。這里選用 u-boot 里面的 Nor Flash 代碼來分析。代碼位於:u-boot-2010.06/board/samsung/smdk2410/flash.c 。
    通常內核里面要識別一個 Nor Flash 有兩種方法:一種是 jedec 探測,就是在內核里面事先定義一個數組,該數組里面放有不同廠家各個芯片的一些參數,探測的時候將 flash 的 ID 和數組里面的 ID 一一比較,如果發現相同的,就使用該數組的參數。另一種是 cfi 探測,就是直接發各種命令來讀取芯片的信息,比如 ID、容量等。jedec 探測的優點就是簡單,缺點是如果內核要支持的 flash 種類很多,這個數組就會很龐大。../samsung/smdk2410/flash.c 文件采用的是第一種方法,但是還是有些區別的,內核里面用 jedec 探測一個芯片時,是先通過發命令來獲取 flash 的 ID,然后和數組比較,但是 flash.c 中連 ID 都是自己通過宏配置的。

unsigned long flash_init (void)
{
    for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) 
    {
        ulong flashbase = 0;
        //設置 flash_id ,這個標志保存廠家 ID 和 設備 ID
        flash_info[i].flash_id =
#if defined(CONFIG_AMD_LV400)
            (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV400B & FLASH_TYPEMASK);
#elif defined(CONFIG_AMD_LV800)
            (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV800B & FLASH_TYPEMASK);
#else
            #error "Unknown flash configured"
#endif
        //設置 flash 大小和扇區數
        flash_info[i].size = FLASH_BANK_SIZE;
        flash_info[i].sector_count = CONFIG_SYS_MAX_FLASH_SECT;
        //對於 flash 的每個扇區,都需要保存扇區的首地址
        for (j = 0; j < flash_info[i].sector_count; j++)     
        {
            ......      
            flash_info[i].start[j] = flashbase + (j - 3) * MAIN_SECT_SIZE;
        }
        size += flash_info[i].size;    //片外所有flash 的總大小
    }
    //對代碼區的扇區設置寫保護,這里只是軟件的一種設定

    flash_protect (FLAG_PROTECT_SET, CONFIG_SYS_FLASH_BASE,
               CONFIG_SYS_FLASH_BASE + monitor_flash_len - 1,
               &flash_info[0]);  
    //如果環境變量保存在 nor 里面,還需對這些扇區設置寫保護

    flash_protect (FLAG_PROTECT_SET, CONFIG_ENV_ADDR,
               CONFIG_ENV_ADDR + CONFIG_ENV_SIZE - 1, &flash_info[0]);
                            
    return size;    //返回 flash 大小
}

    flash_init() 函數主要是做一些 flash 的初始化,比如設置 flash 的 ID、大小、扇區數等來構造 flash_info_t 結構體,但是從上面的代碼可以看出,在該初始化函數中並沒有做任何與硬件有關的初始化,所有的值都是通過外部賦值,也就是說我們可以給這些成員變量賦任何我們想要的值,哪怕這些值並不是 flash 真正的參數,雖然這些值並不影響本函數的調用,但是和下面這些函數就有密切關系。 
int flash_erase (flash_info_t * info, int s_first, int s_last)
{
    //參看是否有寫保護扇區,有直接返回錯誤
    prot = 0;
    for (sect = s_first; sect <= s_last; ++sect)
    {
        if (info->protect[sect]) 
            prot++;
    }
    if (prot)
        return ERR_PROTECTED;
    //關閉中斷等,防止擦除過程被中斷
    cflag = icache_status ();
    icache_disable ();
    iflag = disable_interrupts ();

    /* Start erase on unprotected sectors */
    for (sect = s_first; sect <= s_last && !ctrlc (); sect++) 
    {
        printf ("Erasing sector %2d ... ", sect);

        /* arm simple, non interrupt dependent timer */
        reset_timer_masked ();

        if (info->protect[sect] == 0)    //此處的判斷有點多余 
        {   /* not protected */
            
//取扇區的首地址
            vu_short *addr = (vu_short *) (info->start[sect]);    
            //發解鎖和擦除扇區命令
            MEM_FLASH_ADDR1 = CMD_UNLOCK1;    //往地址 0x555<<1 寫入 0xAA
            MEM_FLASH_ADDR2 = CMD_UNLOCK2;    //往地址 0x2AA<<1 寫入 0x55

            MEM_FLASH_ADDR1 = CMD_ERASE_SETUP;//往地址 0x555<<1 寫入 0x80

            MEM_FLASH_ADDR1 = CMD_UNLOCK1;    
            MEM_FLASH_ADDR2 = CMD_UNLOCK2;
            *addr = CMD_ERASE_CONFIRM;    //往地址 0x555<<1 寫入 0x30


            /* wait until flash is ready */
            chip = 0;
            do 
            {
                result = *addr;    //讀取該扇區首地址里面的值
                /* check timeout */

                if (get_timer_masked () > CONFIG_SYS_FLASH_ERASE_TOUT)
                {
                    MEM_FLASH_ADDR1 = CMD_READ_ARRAY;
                    chip = TMO;
                    break;
                }
                //BIT_ERASE_DONE = 0x80,即判斷 DQ7 是否為 1

                if (!chip && (result & 0xFFFF) & BIT_ERASE_DONE)  
                    chip = READY;
                //
BIT_PROGRAM_ERROR = 0x20,即判斷 DQ5 是否為 1
                if (!chip && (result & 0xFFFF) & BIT_PROGRAM_ERROR)
                    chip = ERR;
            } while (!chip);

            MEM_FLASH_ADDR1 = CMD_READ_ARRAY; //往地址 0x555<<1 寫入 0xF0

            ......
            printf ("ok.\n");
        } 
        else 
        {    /* it was protected */
            printf ("protected!\n");
        }
    }
    ......
    /* allow flash to settle - wait 10 ms */
    udelay_masked (10000);

    return rc;
}

    對於擦除工作,不可能瞬間完成,如何檢測芯片是否已經完成擦除工作是我們所關心的,當然你可以用一個很長的延時來確保芯片肯定已經完成擦除,但是一款芯片一定會提供相應的狀態供我們檢測,利用這些狀態位可以減少程序中不必要的等待。下面這些描述是摘自芯片手冊。  

      
Sector Erase Command Sequence
When the Embedded Erase algorithm is complete, the device returns to reading array data and addresses are no longer latched. The system can determine the status of the erase operation by using DQ7, DQ6, DQ2, or RY/BY#. (Refer to “Write Operation Status” for information on these status bits.)

                                
WRITE OPERATION STATUS
The device provides several bits to determine the status of a write operation: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY#. Table 10 and the following subsections describe the functions of these bits. DQ7, RY/BY#, and DQ6 each offer a method for determining whether a program or erase operation is complete or in progress. These three bits are discussed first.

對於擦除過程:
During the Embedded Erase algorithm, Data# Polling produces a “0” on DQ7. When the Embedded Erase algorithm is complete, or if the device enters the Erase Suspend mode, Data# Polling produces a “1” on DQ7. This is analogous to the complement/true datum output described for the Embedded Program algorithm: the erase function changes all the bits in a sector to “1”; prior to this, the device outputs the “complement,” or“0.” The system must provide an address within any of the sectors selected for erasure to read valid status information on DQ7.
   Embedded Erase algorithm 是指”嵌入的擦除算法程序“,當我們發出擦除命令的時候 Nor Flash 內部就會執行一系列指令來進行擦除工作,在這過程它通過檢測 Data = FF?(如上圖所示)來判斷擦除狀態,但是這是 Nor Flash 內部的判斷方法,與之對應,外部的內存控制器可以通過Data# Polling 來檢測 。
When the system detects DQ7 has changed from the complement to true data, it can read valid data at DQ7–DQ0 on the following read cycles. This is because DQ7 may change asynchronously with DQ0–DQ6 while Output Enable (OE#) is asserted low. Figure 19, Data#Polling Timings (During Embedded Algorithms), in the“AC Characteristics” section illustrates this.

     

   如果你實在不想花太多時間看手冊,或對英文文檔頭疼,可以看看下面的總結。
   Nor Flash 提供幾個數據位來確定一個寫操作的狀態,它們分別是: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY# 。其中DQ7, RY/BY#引腳, 和 DQ6 中的每一個都提供了一種方法來判斷一個編程或者擦除操作是否已經完成或正在進行中。實際編程中只需要使用其中的一種。

 

 

DQ7:Data# Polling bit,在編程過程從正在編程的地址中讀出的數據的DQ7為要寫入數據位的補碼。比如寫入的數據為0x0000,即輸入的DQ7為 0 ,則在編程中讀出的數據為 1 ;當編程完成時讀出的數據又變回輸入的數據 0 。在擦除過程中DQ7輸出為  0 ;擦除完成后輸出為 1 ;注意讀取的地址必須是擦除范圍內的地址。RY/BY#高電平表示‘就緒’,低電平表示‘忙’。

DQ6輪轉位1(Toggle Bit 1),在編程和擦除期間,讀任意地址都會導致DQ6的輪轉(0,1間相互變換)。當操作完成后,DQ6停止轉換。

 

DQ2:輪轉位2(Toggle Bit 2),當某個扇區被選中擦除時,讀有效地址(地址都在擦除的扇區范圍內)會導致DQ2的輪轉。

注意:DQ2只能判斷一個特定的扇區是否被選中擦除,但不能區分這個扇區是否正在擦除中或者正處於擦除暫停狀態。相比之下,DQ6可以區分Nor Flash是否處於擦除中或者擦除暫停狀態,但不能區分哪個扇區被選中擦除,因此需要這2個位來確定扇區和模式狀態信息。

 

 

DQ5: 超時位(Exceeded Timing Limits) ,當編程或擦除操作超過了一個特定內部脈沖計數時 DQ5=1,表明操作失敗。當編程時把 0 改為 1 就會導致 DQ5=1,因為只有擦除擦做才能把 0 改為 1。當錯誤發生后需要執行復位命令才能返回到讀數據狀態。

DQ3: (扇區擦除計時位)Sector Erase Timer ,只在扇區擦除指令時起作用。當擦除指令真正開始工作時 DQ3=1 ,此時輸入的命令(除擦除暫停命令外)都被忽略,DQ3=0 時,可以添加附加的扇區用於多扇區擦除。 

        
    看了上面的分析就知道為什么在 flash_erase() 函數中當發出擦除命令后要檢測 DQ7 是否為 1,if (!chip && (result & 0xFFFF) & BIT_ERASE_DONE) 。因為擦除過程用到了扇區的首地址,所以在 flash_init() 函數中就不能隨便設置這些值,要保持和實際的 flash 一致。 

/* Copy memory to flash. */
int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
{
    ulong cp, wp;
    int l;
    int i, rc;
    ushort data;
    //保持16位地址對齊
    wp = (addr & ~1);    /* get lower word aligned address */

    /* handle unaligned start bytes */
    if ((l = addr - wp) != 0) 
    {
        data = 0;
        for (i = 0, cp = wp; i < l; ++i, ++cp) 
            data = (data >> 8) | (*(uchar *) cp << 8);
      
        for (; i < 2 && cnt > 0; ++i) 
        {
            data = (data >> 8) | (*src++ << 8);
            --cnt;
            ++cp;
        }
        //條件不成立,此語句不起作用

        for (; cnt == 0 && i < 2; ++i, ++cp) 
            data = (data >> 8) | (*(uchar *) cp << 8);
      
        if ((rc = write_hword (info, wp, data)) != 0) 
            return (rc);
  
        wp += 2;
    }

    /* handle word aligned part */
    while (cnt >= 2)
    {
        data = *((vu_short *) src);
        if ((rc = write_hword (info, wp, data)) != 0) 
            return (rc);
        
        src += 2;
        wp += 2;
        cnt -= 2;
    }

    if (cnt == 0) 
        return ERR_OK;
    
    /* handle unaligned tail bytes */
    data = 0;
    for (i = 0, cp = wp; i < 2 && cnt > 0; ++i, ++cp) 
    {
        data = (data >> 8) | (*src++ << 8);
        --cnt;
    }
    //
條件不成立,此語句不起作用
    for (; i < 2; ++i, ++cp) 
        data = (data >> 8) | (*(uchar *) cp << 8);

    return write_hword (info, wp, data);
}

    write_buff() 函數拷貝內存里面的數據到 Nor Flash ,這個函數首先要保證16位地址對齊,由 /* handle unaligned start bytes */ 包含的語句處理,雖然寫得比較復雜,但是做得事情很簡單。假如要往 flash 地址 3 處(這里的地址 3 是相對於 ARM 而言)開始寫一段數據,先把地址 2 處(這里的地址 2 是相對於 ARM 而言)的數據讀出,和要寫入地址 3 的數據組成一個16為的數據,寫入地址 2 處,這樣地址 2 里面的數據不變,地址 3 處就是寫入的數據。
注意:這里的地址都是站在 ARM 角度說的,對於 flash 地址 2 對於它的地址 1,這里面涉及到地址線的對齊關系。
    write_buff() 函數里面是調用 
write_hword() 來實現底層的寫操作。
static int write_hword (flash_info_t * info, ulong dest, ushort data)
{
    vu_short *addr = (vu_short *) dest;
    ushort result;
    int rc = ERR_OK;
    int cflag, iflag;
    int chip;
    /* Check if Flash is (sufficiently) erased */
    result = *addr;
    if ((result & data) != data)
        return ERR_NOT_ERASED;

    /*
     * Disable interrupts which might cause a timeout
     * here. Remember that our exception vectors are
     * at address 0 in the flash, and we don't want a
     * (ticker) exception to happen while the flash
     * chip is in programming mode.
     */
    cflag = icache_status ();
    icache_disable ();
    iflag = disable_interrupts ();

    MEM_FLASH_ADDR1 = CMD_UNLOCK1;    //往地址 0x555<<1 寫入 0xAA

    MEM_FLASH_ADDR2 = CMD_UNLOCK2;    //往地址 0x2AA<<1 寫入 0x55
    MEM_FLASH_ADDR1 = CMD_UNLOCK_BYPASS;    //往地址 0x555<<1 寫入 0x20
    *addr = CMD_PROGRAM;    //往地址 0x555<<1 寫入 0xA0  編程
    *addr = data;    //往地址 addr 寫入數據

    /* arm simple, non interrupt dependent timer */
    reset_timer_masked ();

    /* wait until flash is ready */
    chip = 0;
    do 
    {
        result = *addr;
        /* check timeout */
        if (get_timer_masked () > CONFIG_SYS_FLASH_ERASE_TOUT) 
        {
            chip = ERR | TMO;
            break;
        }
        if (!chip && ((result & 0x80) == (data & 0x80)))
            chip = READY;
        if (!chip && ((result & 0xFFFF) & BIT_PROGRAM_ERROR)) 
        {
            result = *addr;

            if ((result & 0x80) == (data & 0x80))
                chip = READY;
            else
                chip = ERR;
        }

    } while (!chip);
    *addr = CMD_READ_ARRAY;    //往地址 0x555<<1 寫入 0xF0
  復位
    ......
    return rc;
}
    寫入數據后通過判斷 DQ7 是否和寫入的一致來確認編程操作結束,對應的代碼是
 if (!chip && ((result & 0x80) == (data & 0x80))) 。        
During the Embedded Program algorithm, the device outputs on DQ7 the complement of the datum programmed to DQ7. This DQ7 status also applies to programming during Erase Suspend. When the Embedded Program algorithm is complete, the device outputs the datum programmed to DQ7. The system
must provide the program address to read valid status information on DQ7. If a program address falls within a protected sector, Data# Polling on DQ7 is active for
approximately 1 μs, then the device returns to reading array data.

   

         
    關於Unlock Bypass的相關概念和操作可以參考手冊里面的介紹,簡單來講,Unlock Bypass 就是忽略解鎖的意思,因為每次編程 flash 都要發出解鎖命令,再發出編程命令 0xA0,最后才寫入16位數據,設想如果寫入的數據量很大,每次寫入一個 word(16位) 都要重新解鎖一下,效率很低。Unlock Bypass 允許我們寫入數據的時候忽略解鎖命令,直接發出 0xA0 命令后再往相應地址寫入數據。
Unlock Bypass Command Sequence
The unlock bypass feature allows the system to program bytes or words to the device faster than using the standard program command sequence. The unlock bypass
command sequence is initiated by first writing two unlock cycles. This is followed by a third write cycle containing the unlock bypass command, 20h. The device then enters the unlock bypass mode. A two-cycle unlock bypass program command sequence is all that is required to program in this mode. The first cycle in this sequence contains the unlock bypass program command, A0h; the second cycle contains the program address and data. Additional data is programmed in the same manner. This mode dispenses with the initial two unlock cycles required in the standard program command sequence, resulting in faster total programming time. Table 9 shows the requirements for the command sequence.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM