[置顶] Z-STACK之OSAL_Nv非易失性存储解读上
????? 本章解讀Z-STACK中關于Nv操作的源碼,以及z-stack中Nv的使用!
????? 在Z-STACK中Nv存儲器主要用于保存網絡的配置參數,如網絡地址,使 系統在掉電重啟仍然能讀取一些參數,自動加入到原來的網絡中,這樣其網絡地址沒有變化!
???? 在z-stack中,每一個參數的配置對應的是一個Nv條目(item),每一個item都有自己的ID,z-stack中使用的條目ID范圍如下:
??? 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個函數。使用如下:
unsigned char value_read;
unsigned char value = 0x18;
osal_nv_item_init(TEST_NV,1,NULL);//NULL表示初始化的時候,item數據部分為空
osal_nv_item_write(TEST_NV,0,1,&value);
osal_nv_item_read(TEST_NV,0,1,&value_read);
value_read的值便是0x18,記住在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()函數完成!
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
???
?
?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的[置顶] Z-STACK之OSAL_Nv非易失性存储解读上的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj 3067
- 下一篇: Ural 1627 Join(生成树计数