1. 程式人生 > >百為STM32開發板教程之十二——NAND FLASH

百為STM32開發板教程之十二——NAND FLASH



參考資料:
百為stm32開發板光碟V3\百為stm32開發板光碟\晶片資料手冊\K9F1208.pdf
百為stm32開發板光碟\st官方參考資料\Application notes\AN2784 Using the high-density STM32F10xxx FSMC peripheral to drive external memories.pdf

實驗目的:實現擦除NAND FLASH的第一個塊,並讀寫NAND FLASH的開頭兩頁

主要內容:
一、瞭解STM32 FSMC NAND控制器
二、瞭解K9F1208 NAND FLASH的原理與操作
三、程式設計實現擦除K9F1208 NAND FLASH的第一個塊,並讀寫K9F1208 NAND FLASH的開頭兩頁


一、STM32 FSMC NAND控制器
1、1、STM32 FSMC功能框圖:
FSMC主要包括有:AHB介面(包含FSMC配置暫存器) , NOR快閃記憶體和PSRAM控制器,NAND快閃記憶體和PC卡控制器,外部裝置介面

2、STM32 FSMC外部裝置地址對映:
從FSMC的角度看,可以把外部儲存器劃分為固定大小為256M位元組的四個儲存塊,見下圖
● 儲存塊1用於訪問最多4個NOR快閃記憶體或PSRAM儲存裝置。這個儲存區又被劃分為4個NOR/PSRAM區,並有4個專用的片選。 
● 儲存塊2和3用於訪問NAND快閃記憶體裝置,每個儲存塊連線一個NAND快閃記憶體。 
● 儲存塊4用於訪問PC卡裝置 

每一個儲存塊上的儲存器型別是由使用者在配置暫存器中定義的。


其中塊2和塊3屬於NAND儲存塊,每個儲存塊又可以劃分為通用和屬性空間


通用和屬性空間又可以在低256K位元組部分劃分為3個區
● 資料區(通用/屬性空間的前64K位元組區域) 
● 命令區(通用/屬性空間的第2個64K位元組區域) 
● 地址區(通用/屬性空間的第2個128K位元組區域)

應用軟體使用這3個區訪問NAND快閃記憶體儲存器: 
● 傳送命令到NAND快閃記憶體儲存器:軟體只需對命令區的任意一個地址寫入命令即可。 
● 指定操作NAND快閃記憶體儲存器的地址:軟體只需對地址區的任意一個地址寫入命令即可。因為一個NAND地址可以有4或5個位元組(依實際的儲存器容量而定),需要連續地執行對地址區的寫才能輸出完整的操作地址。 

● 讀寫資料:軟體只需對資料區的任意一個地址寫入或讀出資料即可。 因為NAND快閃記憶體儲存器自動地累加其內部的操作地址,讀寫資料時沒有必要變換資料區的地址,即不必對連續的地址區操作。

3、STM32的NAND控制訊號
(1)STM32 FSMC NAND控制訊號描述:


(2)STM32 FSMC訊號連線K9F1208 NAND FLASH:


(3)百為STM3210E-EVAL開發板上STM32和K9F1208的連線電路圖(這裡電路圖上畫的是NAND512,實際焊接的硬體是K9F1208):



二、K9F1208 NAND FLASH的原理與操作
1、K9F1208 的硬體結構組織
K9F1208是容量為512M bit,即64M byte的儲存器,它是由4096個塊(block)組成,其中每個塊又是由32個頁(page)組成,每個頁由512byte+16byte組成。
K9F1208的讀寫都是以頁為單位,而擦除則是以塊為單位。


程式中相關定義:
/* FSMC NAND memory parameters */
#define NAND_PAGE_SIZE             ((u16)0x0200) /* 512 bytes per page w/o Spare Area */
#define NAND_BLOCK_SIZE            ((u16)0x0020) /* 32x512 bytes pages per block */
#define NAND_ZONE_SIZE             ((u16)0x0400) /* 1024 Block per zone */
#define NAND_SPARE_AREA_SIZE       ((u16)0x0010) /* last 16 bytes as spare area */
#define NAND_MAX_ZONE              ((u16)0x0004) /* 4 zones of 1024 block */

2、K9F1208引腳定義


3、K9F1208的操作命令集


(1)讀ID命令:

先輸出命令90H,再輸出地址00H,然後讀回4個位元組的資料即是K9F1208的ID,ECH,76H,5AH,3FH
程式碼如下:
void FSMC_NAND_ReadID(NAND_IDTypeDef* NAND_ID)
{
  u32 data = 0;
  /* 傳送命令到命令區0x70010000 */  
  *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = 0x90;
  *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;
   /* 從K9F1208 NAND FLASH讀回ID序列 */ 
   data = *(vu32 *)(Bank_NAND_ADDR | DATA_AREA);  //從資料區0x70000000讀回資料
   NAND_ID->Maker_ID   = ADDR_1st_CYCLE (data);
   NAND_ID->Device_ID  = ADDR_2nd_CYCLE (data);
   NAND_ID->Third_ID   = ADDR_3rd_CYCLE (data);
   NAND_ID->Fourth_ID  = ADDR_4th_CYCLE (data);  
}

(2)塊擦除命令

塊擦除是先輸出60H,再輸出塊地址,然後輸出D0H,用70H讀回狀態,等待操作完成即可
u32 FSMC_NAND_EraseBlock(NAND_ADDRESS Address)
{
  *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_ERASE0;  //傳送命令60H到命令區0x70010000
  *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS);  //傳送地址A9~A16到地址區0x70020000
  *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS); //傳送地址A17~A24到地址區0x70020000
  *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_3rd_CYCLE(ROW_ADDRESS);  //傳送地址A25到地址區0x70020000

  *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_ERASE1;  //傳送命令D0H到命令區0x70010000 
  return (FSMC_NAND_GetStatus());  //讀回操作結果
}

(4)頁寫入命令
因為每個頁(page)可分為A,B,C三個區


所以頁寫入也分為三種方式:


具體時序:


我們這裡採用的第一種方式,可以寫入0~528byte的資料。K9F1208屬於小頁的NAND(512byte+16byte),區別於大頁的NAND(2048byte+64byte)
u32 FSMC_NAND_WriteSmallPage(u8 *pBuffer, NAND_ADDRESS Address, u32 NumPageToWrite)
{
  u32 index = 0x00, numpagewritten = 0x00, addressstatus = NAND_VALID_ADDRESS;
  u32 status = NAND_READY, size = 0x00;
  while((NumPageToWrite != 0x00) && (addressstatus == NAND_VALID_ADDRESS) && (status == NAND_READY))
  {
    /* 頁寫命令和地址 */
    *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_AREA_A;  //傳送命令00H到命令區0x70010000,從A區開始寫入
    *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_WRITE0;  //傳送命令80H到命令區0x70010000
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;  //傳送地址A0~A7到地址區0x70020000,從地址0開始寫入  
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS); //傳送地址A9~A16到地址區0x70020000  
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS);  //傳送地址A17~A24到地址區0x70020000  
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_3rd_CYCLE(ROW_ADDRESS);  //傳送地址A25到地址區0x70020000
    /* 計算寫入資料的大小 */
    size = NAND_PAGE_SIZE + (NAND_PAGE_SIZE * numpagewritten);
    /* 寫入資料 */
    for(; index < size; index++)
    {
      *(vu8 *)(Bank_NAND_ADDR | DATA_AREA) = pBuffer[index];  //傳送資料到資料區0x70000000
    }

    /* 檢查狀態看是否操作成功 */
    status = FSMC_NAND_GetStatus();

    if(status == NAND_READY)  //如果操作完成
    {
      numpagewritten++;  //已寫入頁數加1
      NumPageToWrite--;  //待寫入頁數減1
      /* 計算要寫入的下一個小頁的地址 */
      addressstatus = FSMC_NAND_AddressIncrement(&Address);    
    }    
  }

  return (status | addressstatus);
}

(5)read 1頁讀命令

頁讀命令是先輸出00H,再輸出要讀入的頁地址,然後就可以讀回最多528byte的資料了。
u32 FSMC_NAND_ReadSmallPage(u8 *pBuffer, NAND_ADDRESS Address, u32 NumPageToRead)
{
  u32 index = 0x00, numpageread = 0x00, addressstatus = NAND_VALID_ADDRESS;
  u32 status = NAND_READY, size = 0x00;
  while((NumPageToRead != 0x0) && (addressstatus == NAND_VALID_ADDRESS))
  {    
    /* 頁讀命令和頁地址*/
    *(vu8 *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_AREA_A;   //傳送命令00H到命令區0x70010000

    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;  ////傳送地址A0~A7(00H)到地址區0x70020000
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS); //傳送地址A9~A16到地址區0x70020000 
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS);  //傳送地址A17~A24到地址區0x70020000 
    *(vu8 *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_3rd_CYCLE(ROW_ADDRESS);   //傳送地址A25到地址區0x70020000

    /* 計算要讀的資料大小 */
    size = NAND_PAGE_SIZE + (NAND_PAGE_SIZE * numpageread);

    /* 讀資料到pBuffer */    
    for(; index < size; index++)
    {
      pBuffer[index]= *(vu8 *)(Bank_NAND_ADDR | DATA_AREA);  //從資料區0x70000000讀回資料
    }
    numpageread++;  //已讀的頁數加1

    NumPageToRead--;  //待讀的頁數減1
    /* 計算下一個要讀的頁地址 */               
    addressstatus = FSMC_NAND_AddressIncrement(&Address);
  }
  /* 檢查狀態看是否操作成功 */
  status = FSMC_NAND_GetStatus();

  return (status | addressstatus);
}

三、程式設計實現擦除K9F1208 NAND FLASH的第一個塊,並讀寫K9F1208 NAND FLASH的開頭兩頁

/* main.c */

  /* FSMC NAND初始化 */
  FSMC_NAND_Init();
  /* 讀NAND ID操作 */
  FSMC_NAND_ReadID(&NAND_ID);
  /* 檢查ID是否正確 */
  if((NAND_ID.Maker_ID == NAND_K9F1208_MakerID) && (NAND_ID.Device_ID == NAND_K9F1208_DeviceID))
  {
    /* 初始化要寫入NAND的頁地址 */ 
    WriteReadAddr.Zone = 0x00;
    WriteReadAddr.Block = 0x00;
    WriteReadAddr.Page = 0x00; 
    /* 擦除NAND FLASH的第一個塊(第1和第2頁所在的塊) */
    status = FSMC_NAND_EraseBlock(WriteReadAddr);
    /* 寫資料到NAND FLASH的第1和第2頁 */
    /* 填充要傳送的資料到buffer */
    Fill_Buffer(TxBuffer, BUFFER_SIZE , 0x66);
    status = FSMC_NAND_WriteSmallPage(TxBuffer, WriteReadAddr, PageNumber);  //PageNumber=2,表示要寫入第1和第2頁
    /* 從NAND FLASH讀回資料 */
    status = FSMC_NAND_ReadSmallPage (RxBuffer, WriteReadAddr, PageNumber);

    /* 比較寫入的資料和讀回的資料是否相等 */
    for(j = 0; j < BUFFER_SIZE; j++)
    {
      if(TxBuffer[j] != RxBuffer[j])
      {     
        WriteReadStatus++;
      } 
    }
    if (WriteReadStatus == 0)
    { 
      /* 如果相等,則點亮LED1 */
      GPIO_SetBits(GPIOF, GPIO_Pin_6);
    }
    else
    { 
      /* 否則,點亮LED2 */
      GPIO_SetBits(GPIOF, GPIO_Pin_7);     <