本文主要是介绍Z-STACK之OSAL_Nv非易失性存储解读上,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、在工程选项设置里的PreprocessoràDefined symbols里添加了NV_INIT和,xNV_INIT (保存设备基本信息到非易失性存储器里)NV_RESTORE,xNV_RESTORE(不保存网络状态信息),(前面加x代表不保存);在这里要需要搞明白的是此处的网络状态信息是具体指哪一类的信息,是否与设备类型有关呢?
REFLECTOR:是实现绑定功能,在ZDobject.c里有体现如下:
#if defined ( REFLECTOR )
/*********************************************************************
* @fn ZDO_RemoveEndDeviceBind
*
* @brief Remove the end device bind
*
* @param none
*
* @return none
*/
static void ZDO_RemoveEndDeviceBind( void )
{;;;;}
2、在调用函数时往往会#include头文件,前提是在工程里要有这个头文件,所以还要知道在文件组里添加头文件,这样才能#include。
3、设备信息的配置在sample.h里,比如profile的ID,设备ID和版本,端口,命令,端口描述符。
应用的非易失性存储器(Application Non-Volatile Memory)
NV同样可以用来保存应用程序的特定信息,用户描述符就是一个很好的例子。NV中用户描述符ID项是ZDO_NV_USERDESC(在ZComDef.h中定义)。
在ZDApp_Init()函数中,调用函数osal_nv_item_init()(这个函数在osal_Nv.c里)来初始化用户描述符所需要的NV空间。如果这个针对这个NV项,这个函数是第一次调用,这个初始化函数将为用户描述符保留空间,并且将它设置为默认值ZDO_DefaultUserDesc- riptor。
当需要使用保存在NV中的用户描述符时,就ZDO_ProcessUserDescReq()(在ZDObject.c中)函数一样,调用osal_nv_read()函数从NV中获取用户描述符。
如果要更新NV中的用户描述符,就像ZDO_ProcessUserDescSet()(在ZDObject.c中)函数一样,调用osal_nv_write()函数更新NV中的用户描述符。
记住:NV中的项都是独一无二的。如果用户应用程序要创建自己的NV项,那么必须从应用值范围0x0201~0x0FFF中选择ID。
本章解读Z-STACK中关于Nv操作的源码,以及z-stack中Nv的使用!
在Z-STACK中Nv存储器主要用于保存网络的配置参数,如网络地址,使 系统在掉电重启仍然能读取一些参数,自动加入到原来的网络中,这样其网络地址没有变化!
注意:一般不要轻易的写NV,并且在写NV过程中,如果有大量数据,最好将接收机关掉,写Nv是件非常耗能量的动作。
在z-stack中,每一个参数的配置对应的是一个Nv条目(item),每一个item都有自己的ID,z-stack中使用的条目ID范围如下:
0×0000 保留
0×0001~0×0020 操作系统抽象层(OSAL)
0×0021~0×0040 网络层(NWK)
0×0041~0×0060 应用程序支持子层(APS)
0×0061~0×0080 安全(Security)
0×0081~0x00A0 Zigbee设备对象(ZDO)
0x00A1~0×0200 保留
0×0201~0x0FFF 应用程序
0×1000~0xFFFF 保留
0x0000 Reserved
0x0001 – 0x0020 OSAL
0x0021 – 0x0040 NWK
0x0041 – 0x0060 APS
0x0061 – 0x0080 Security
0x0081 – 0x00B0 ZDO
0x00B1 – 0x00E0 Commissioning SAS (调试用的)
0x00E1 – 0x0100 Reserved
0x0101 – 0x01FF Trust Center Link Keys (秘钥)
0x0201 – 0x0300 ZigBee-Pro: APS Links Keys
ZigBee-RF4CE: network layer
0x0301 – 0x0400 ZigBee-Pro: Master Keys
ZigBee-RF4CE: app framework
0x0401 – 0x0FFF Application (应用层,用来给我们自己使用)
0x1000 -0xFFFF Reserved
如果是我们自己的应用程序中需要使用Nv,则定义其ID在0×0201~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中,其中id 是每一个我们定义好的id号,这是独立唯一的,offerst是偏移量,一般是0
第4个函数创建一个Nv条目(如果条目的ID不存在,如果存在,就将原来的item数据部分覆盖),并向其中写入数据
第5个函数是查询某一个item的数据长度。
真正我们使用的是第2~4个函数。使用如下:
unsigned char value_read;
unsigned char value = 0×18;
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的值便是0×18,记住在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为0×0000,如果inUse为TRUE,则置其inUse为OSAL_NV_ZEROED_ID(0×0000),表示此页投入使用中!否则置为OSAL_NV_ERASED_ID(0xFFFF),表示弃用该页!最后调用writeWord,将pgHdr头写进page的头部位置!
①(与上面的①对应,表示if和else)
如果该page 的active为OSAL_NV_ZEROED_ID(0×0000),此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,即0×0000)。这个时候 我们使 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(0×0000),此页将投入使用中。如果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(0×0000),如果id为0×0000,则直接跳到步骤4
对其进行和校验,如果正确的话转下一步,如果不正确转到步骤3
2,调用setItem( srcPg, srcOff, eNvXfer );设置item 的状态位为激活状态,即使其stat位为OSAL_NV_ACTIVE(0×00),然后调用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为0×0000的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非易失性存储解读上的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!