【轉載】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