[置顶] Z-STACK之OSAL_Nv非易失性存储解读上
????? 本章解讀Z-STACK中關(guān)于Nv操作的源碼,以及z-stack中Nv的使用!
????? 在Z-STACK中Nv存儲(chǔ)器主要用于保存網(wǎng)絡(luò)的配置參數(shù),如網(wǎng)絡(luò)地址,使 系統(tǒng)在掉電重啟仍然能讀取一些參數(shù),自動(dòng)加入到原來的網(wǎng)絡(luò)中,這樣其網(wǎng)絡(luò)地址沒有變化!
???? 在z-stack中,每一個(gè)參數(shù)的配置對(duì)應(yīng)的是一個(gè)Nv條目(item),每一個(gè)item都有自己的ID,z-stack中使用的條目ID范圍如下:
??? 0x0000????????????????????????????? 保留
??? 0x0001~0x0020????????????? 操作系統(tǒng)抽象層(OSAL)
??? 0x0021~0x0040????????????? 網(wǎng)絡(luò)層(NWK)
????0x0041~0x0060????????????? 應(yīng)用程序支持子層(APS)
??? 0x0061~0x0080????????????? 安全(Security)
??? 0x0081~0x00A0???????????? Zigbee設(shè)備對(duì)象(ZDO)
??? 0x00A1~0x0200???????????? 保留
??? 0x0201~0x0FFF????????????? 應(yīng)用程序
??? 0x1000~0xFFFF????????????? 保留
? 如果是我們自己的應(yīng)用程序中需要使用Nv,則定義其ID在0x0201~0x0FFF?范圍內(nèi)!
Z-STACK真正提供給用戶使用的是五個(gè)函數(shù):(在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個(gè)函數(shù)在系統(tǒng)初始化的時(shí)候被調(diào)用,我們?cè)趹?yīng)用程序中不用管!
第2個(gè)函數(shù)是我們?cè)谑褂肗v時(shí),初始化某個(gè)條目,如osal_nv_item_init(TEST_NV,1,NULL);
第3個(gè)函數(shù)是Nv讀取某一個(gè)條目的數(shù)據(jù),將其存儲(chǔ)在buf中
第4個(gè)函數(shù)創(chuàng)建一個(gè)Nv條目(如果條目的ID不存在,如果存在,就將原來的item數(shù)據(jù)部分覆蓋),并向其中寫入數(shù)據(jù)
第5個(gè)函數(shù)是查詢某一個(gè)item的數(shù)據(jù)長度。
?真正我們使用的是第2~4個(gè)函數(shù)。使用如下:
unsigned char value_read;
unsigned char value = 0x18;
osal_nv_item_init(TEST_NV,1,NULL);//NULL表示初始化的時(shí)候,item數(shù)據(jù)部分為空
osal_nv_item_write(TEST_NV,0,1,&value);
osal_nv_item_read(TEST_NV,0,1,&value_read);
value_read的值便是0x18,記住在write之前必須要初始化item,即調(diào)用osal_nv_item_init函數(shù)
?
下面我們打開OSAL_Nv.c源文件,通過分析源代碼,就知道Z-STACK是如何抽象的封裝出以上幾個(gè)API,這對(duì)我們以后寫程序還是很有幫助的!
?
在解讀源碼之前,必須要知道存儲(chǔ)Nv條目的6個(gè)page如何存儲(chǔ)Nv的,即其item在page中的結(jié)構(gòu)和布局!
首先每一個(gè)page都有一個(gè)osalNvPgHdr_t結(jié)構(gòu)體的頭
typedef struct
{
? uint16 active;
? uint16 inUse;
? uint16 xfer;
? uint16 spare;
} osalNvPgHdr_t;???? 其中的幾個(gè)成員稍后在做解釋!
在這8個(gè)字節(jié)的page頭部之后才是item的存儲(chǔ)位置。而每一個(gè)item都有一個(gè)8字節(jié)的頭部
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;??? 從后面注釋就知道了每一個(gè)成員變量的含義
?
然后我們還必須得知道幾個(gè)全局變量和數(shù)組的含義:OSAL_NV_PAGES_USED值為6,即6個(gè)page
uint16 pgOff[OSAL_NV_PAGES_USED];
Offset into the page of the first available erased space.? 每一個(gè)page的可用數(shù)據(jù)的偏移量
uint16 pgLost[OSAL_NV_PAGES_USED];
?Count of the bytes lost for the zeroed-out items.? 為0數(shù)據(jù)的item的字節(jié)
uint8 pgRes;
Page reserved for item compacting transfer.???? item 壓縮傳輸?shù)?保留page
uint8 findPg;
Saving ~100 code bytes to move a uint8* parameter/return value from findItem() to a global.
用一個(gè)全局變量能節(jié)省100字節(jié)的空間,指示某一個(gè)item對(duì)應(yīng)的page
uint8 failF;??這個(gè)變量最用最后再解釋!
?
?
在系統(tǒng)初始的時(shí)候調(diào)用osal_nv_init函數(shù),它有調(diào)用initNV()函數(shù),這個(gè)函數(shù)的作用就是初始化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;
??? }
}
先看看這個(gè)for循環(huán),循環(huán)每一個(gè)page,然后讀取其page頭部存儲(chǔ)在pgHdr中,①如果其active成員為
OSAL_NV_ERASED_ID(0xFFFF),表示此page還沒有被激活(想想我們的flash中沒寫的數(shù)據(jù)每一位為1,一字節(jié)就為0xFF,active占2個(gè)字節(jié))。如果此頁沒有激活,且此時(shí)pgRes為OSAL_NV_PAGE_NULL(0),則我們不激活此page,而是將此頁作為后面壓縮的保留頁,如果pgRes不為0,即已經(jīng)有了保留頁,則將此page激活,且使此頁投入以后使用中,調(diào)用setPageUse( pg, TRUE );我們看看這個(gè)函數(shù)
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) );
調(diào)用此函數(shù)激活page,即使active為OSAL_NV_ZEROED_ID為0x0000,如果inUse為TRUE,則置其inUse為OSAL_NV_ZEROED_ID(0x0000),表示此頁投入使用中!否則置為OSAL_NV_ERASED_ID(0xFFFF),表示棄用該頁!最后調(diào)用writeWord,將pgHdr頭寫進(jìn)page的頭部位置!
?
①(與上面的①對(duì)應(yīng),表示if和else)
如果該page 的active為OSAL_NV_ZEROED_ID(0x0000),此page 為激活狀態(tài),此時(shí)檢查此page是否投入使用中,如果其inUse為OSAL_NV_ERASED_ID(0xFFFF),即沒有投入到使用中,那么If the page is not yet in use, it is the tgt of items from an xfer.//將其作為后面壓縮傳輸?shù)哪繕?biāo),即使newPg = pg;
?
如果此頁的xfer不為OSAL_NV_ERASED_ID(0xFFFF),表明其處于Xfer的過程中,(有時(shí)候機(jī)器意外斷電,而此時(shí)剛好有page在Xfer過程,那么page的xfer位就為非0xFFFF,即0x0000)。這個(gè)時(shí)候 我們使?oldPg = pg;
?
然后調(diào)用了initPage( pg, OSAL_NV_ITEM_NULL, findDups ),這個(gè)函數(shù)有什么用呢?我們先看其代碼:
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;
}
代碼有點(diǎn)長!其實(shí)這個(gè)函數(shù)的最用通過注釋就知道了,Walk the page items; calculate checksums, lost bytes & page offset. 對(duì)于某個(gè)page,逐個(gè)item地計(jì)算其checksums,lost bytes,然后計(jì)算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,且校驗(yàn)和正確就返回和此item的數(shù)據(jù)的偏移量,否則返回OSAL_NV_ITEM_NULL(0)
那么在initNV的for循環(huán)中
?if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL )
??? {
????? findDups = TRUE;
????? pg = OSAL_NV_PAGE_BEG-1;
????? continue;
??? }
這個(gè)if語句干什么的呢?知道了initPage的返回值,不難理解其用途!如果if為真,即initPage返回的值為OSAL_NV_ERASED_ID(0xFFFF)
initPage執(zhí)行到下面一句
else if ( hdr.stat != OSAL_NV_ERASED_ID )
{
??????????? return OSAL_NV_ERASED_ID;
}
此時(shí)Any "old" item immediately exits and triggers the N^2 exhaustive initialization.為什么呢?因?yàn)槿绻莍d為0,那么該處的hdr.stat值應(yīng)該為0xFFFF,如果某種意外情況導(dǎo)致其不為0xFFFF,則說明出了問題,得重新去初始化所有的item(即檢查他們的頭部)
?
回歸到上面,如果initPage返回值為OSAL_NV_ERASED_ID(0xFFFF),則
????? findDups = TRUE;
????? pg = OSAL_NV_PAGE_BEG-1;
????? continue;
置findDups為TRUE,那么在下次調(diào)用initPage的時(shí)候就會(huì)去初始化所有item,然后pg =OSAL_NV_PAGE_BEG-1
for循環(huán)從開頭執(zhí)行! 這就是for循環(huán)中的代碼,重要的是記住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,我們?cè)龠M(jìn)行下一步判斷pgRes,如果其值不為OSAL_NV_PAGE_NULL,即保留了某一個(gè)page為compact xfer page。
這個(gè)時(shí)候調(diào)用setPageUse( newPg, TRUE );即使其inUse為OSAL_NV_ZEROED_ID(0x0000),此頁將投入使用中。如果pgReg為OSAL_NV_PAGE_NULL(此時(shí)所有的page均激活了),且某一頁其xfer為OSAL_NV_ZEROED_ID,其保存在oldPg中,此時(shí)們將newPg 賦值給pgRes,即將newPg作為compact的保留page(此時(shí)newPg沒有投入使用中),接下來如果oldPg中保存了xfer被打斷了的page,則調(diào)用compactPage( oldPg ),將其進(jìn)行壓縮!
有這段注釋:
/* 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.
???? */
?
看下這個(gè)函數(shù)代碼:
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. 標(biāo)志該頁正在壓縮處理中!
然后依次讀取srcPg中的每一個(gè)item,然后對(duì)每一個(gè)item進(jìn)行處理,處理過程如下:
1,如果item的id不為OSAL_NV_ZEROED_ID(0x0000),如果id為0x0000,則直接跳到步驟4
對(duì)其進(jìn)行和校驗(yàn),如果正確的話轉(zhuǎn)下一步,如果不正確轉(zhuǎn)到步驟3
2,調(diào)用setItem( srcPg, srcOff, eNvXfer );設(shè)置item 的狀態(tài)位為激活狀態(tài),即使其stat位為OSAL_NV_ACTIVE(0x00),然后調(diào)用writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) );將該item頭部八字節(jié)寫進(jìn)pgRes頁的dstOff處,此頁為保留頁,記住此時(shí)我們已經(jīng)從前面的步驟中劃分出了一個(gè)page為pgRes。最后調(diào)用xferBuf( srcPg, srcOff, pgRes, dstOff, sz );將該item的數(shù)據(jù)部分從srcPg中轉(zhuǎn)移到pgRes中,其中sz為item的數(shù)據(jù)長度。轉(zhuǎn)下一步
3,調(diào)用setItem( srcPg, srcOff, eNvZero );標(biāo)記srcPg中這些被轉(zhuǎn)移的item為invalid,即將他們的id全部置0,函數(shù)中最后調(diào)整了pgLost數(shù)組中該page的lost bytes,即為該item的數(shù)據(jù)長度!
4,調(diào)整srcOff, srcOff += sz;即指向下一個(gè)srcPg的item。
經(jīng)過上述步驟,就處理完了srcPg中的所有item,將他們都轉(zhuǎn)移到pgRes中,其實(shí)就是壓縮的是其中那些id為0x0000的item。
?
?pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff;調(diào)整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就完成了,還記得它前后完成的工作吧!
?
?
繼續(xù)回到initNV函數(shù)最后一個(gè)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?
?? */
如果沒有一個(gè)page滿足“標(biāo)準(zhǔn)”稱為the reserve page 那么將所有Nv page擦出掉,然后重新初始化NV。
至此initNV()函數(shù)完成!
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
???
?
?
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的[置顶] Z-STACK之OSAL_Nv非易失性存储解读上的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj 3067
- 下一篇: Ural 1627 Join(生成树计数