【轉載】Z-STACK中關於非易失性存儲器Nv操作實例

【轉載】Z-STACK中關於非易失性存儲器Nv操作實例

在Z-STACK中Nv存儲器主要用於保存網絡的配置參數,如網絡地址,使 系統在掉電重啓仍然能讀取一些參數,自動加入到原來的網絡中,這樣其網絡地址沒有變化!

在z-stack中,每一個參數的配置對應的是一個Nv條目(item),每一個item都有自己的ID,z-stack中使用的條目ID範圍如下(ZComDef.h):

0x0000                              保留

0x0001~0x0020              操作系統抽象層(OSAL)

0x0021~0x0040              網絡層(NWK)

0x0041~0x0060              應用程序支持子層(APS)

0x0061~0x0080              安全(Security)

0x0081~0x00A0             Zigbee設備對象(ZDO)

0x00A1~0x0200             保留

0x0201~0x0FFF              應用程序

0x1000~0xFFFF              保留

如果是我們自己的應用程序中需要使用Nv,則定義其ID在0x0201~0x0FFF 範圍內!

Z-STACK真正提供給用戶使用的是五個函數:(在OSAL_Nv.h中聲明)

1    void osal_nv_init( void *p );

2    uint8 osal_nv_item_init( uint16 id, uint16 len, void *buf );

3    uint8 osal_nv_read( uint16 id, uint16 offset, uint16 len, void *buf );

4    uint8 osal_nv_write( uint16 id, uint16 offset, uint16 len, void *buf );

5    uint16 osal_nv_item_len( uint16 id );

第1個函數在系統初始化的時候被調用,我們在應用程序中不用管!

第2個函數是我們在使用Nv時,初始化某個條目,如osal_nv_item_init(TEST_NV,1,NULL);

第3個函數是Nv讀取某一個條目的數據,將其存儲在buf中

第4個函數創建一個Nv條目(如果條目的ID不存在,如果存在,就將原來的item數據部分覆蓋),並向其中寫入數據

第5個函數是查詢某一個item的數據長度。

真正我們使用的是第2~4個函數。使用如下:

void App_osal_NV_test( void )
{
  struct nv_test{
    uint8 Mgic;//魔數
    uint8 nv_origin_data[10];
  }nv_test;

  osal_nv_read(ZCD_NV_APP_TEST1,0,sizeof(nv_test),&nv_test);
  if( nv_test.Mgic == 0x8b ){//說明數據已經寫過
    HalUARTWrite(0,nv_test.nv_origin_data,10);
  } else {
    osal_nv_item_init(ZCD_NV_APP_TEST1,sizeof(nv_test),NULL);
    nv_test.Mgic = 0x8b;
    strcpy(nv_test.nv_origin_data,"123568974");
    osal_nv_write(ZCD_NV_APP_TEST1,0,sizeof(nv_test),&nv_test);
  }
}

記住在write之前必須要初始化item,即調用osal_nv_item_init函數

下面我們打開OSAL_Nv.c源文件,通過分析源代碼,就知道Z-STACK是如何抽象的封裝出以上幾個API,這對我們以後寫程序還是很有幫助的!

在解讀源碼之前,必須要知道存儲Nv條目的6個page如何存儲Nv的,即其item在page中的結構和佈局!

首先每一個page都有一個osalNvPgHdr_t結構體的頭

typedef struct
{
  uint16 active;
  uint16 inUse;
  uint16 xfer;
  uint16 spare;
} osalNvPgHdr_t;     其中的幾個成員稍後在做解釋!

在這8個字節的page頭部之後纔是item的存儲位置。而每一個item都有一個8字節的頭部

typedef struct
{
  uint16 id;
  uint16 len;   // Enforce Flash-WORD size on len.
  uint16 chk;   // Byte-wise checksum of the 'len' data bytes of the item.
  uint16 stat;  // Item status.
} osalNvHdr_t;    從後面註釋就知道了每一個成員變量的含義

然後我們還必須得知道幾個全局變量和數組的含義:

OSAL_NV_PAGES_USED值爲6,即6個page

uint16 pgOff[OSAL_NV_PAGES_USED];

Offset into the page of the first available erased space.  每一個page的可用數據的偏移量

uint16 pgLost[OSAL_NV_PAGES_USED];

Count of the bytes lost for the zeroed-out items.  爲0數據的item的字節

uint8 pgRes;

Page reserved for item compacting transfer.     item 壓縮傳輸的 保留page

uint8 findPg;

Saving ~100 code bytes to move a uint8* parameter/return value from findItem() to a global.

用一個全局變量能節省100字節的空間,指示某一個item對應的page

uint8 failF;  這個變量最用最後再解釋!

在系統初始的時候調用osal_nv_init函數,它有調用initNV()函數,這個函數的作用就是初始化NV flash page,那在初始化中都做了什麼呢?

for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
  {
    HalFlashRead(pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr), OSAL_NV_HDR_SIZE);

    if ( pgHdr.active == OSAL_NV_ERASED_ID )
    {
      if ( pgRes == OSAL_NV_PAGE_NULL )
      {
        pgRes = pg;
      }
      else
      {
        setPageUse( pg, TRUE );
      }
    }
    else  // Page is active.
    {
      // If the page is not yet in use, it is the tgt of items from an xfer.
      if ( pgHdr.inUse == OSAL_NV_ERASED_ID )
      {
        newPg = pg;
      }
      // An Xfer from this page was in progress.
      else if ( pgHdr.xfer != OSAL_NV_ERASED_ID )
      {
        oldPg = pg;
      }
    }

    // Calculate page offset and lost bytes - any "old" item triggers an N^2 re-scan from start.
    if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL )
    {
      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;
    }

}

先看看這個for循環,循環每一個page,然後讀取其page頭部存儲在pgHdr中,①如果其active成員爲
OSAL_NV_ERASED_ID(0xFFFF),表示此page還沒有被激活(想想我們的flash中沒寫的數據每一位爲1,一字節就爲0xFF,active佔2個字節)。如果此頁沒有激活,且此時pgRes爲OSAL_NV_PAGE_NULL(0),則我們不激活此page,而是將此頁作爲後面壓縮的保留頁,如果pgRes不爲0,即已經有了保留頁,則將此page激活,且使此頁投入以後使用中,調用setPageUse( pg, TRUE );我們看看這個函數

osalNvPgHdr_t pgHdr;

  pgHdr.active = OSAL_NV_ZEROED_ID;

  if ( inUse )
  {
    pgHdr.inUse = OSAL_NV_ZEROED_ID;
  }
  else
  {
    pgHdr.inUse = OSAL_NV_ERASED_ID;
  }

  writeWord( pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8*)(&pgHdr) );

調用此函數激活page,即使active爲OSAL_NV_ZEROED_ID爲0x0000,如果inUse爲TRUE,則置其inUse爲OSAL_NV_ZEROED_ID(0x0000),表示此頁投入使用中!否則置爲OSAL_NV_ERASED_ID(0xFFFF),表示棄用該頁!最後調用writeWord,將pgHdr頭寫進page的頭部位置!

①(與上面的①對應,表示if和else)

如果該page 的active爲OSAL_NV_ZEROED_ID(0x0000),此page 爲激活狀態,此時檢查此page是否投入使用中,如果其inUse爲OSAL_NV_ERASED_ID(0xFFFF),即沒有投入到使用中,那麼If the page is not yet in use, it is the tgt of items from an xfer.//將其作爲後面壓縮傳輸的目標,即使newPg = pg;

如果此頁的xfer不爲OSAL_NV_ERASED_ID(0xFFFF),表明其處於Xfer的過程中,(有時候機器意外斷電,而此時剛好有page在Xfer過程,那麼page的xfer位就爲非0xFFFF,即0x0000)。這個時候 我們使 oldPg = pg;

然後調用了initPage( pg, OSAL_NV_ITEM_NULL, findDups ),這個函數有什麼用呢?我們先看其代碼:

static uint16 initPage( uint8 pg, uint16 id, uint8 findDups )
{
  uint16 offset = OSAL_NV_PAGE_HDR_SIZE;
  uint16 sz, lost = 0;
  osalNvHdr_t hdr;

  do
  {
    HalFlashRead(pg, offset, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE);

    if ( hdr.id == OSAL_NV_ERASED_ID )
    {
      break;
    }
    offset += OSAL_NV_HDR_SIZE;
    sz = OSAL_NV_DATA_SIZE( hdr.len );

      if ( (offset + sz) > OSAL_NV_PAGE_FREE )
    {
      lost += (OSAL_NV_PAGE_FREE - offset + OSAL_NV_HDR_SIZE);
      offset = OSAL_NV_PAGE_FREE;
      break;
    }

    if ( hdr.id != OSAL_NV_ZEROED_ID )
    {
      if ( id != OSAL_NV_ITEM_NULL )
      {
         if ( (id & 0x7fff) == hdr.id )
        {
          if ( (((id & OSAL_NV_SOURCE_ID) == 0) && (hdr.stat == OSAL_NV_ERASED_ID)) ||
               (((id & OSAL_NV_SOURCE_ID) != 0) && (hdr.stat != OSAL_NV_ERASED_ID)) )
          {
            return offset;
          }
        }
      }
      else
      {
        if ( hdr.chk == calcChkF( pg, offset, hdr.len ) )
        {
          if ( findDups )
          {
            if ( hdr.stat == OSAL_NV_ERASED_ID )
            {
               uint16 off = findItem( (hdr.id | OSAL_NV_SOURCE_ID) );

              if ( off != OSAL_NV_ITEM_NULL )
              {
                setItem( findPg, off, eNvZero );  // Mark old duplicate as invalid.
              }
            }
          }
          else if ( hdr.stat != OSAL_NV_ERASED_ID )
          {
            return OSAL_NV_ERASED_ID;
          }
        }
        else
        {
          setItem( pg, offset, eNvZero );  // Mark bad checksum as invalid.
          lost += (OSAL_NV_HDR_SIZE + sz);
        }
      }
    }
    else
    {
      lost += (OSAL_NV_HDR_SIZE + sz);
    }
    offset += sz;

  } while ( TRUE );

  pgOff[pg - OSAL_NV_PAGE_BEG] = offset;
  pgLost[pg - OSAL_NV_PAGE_BEG] = lost;

  return OSAL_NV_ITEM_NULL;
}

代碼有點長!其實這個函數的最用通過註釋就知道了,Walk the page items; calculate checksums, lost bytes & page offset. 對於某個page,逐個item地計算其checksums,lost bytes,然後計算page offset!再看下其返回值

If ‘id’ is non-NULL and good checksums are found, return the offset of the data corresponding to item Id; else OSAL_NV_ITEM_NULL. 如果id值不爲0,且校驗和正確就返回和此item的數據的偏移量,否則返回OSAL_NV_ITEM_NULL(0)

那麼在initNV的for循環中

if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL )
    {
      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;
    }

這個if語句幹什麼的呢?知道了initPage的返回值,不難理解其用途!如果if爲真,即initPage返回的值爲OSAL_NV_ERASED_ID(0xFFFF)

initPage執行到下面一句

else if ( hdr.stat != OSAL_NV_ERASED_ID )
{
            return OSAL_NV_ERASED_ID;
}

此時Any “old” item immediately exits and triggers the N^2 exhaustive initialization.爲什麼呢?因爲如果是id爲0,那麼該處的hdr.stat值應該爲0xFFFF,如果某種意外情況導致其不爲0xFFFF,則說明出了問題,得重新去初始化所有的item(即檢查他們的頭部)

迴歸到上面,如果initPage返回值爲OSAL_NV_ERASED_ID(0xFFFF),則

      findDups = TRUE;
      pg = OSAL_NV_PAGE_BEG-1;
      continue;

置findDups爲TRUE,那麼在下次調用initPage的時候就會去初始化所有item,然後pg =OSAL_NV_PAGE_BEG-1

for循環從開頭執行! 這就是for循環中的代碼,重要的是記住newPg 和oldPg ;

接下來

if ( newPg != OSAL_NV_PAGE_NULL )
  {
     if ( pgRes != OSAL_NV_PAGE_NULL )
    {
      setPageUse( newPg, TRUE );
    }
    else if ( oldPg != OSAL_NV_PAGE_NULL )
    {
      pgRes = newPg;
    }

     if ( oldPg != OSAL_NV_PAGE_NULL )
    {
      compactPage( oldPg );
    }

}

newPage保存的是inUse爲OSAL_NV_ERASED_ID(0xFFFF)即還沒有投入使用中的頁,如果有這樣的page,我們再進行下一步判斷pgRes,如果其值不爲OSAL_NV_PAGE_NULL,即保留了某一個page爲compact xfer page。

這個時候調用setPageUse( newPg, TRUE );即使其inUse爲OSAL_NV_ZEROED_ID(0x0000),此頁將投入使用中。如果pgReg爲OSAL_NV_PAGE_NULL(此時所有的page均激活了),且某一頁其xfer爲OSAL_NV_ZEROED_ID,其保存在oldPg中,此時們將newPg 賦值給pgRes,即將newPg作爲compact的保留page(此時newPg沒有投入使用中),接下來如果oldPg中保存了xfer被打斷了的page,則調用compactPage( oldPg ),將其進行壓縮!

有這段註釋:

/* If a page compaction was interrupted and the page being compacted is not
     * yet erased, then there may be items remaining to xfer before erasing.
     */

看下這個函數代碼:

static void compactPage( uint8 srcPg )
{
  uint16 dstOff = pgOff[pgRes-OSAL_NV_PAGE_BEG];
  uint16 srcOff = OSAL_NV_ZEROED_ID;
  osalNvHdr_t hdr;
  writeWordH( srcPg, OSAL_NV_PG_XFER, (uint8*)(&srcOff) );

  srcOff = OSAL_NV_PAGE_HDR_SIZE;

  do
  {
    uint16 sz;
    HalFlashRead(srcPg, srcOff, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE);

    if ( hdr.id == OSAL_NV_ERASED_ID )
    {
      break;
    }

    srcOff += OSAL_NV_HDR_SIZE;

    if ( (srcOff + hdr.len) > OSAL_NV_PAGE_FREE )
    {
      break;
    }

    sz = OSAL_NV_DATA_SIZE( hdr.len );

    if ( hdr.id != OSAL_NV_ZEROED_ID )
    {
      if ( hdr.chk == calcChkF( srcPg, srcOff, hdr.len ) )
      {
        setItem( srcPg, srcOff, eNvXfer );
        writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) );
        dstOff += OSAL_NV_HDR_SIZE;
        xferBuf( srcPg, srcOff, pgRes, dstOff, sz );
        dstOff += sz;
      }

      setItem( srcPg, srcOff, eNvZero );  // Mark old location as invalid.
    }

    srcOff += sz;

  } while ( TRUE );

  pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff;
  erasePage( srcPg );

  setPageUse( pgRes, TRUE );
  pgRes = srcPg;
}

首先 Mark page as being in process of compaction. 標誌該頁正在壓縮處理中!

然後依次讀取srcPg中的每一個item,然後對每一個item進行處理,處理過程如下:

1,如果item的id不爲OSAL_NV_ZEROED_ID(0x0000),如果id爲0x0000,則直接跳到步驟4

對其進行和校驗,如果正確的話轉下一步,如果不正確轉到步驟3

2,調用setItem( srcPg, srcOff, eNvXfer );設置item 的狀態位爲激活狀態,即使其stat位爲OSAL_NV_ACTIVE(0x00),然後調用writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) );將該item頭部八字節寫進pgRes頁的dstOff處,此頁爲保留頁,記住此時我們已經從前面的步驟中劃分出了一個page爲pgRes。最後調用xferBuf( srcPg, srcOff, pgRes, dstOff, sz );將該item的數據部分從srcPg中轉移到pgRes中,其中sz爲item的數據長度。轉下一步

3,調用setItem( srcPg, srcOff, eNvZero );標記srcPg中這些被轉移的item爲invalid,即將他們的id全部置0,函數中最後調整了pgLost數組中該page的lost bytes,即爲該item的數據長度!

4,調整srcOff, srcOff += sz;即指向下一個srcPg的item。

經過上述步驟,就處理完了srcPg中的所有item,將他們都轉移到pgRes中,其實就是壓縮的是其中那些id爲0x0000的item。

pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff;調整pgRes的pgOff;

erasePage( srcPg );擦出被compact的page,


setPageUse( pgRes, TRUE );   // Mark the reserve page as being in use. 

pgRes = srcPg;  // Set the reserve page to be the newly erased page.

這樣compactPage就完成了,還記得它前後完成的工作吧!
繼續回到initNV函數最後一個if語句:

if ( pgRes == OSAL_NV_PAGE_NULL )
  {
    for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
    {
      erasePage( pg );
    }
    initNV();
  } 

/* If no page met the criteria to be the reserve page:
   *  - A compactPage() failed or board reset before doing so.
   *  - Perhaps the user changed which Flash pages are dedicated to NV and downloaded the code
   *    without erasing Flash?
   */

如果沒有一個page滿足“標準”稱爲the reserve page 那麼將所有Nv page擦出掉,然後重新初始化NV。

至此initNV()函數完成!
來源: http://feibit.com/thread-10159-1-1.html

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