【CC2530 教程 十】CC2530 Z-Stack 协议栈

一、Z-Stack 协议栈目录结构:

        Z-Stack 协议栈可以从 TI 官网免费下载,下载安装完成以后,会默认在 C 盘的根目录下创建 Texas Instruments 目录,该目录下的子目录就是安装的 Z-Stack 文件,并且在该子目录下创建Accessories、 Components、Documents、Projects 和 Tools 等 5个文件夹。

(1)Accessories目录:

存放了各种辅助工具,如下图所示:

其中:

  • OtaServer:存放了OTA(Over The Air,空中升级)测试工具
  • SerialBootTool:存放了串口升级测试工具。  

(2)Components目录:

存放了Z-Stack核心源代码和链接库,打开Components目录,如下图所示:

文件名称 描述
bsp 板级支持包,用于适配 TI 官方开发板的硬件资源。
driverlib 驱动程序链接库,存放 TI 官方未开源驱动程序的链接库。
hal 硬件抽象层,存放各种驱动程序。
mac 媒体介质访问控制,实现物理层通信及 IEEE 802.15.4 协议。
mt 监视层,为监视协议栈各层的运行状态提供支持。
osal 操作系统抽象层,是 Z-Stack 3.0.2的操作系统。
services 提供一些公共的、常用的功能。
stack ZigBee 协议的实现。
usblib USB 链接库,芯片支持 USB(比如 CC2538)时需要用到的功能。
zmac 属于 mac 层的内容。

(3)Documents目录: 

存放了Z-Stack开发辅助的相关文档,打开Documents目录,如下图所示:

文件名称 描述
API 存放 Z-Stack 3.0 相关 API 的说明文档。
CC2530 存放适用于 CC2530 型号 MCU 的相关文档。
CC2538 存放适用于 CC2538 型号 MCU 的相关文档。
Z-Stack 3.0 Developer's Guide.pdf Z-Stack 3.0 开发指导。
Z-Stack 3.0 Sample Application User's Guide.pdf Z-Stack 3.0 应用例程指导。
Z-Stack OTA Upgrade User's Guide.pdf OTA(Over The Air)空中升级说明指导。

(4)Projects目录:

存放了ZigBee应用例程的工程文件和源码文件,打开Projects目录,如下图所示:

其中:

  • tools:存放与ZigBee例程应用相关的工具。
  • zstack:存放ZigBee相关例程。

打开zstack文件夹,如下图所示:

文件夹名称 描述
HomeAutomation 面向家庭自动化领域的相关例程。
Libraries 存放链接库文件,TI 不开放的源代码会被编译为链接库的形式提供给开发者使用。
OTA OTA(Over The Air)空中升级例程。
Tools 存放工程配置相关的文件。
Utilities 公共文件夹。
ZMain 存放主函数所在的源代码文件及系统硬件启动相关的源代码文件。
ZNP ZNP(ZigBee And Processor)例程。

(5)其他目录:

文件名或文件夹名 描述
Tools 存放开发调试相关的工具。
EULA.pdf 版权声明文件。
Getting Started Guide - CC2530.pdf 针对 CC2530 型号 MCU 的开始向导文件。
Getting Started Guide - CC2538.pdf 针对 CC2538 型号 MCU 的开始向导文件。
Z-Stack 3.0 Release Notes.txt Z-Stack 3.0 发布描述文件。
Z-Stack 3.0 Software Development Kit Resource Guide.html Z-Stack 3.0 开发资源向导链接文件。
Z-Stack Core Release Notes.txt Z-Stack Core 发布描述文件。
Z-Stack_3.0.1_Manifest.html Z-Stack 3.0 关键信息描述清单。
.iss 此文件夹为隐藏文件夹,存放用于卸载 Z-Stack 3.0 的相关文件。

二、Z-Stack 3.0.2 工程框架:

在 Projects\Zstack\HomeAutomation 目录下,包含:进入其中一个文件夹,选择相应的 Demo 工程文件并双击,即可打开一个 Demo 工程。

(1)工程组织结构:

打开 GenericApp 演示项目工程后,IAR 软件左边出现如下图所示的 Z-Stack 协议栈目录结构:

 其中:

组名称 说明
App 存放应用程序相关源代码文件。
BDB 实现 ZigBee BDB(Base Device Behavior,设备基础行为)功能。
GP 实现 ZigBee GP(Green Power,绿色能源)功能。
HAL 硬件抽象层,存放各种驱动程序。
MAC 媒体介质访问控制,实现物理层通信及 IEEE 802.15.4 协议。
MT 监视层,为监视协议栈各层的运行状态提供支持。
NWK ZigBee 网络层。
OSAL 操作系统抽象层。
Profile 存放 ZigBee 标准化定义及相关功能实现的源代码文件。
Security 实现安全相关服务。
Services 提供一些公共的、常用的功能。
Tools 存放工程配置相关的文件。
ZDO 存放 ZDO(ZigBee Device Object,ZigBee 设备对象)相关源代码文件。
ZMac 属于 mac 层的内容。
ZMain 存放主函数所在的源代码文件及系统硬件启动相关的源代码文件。
Output 存放工程编译/链接时输出的文件。

在工程中可以选择不同的ZigBee网络设备类型。单击选项卡→选择网络设备类型,如下图所示:

之前有介绍到,ZigBee网络设备类型有3种,分别是Coordinator(协调器),Router(路由器)和EndDevice(终端设备)。

选项 含义
CoordinatorEB ZigBee 协调器。
RouterEB ZigBee 路由器。
EndDeviceEB ZigBee 终端设备。
EndDeviceEB-OTAClient 支持 OTA(Over The Air)空中升级的 ZigBee 终端设备。
RouterEB-OTAClient 支持 OTA(Over The Air)空中升级的 ZigBee 路由器。

(2)工程编译及链接:

选择Coordinator(协调器),点击重建所有:

编译及链接过程没有错误和警告。

三、Z-Stack OSAL调度原理

(1)OSAL简介:

        OSAL(Operating System Abstraction Layer,系统抽象层),可以通俗地理解为一个简化版的操作系统,为Z-Stack的正确运行提供了内存管理、中断管理和任务调度等基本功能。

(2)OSAL任务调度原理:

        OSAL的任务调度其实是初始化任务池以及轮询任务池。

以SampleSwitch为例:

打开ZMain.c文件:

main函数如下:

/*********************************************************************
 * @fn      main
 * @brief   启动后调用的第一个函数。
 * @return  无关心
 */
int main(void)
{
  // 禁用中断,防止在初始化过程中被打断
  osal_int_disable(INTS_ALL);

  // 初始化与板子相关的功能,如LED等
  HAL_BOARD_INIT();

  // 确保供电电压足够高以运行
  zmain_vdd_check();

  // 初始化板子的I/O
  InitBoard(OB_COLD);

  // 初始化HAL驱动程序
  HalDriverInit();

  // 初始化NV(非易失性)系统
  osal_nv_init(NULL);

  // 初始化MAC层
  ZMacInit();

  // 确定扩展地址
  zmain_ext_addr();

#if defined ZCL_KEY_ESTABLISH
  // 初始化Certicom证书信息,用于密钥建立
  zmain_cert_init();
#endif

  // 初始化基本的NV项
  zgInit();

#ifndef NONWK
  // 由于AF不是一个任务,调用其初始化例程
  afInit();
#endif

  // 初始化操作系统
  osal_init_system();

  // 允许中断
  osal_int_enable(INTS_ALL);

  // 最终的板子初始化
  InitBoard(OB_READY);

  // 显示有关此设备的信息
  zmain_dev_info();

  /* 在LCD上显示设备信息 */
#ifdef LCD_SUPPORTED
  zmain_lcd_init();
#endif

#ifdef WDT_IN_PM1
  /* 如果使用WDT(看门狗定时器),这是启用它的好地方 */
  WatchDogEnable(WDTIMX);
#endif

  osal_start_system(); // 从此处开始运行操作系统,不会返回

  return 0;  // 不应该到达这里。
} // main()

 其中有两个关键的函数调用:

// 初始化操作系统任务池
osal_init_system();

// 轮询任务池
osal_start_system(); 

osal_init_system()定义如下:

/*********************************************************************
 * @fn      osal_init_system
 *
 * @brief
 *
 *   This function initializes the "task" system by creating the
 *   tasks defined in the task table (OSAL_Tasks.h).
 *
 * @param   void
 *
 * @return  SUCCESS
 */
uint8 osal_init_system(void)
{
#if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
  // Initialize the Memory Allocation System
  // 初始化内存分配系统,用于管理动态内存的分配和释放
  osal_mem_init();
#endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */

  // Initialize the message queue
  // 初始化消息队列,用于任务间的消息传递和处理
  osal_qHead = NULL;

  // Initialize the timers
  // 初始化定时器系统,用于处理系统中的定时任务和时间管理
  osalTimerInit();

  // Initialize the Power Management System
  // 初始化电源管理系统,用于管理设备的功耗和电源状态
  osal_pwrmgr_init();

#ifdef USE_ICALL
  /* Prepare memory space for service enrollment */
  // 为服务注册准备内存空间,用于ICall服务的内存管理
  osal_prepare_svc_enroll();
#endif /* USE_ICALL */

  // Initialize the system tasks.
  // 初始化系统任务,创建任务表中定义的任务并设置任务环境
  osalInitTasks();

#if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
  // Setup efficient search for the first free block of heap.
  // 设置高效的堆内存搜索,优化内存分配的性能
  osal_mem_kick();
#endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */

#ifdef USE_ICALL
  // Initialize variables used to track timing and provide OSAL timer service
  // 初始化用于跟踪时间和提供OSAL定时器服务的变量
  osal_last_timestamp = (uint_least32_t)ICall_getTicks();
  osal_tickperiod = (uint_least32_t)ICall_getTickPeriod();
  osal_max_msecs = (uint_least32_t)ICall_getMaxMSecs();
  /* Reduce ceiling considering potential latency */
  // 考虑到可能的延迟,减少最大毫秒数的上限
  osal_max_msecs -= 2;
#endif /* USE_ICALL */

  return (SUCCESS);
}

在该函数中可以看到一个任务池初始化函数osalInitTasks(),它的作用是初始化任务池。 

  osal_start_system()定义如下:

/*********************************************************************
 * @fn      osal_start_system
 *
 * @brief
 *
 *   This function is the main loop function of the task system (if
 *   ZBIT and UBIT are not defined). This Function doesn't return.
 *
 * @param   void
 *
 * @return  none
 */
void osal_start_system(void)
{
#ifdef USE_ICALL
  /* Kick off timer service in order to allocate resources upfront.
   * The first timeout is required to schedule next OSAL timer event
   * as well. */
  // 启动定时器服务,提前分配资源。第一次超时用于安排下一个OSAL定时器事件。
  ICall_Errno errno = ICall_setTimer(1, osal_msec_timer_cback,
                                     (void *)osal_msec_timer_seq,
                                     &osal_timerid_msec_timer);
  if (errno != ICALL_ERRNO_SUCCESS)
  {
    // 如果设置定时器失败,调用ICall_abort()进行错误处理
    ICall_abort();
  }
#endif /* USE_ICALL */

#if !defined(ZBIT) && !defined(UBIT)
  for (;;) // 永远循环,作为任务系统的主循环
#endif
  {
    // 运行系统任务,处理任务调度和执行
    osal_run_system();

#ifdef USE_ICALL
    // 等待ICall事件,ICALL_TIMEOUT_FOREVER表示无限期等待
    ICall_wait(ICALL_TIMEOUT_FOREVER);
#endif /* USE_ICALL */
  }
}

在osal_start_system()函数的主循环中,循环调用了 osal_run_system()函数,该函数主要作用是轮询任务池。

osal_run_system()函数 中这个do-while循环:

 它的主要作用是轮询整个任务池,检查是否有需要处理的任务。循环中只有一个条件判断,如果条件成立,就结束循环。

其中,tasksEvents 是一个 uint16 类型的数组,每个元素表示一种类型的任务也就是任务池。tasksCnt 是这个任务池的大小。

循环的运行逻辑是这样的:

  • 首先,idx 的初始值为 0。
  • 当 tasksEvents[idx] 的值为 0 时,表示该任务没有事情要处理,这时候条件判断不成立,进入下一次循环。
  • 每执行一次循环前,idx 加 1,然后判断是否小于 tasksCnt。
  • 当 tasksEvents[idx] 的值不等于 0 时,表示该任务中有事情要处理,这时候条件判断成立,于是通过 break 结束循环。
  • 当循环结束后,如果整个任务池中都没有任务要处理,那么 idx 必定会 >= tasksCnt。因此,如果 idx < tasksCnt,表示现在任务池中有任务需要处理,并且 tasksEvents[idx] 就是当前需要处理的任务。因此在循环结束后,Z-Stack会 用 if (idx < tasksCnt) 语句来判断有没有任务需要处理。

(3)任务与事件:

        每个任务中可能包含一系列待处理的事情,这些待处理的事情可以通俗地称为“事件”,例如一个任务中可以包含打开 LED 灯、读取温湿度和查看设备状态3 个事件(待处理的事情)。

        tasksEvents 中的每个元素都是一个 uint16 类型的变量,每一个元素都表示了一个任务,并且储存了这个任务中包含的一系列事件