查看: 17429|回复: 11

研究 Z-Stack 中ZigBee 设备的 IEEE 地址

[复制链接]
一月的萧邦 发表于 2012-9-11 23:20:33 | 显示全部楼层 |阅读模式
本帖最后由 一月的萧邦 于 2013-1-4 12:56 编辑

    对于IEEE的地址,通过看理论的书籍感觉有点懂了,但是还没有真正实践,借此机会好好整理一下最近学习ZigBee IEEE地址的知识。
    下面将结合具体的Z-Stack(2.5.1.a版本)分享一下学习心得。
     首先来看一下 TI 官方的一个文档,位于Z-stack 目录下的 document -> CC2530 -> Z-Stack User's Guide - CC2530DB.pdf   从目录中可以看到 4.4 章节讲的就是 IEEE地址。
     
   4.4大致说了:CC2530芯片在TI出厂时已经预先烧写了 Primary IEEE address,这个64位地址是全球唯一的。这个地址在CC2530的FLASH信息页中,是只读的。但是用户貌似可以重新写这个预先烧写的Primary IEEE address。具体再来看看 Section 7.2
  
   7.2中说Z-STACK通过4个步骤来确定设备的IEEE地址:
        1.从Z-stack的NV中读取
        2.从Second IEEE 的位置中寻找
        3.在Primary IEEE 的位置寻找
        4.由随机数产生器产生一个临时IEEE地址。
   也就是说,ZigBee设备在上电后,首先会从NV中读取IEEE地址,如果读取失败,则从FLASH的Second IEEE 的存放位置读取IEEE地址,如果读取失败,则再从Primary IEEE 的存放位置读取IEEE,如果还是失败,则由随机数发生器产生一个临时IEEE地址。步骤2或者步骤3一旦有效并且使能了“NV_RESTORE”,就会把这个IEEE地址写入到NV中去。这样下次上电的时候,就可以通过步骤1从NV中直接读取 IEEE地址。
    接下来我们再来看看每一个步骤具体是怎么实现的:

Step1 从Z-stack的NV中读取 IEEE地址: 源代码见 Z-stack 2.5.1.a --> ZMain.c --> main( ) -->  zmain_ext_addr()


我将源代码拿上来,自己添加了注释。
  1. static void zmain_ext_addr(void)
  2. {
  3.   uint8 nullAddr[Z_EXTADDR_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
  4.   uint8 writeNV = TRUE;

  5.   // First check whether a non-erased extended address exists in the OSAL NV.
  6.   if ((SUCCESS != osal_nv_item_init(ZCD_NV_EXTADDR, Z_EXTADDR_LEN, NULL))  ||         //如果NV初始化失败
  7.       (SUCCESS != osal_nv_read(ZCD_NV_EXTADDR, 0, Z_EXTADDR_LEN, aExtendedAddress)) ||//或者NV读取失败
  8.       (osal_memcmp(aExtendedAddress, nullAddr, Z_EXTADDR_LEN)))                       //或者NV没有被写过
  9.   {
  10.     // Attempt to read the extended address from the location on the lock bits page
  11.     // where the programming tools know to reserve it.
  12.     // 若NV读取IEEE地址失败,则从Secondary IEEE中读取,在FLASH最后的偏移24个地址的地方,即0x3FFE8~0x3FFEF
  13.     HalFlashRead(HAL_FLASH_IEEE_PAGE, HAL_FLASH_IEEE_OSET, aExtendedAddress, Z_EXTADDR_LEN);

  14.     if (osal_memcmp(aExtendedAddress, nullAddr, Z_EXTADDR_LEN)) //如果Secondary IEEE 也是无效的则读取Primary IEEE
  15.     {
  16.       // Attempt to read the extended address from the designated location in the Info Page.
  17.       // Primary IEEE 在信息页偏移0x0c-0x13的地方,即(0x7800+0x0c)=0x780c,这个地址是映射到Xdata上的,不是CODE地址
  18.       if (!osal_memcmp((uint8 *)(P_INFOPAGE+HAL_INFOP_IEEE_OSET), nullAddr, Z_EXTADDR_LEN))
  19.       {
  20.         osal_memcpy(aExtendedAddress, (uint8 *)(P_INFOPAGE+HAL_INFOP_IEEE_OSET), Z_EXTADDR_LEN);
  21.       }
  22.       else  // No valid extended address was found.  如果Primary IEEE 还是无效,则产生随机地址,随机地址以0xF8开头
  23.       {
  24.         uint8 idx;

  25. #if !defined ( NV_RESTORE )
  26.         writeNV = FALSE;  // Make this a temporary IEEE address
  27. #endif

  28.         /* Attempt to create a sufficiently random extended address for expediency.
  29.          * Note: this is only valid/legal in a test environment and
  30.          *       must never be used for a commercial product.
  31.          */
  32.         for (idx = 0; idx < (Z_EXTADDR_LEN - 2);)
  33.         {
  34.           uint16 randy = osal_rand();
  35.           aExtendedAddress[idx++] = LO_UINT16(randy);
  36.           aExtendedAddress[idx++] = HI_UINT16(randy);
  37.         }
  38.         // Next-to-MSB identifies ZigBee devicetype.
  39. #if ZG_BUILD_COORDINATOR_TYPE && !ZG_BUILD_JOINING_TYPE
  40.         aExtendedAddress[idx++] = 0x10;
  41. #elif ZG_BUILD_RTRONLY_TYPE
  42.         aExtendedAddress[idx++] = 0x20;
  43. #else
  44.         aExtendedAddress[idx++] = 0x30;
  45. #endif
  46.         // MSB has historical signficance.
  47.         aExtendedAddress[idx] = 0xF8;
  48.       }
  49.     }

  50.     if (writeNV)
  51.     {
  52.       (void)osal_nv_write(ZCD_NV_EXTADDR, 0, Z_EXTADDR_LEN, aExtendedAddress);
  53.     }
  54.   }

  55.   // Set the MAC PIB extended address according to results from above.
  56.   (void)ZMacSetReq(MAC_EXTENDED_ADDRESS, aExtendedAddress);
  57. }
复制代码
从代码中可以看到,第一个 if 从NV的 ZCD_NV_EXTADDR 中读取了 IEEE地址。
    如果 NV 读取IEEE成功,则直接跳到最后,执行
(void)ZMacSetReq(MAC_EXTENDED_ADDRESS, aExtendedAddress);   //将读取成功的 IEEE地址 写到MAC层的PIB属性中

   如果 NV 读取失败,或者读取出来的值全为0xFF,则说明 NV中 ZCD_NV_EXTADDR 这个元素并没有被配置过,视为无效,那就从Step2 中读取 IEEE 地址

Step2 从Second IEEE 的位置中找到 IEEE 地址
     这个Second IEEE  到底在哪里呢,Z-Stack User's Guide - CC2530DB.pdf 的 7.2 中已经提到,这个位置是在 FLASH 地址最后置偏移 0x0018个地址的地方。拿CC2530F256来说,256K的FLASH,那偏移0x18地址的位置应该是:0x3FFE8,IEEE 地址长度是8个字节,所以 Second IEEE 地址的存放地址应该是 0x3FFE8 ~ 3FFEF。
     有人可能会疑惑,为什么是偏移0x18个地址呢,这里我个人的理解是:0x18=24=16+8. 这里的8就是IEEE地址的长度,这个16应该是FLASH最后的加密位。
     再从代码来看,第一个 if 满足之后便是下面这句:
HalFlashRead(HAL_FLASH_IEEE_PAGE, HAL_FLASH_IEEE_OSET, aExtendedAddress, Z_EXTADDR_LEN);
      
      这是直接读FLASH 的函数,从 HAL_FLASH_IEEE_PAGE 这一页的 HAL_FLASH_IEEE_OSET 位置来读取 Second IEEE
      在来看看 HAL_FLASH_IEEE_PAGE 和 HAL_FLASH_IEEE_OSET 分别是什么
  1. // Re-defining Z_EXTADDR_LEN here so as not to include a Z-Stack .h file.
  2. #define HAL_FLASH_IEEE_SIZE 8
  3. #define HAL_FLASH_IEEE_PAGE (HAL_NV_PAGE_END+1)  //127
  4. #define HAL_FLASH_IEEE_OSET (HAL_FLASH_PAGE_SIZE - HAL_FLASH_LOCK_BITS - HAL_FLASH_IEEE_SIZE)//(2048-16-8)=2024
复制代码
从宏定义来看,HAL_FLASH_IEEE_PAGE 是NV最后一页再加1,也就是127页,是FLASH的最后一页(1也是2048 byte),地址是 HAL_FLASH_PAGE_SIZE - HAL_FLASH_LOCK_BITS - HAL_FLASH_IEEE_SIZE,也就是地址最后减去16 字节的加密位和8字节的IEEE地址,即2048-24 = 2024, 这也验证了我之前step1 中的猜想。
     第二个 if 是判断这个 Second IEEE 地址是否有效,如果有效,则到代码的最后将有效的 IEEE 地址写入 PIB 中,若无效则进入Step3

Step3 从Primary IEEE 的位置中找到 IEEE 地址
    这个 Primary IEEE 地址到底又在什么地方呢,Z-Stack User's Guide - CC2530DB.pdf 的 7.2 中已经提到,Primary IEEE 位于 FLASH 信息页偏移 0x0c ~ 0x13 个地址的地方。那么这个 FLASH 信息页到底又在哪里呢,参照 CC2530 user's guide 的 2.2.2 CPU memery space ,可以知道, 这个 FLASH information page 是映射到 XDADA的 0x7800 ~ 0x7FFF 上的,那么偏移0x0c个地址,应该就是XData 的 0x780c ~ 0x7813 地址上。
    再来看看源代码
  1. if(!osal_memcmp((uint8 *)(P_INFOPAGE+HAL_INFOP_IEEE_OSET), nullAddr, Z_EXTADDR_LEN))
  2.       {
  3.         osal_memcpy(aExtendedAddress, (uint8 *)(P_INFOPAGE+HAL_INFOP_IEEE_OSET), Z_EXTADDR_LEN);
  4.       }
复制代码
[/code]
  源代码中第三个if直接用osal_memcmp 将 Xdata 中 (P_INFORFAGE + HAL_INFOP_IEEE_OSET) 与 8个 0xFF作比较。这个(P_INFORFAGE + HAL_INFOP_IEEE_OSET) 到底又是什么呢,请看它的宏定义:
  1. #define P_INFOPAGE  PXREG( 0x7800 )  /* Pointer to Start of Flash Information Page          */

  2. #define HAL_INFOP_IEEE_OSET        0xC
复制代码
很明显,这个地址便是 0x7800 + 0xc = 0x780c,正确。如果这个地址有效,就作为 IEEE地址 使用,若无效则需要进入Step4


Step4 随机得到这个 IEEE 地址

  文档中说到,如果前面的step1 ~ step3 都失效,则只能随机产生这个 IEEE地址,随机产生的这个 IEEE 地址 是以 0XF8 开头的。并且这个 IEEE地址不会被保存到 NV 中,因此如果说这个 IEEE 地址时随机产生的,那么这个值 每次上电后都是随机产生的,即使你使能了“NV_RESTORN”也不会存到 NV 中。这样的话如果用于实际应用其实是很危险的,所以尽量不要这种情况发生。
   接下来看看是怎么产生这个随机 IEEE 地址的:
  1. else  // No valid extended address was found.  如果Primary IEEE 还是无效,则产生随机地址,随机地址以0xF8开头
  2.       {
  3.         uint8 idx;

  4. #if !defined ( NV_RESTORE )
  5.         writeNV = FALSE;  // Make this a temporary IEEE address
  6. #endif

  7.         /* Attempt to create a sufficiently random extended address for expediency.
  8.          * Note: this is only valid/legal in a test environment and
  9.          *       must never be used for a commercial product.
  10.          */
  11.         for (idx = 0; idx < (Z_EXTADDR_LEN - 2);)
  12.         {
  13.           uint16 randy = osal_rand();
  14.           aExtendedAddress[idx++] = LO_UINT16(randy);
  15.           aExtendedAddress[idx++] = HI_UINT16(randy);
  16.         }
  17.         // Next-to-MSB identifies ZigBee devicetype.
  18. #if ZG_BUILD_COORDINATOR_TYPE && !ZG_BUILD_JOINING_TYPE
  19.         aExtendedAddress[idx++] = 0x10;
  20. #elif ZG_BUILD_RTRONLY_TYPE
  21.         aExtendedAddress[idx++] = 0x20;
  22. #else
  23.         aExtendedAddress[idx++] = 0x30;
  24. #endif
  25.         // MSB has historical signficance.
  26.         aExtendedAddress[idx] = 0xF8;
  27.       }
复制代码
这个 else 之后便是产生随机 IEEE 地址的过程。
     首先代码禁止了 NV的存储。然后通过一个 for 循环 产生了 6byte的随机地址。注意这里为什么只有6byte,我们的IEEE地址不是8byte的嘛。不要忘记,文档中说过,这个随机 IEEE 地址是要以 0xf8 开头, 那还有一个字节干嘛去了呢,代码中应该能看到,这个字节用于表示不同的设备类型了,协调器是0x10 , 路由器是 0x20 ,终端节点则是 0x30
     所以最后这个随机 IEEE 地址的格式是: 0xF8 + (设备类型) + 6byte 随机值。

    通过以上的讲述,应该把 Z-STACK 中 IEEE 地址的由来说清楚了,我已经经过仿真验证,但是今天太晚了不能再写下去了。

最后还有几个疑问:希望高手看到了能够帮我解答一下:
1. FLASH 的信息页 映射到 Xdata的地址是 0x7800 ~ 0x7FFF, 它实际的地址在哪里
2. 在对CC2530 仿真时,怎么查看 0x3FFE8 ~ 0x3FFEF 的地址。因为51内核 CODE的最大寻址空间是64k,即0x0000~0xFFFF, CC2530通过映射的方法将FLASH分成了8个bank,每个bank 32kb,我通过仿真查看 Logic Code 区域  bank7 的flash,但是好像不对。我有空再验证一下。
4. 到底能不能修改 Primary IEEE 地址,这个问题和问题1类似,需要只要 FALSH的信息页对应地址才能修改,但是这个信息页是只读的,又需要怎么修改。
                                 
written by 邦邦不耍赖
ccc_ccccc 发表于 2012-12-20 20:17:16 | 显示全部楼层
学习啦,楼主
ccc_ccccc 发表于 2012-12-31 14:24:47 | 显示全部楼层
这个很好,再顶下
 楼主| 一月的萧邦 发表于 2013-1-4 12:39:41 | 显示全部楼层
ccc_ccccc 发表于 2012-12-31 14:24
这个很好,再顶下

这个帖子写了很久了,都没有人回复过,谢谢你的支持 {:soso_e113:}
追枫 发表于 2014-3-22 16:46:01 | 显示全部楼层
受用了,很好的参考资料
Hugo801122 发表于 2014-3-23 22:58:56 | 显示全部楼层
学习啦,楼主
astudo 发表于 2014-3-24 13:59:28 | 显示全部楼层
LZ辛苦啦,顶起来
huhao 发表于 2014-5-5 09:24:34 | 显示全部楼层
楼主辛苦了,谢谢分享。
kaining4 发表于 2014-5-9 15:05:42 | 显示全部楼层
辛苦了,楼主!
媳妇卤面 发表于 2014-5-15 11:56:00 | 显示全部楼层
一月的萧邦 发表于 2013-1-4 12:39
这个帖子写了很久了,都没有人回复过,谢谢你的支持

没人回复是因为这东西太高级了,一般人还未用到这个,楼主牛逼哄哄啊{:soso_e144:}
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表