Zigbee开发环境的建立入门

注意软件版本,特别是用新软件打开旧工程时。(本文是cc2430有些和cc2530不同)

一、安装IAR 8051 7.30B

    使用管理员权限运行安装程序EW8051-EV-730B.exe,根据提示输入相应的注册码,完成相关的安装。

《CC2530手册》有,且网盘里有软件及注册机

二、安装Ti的Zigbee协议栈

      从Ti官网下载到其Zigbee协议栈压缩包swrc073d.zip,解压后安装。Windows 7下无法完成安装,根据之前在WinXP安装的经验,此安装程序生成一个名为的目录,此目录下有Zigbee协议栈和相关的文档和例程,故可以在WinXP的虚拟机中完成安装,之后拷贝到Win7中即可。

可以安装,在网盘有

三、安装Packet Sniffer

      在进行Zigbee开发时,可以使用一个下载器和模块组成嗅探器(sniffer),相关信号的读取和显示使用Ti的Packet Sniffer软件完成,从Ti的网站上下载swrc045j.zip,解压后安装。Packet Sniffer监控的不仅是Zigbee的数据包,监控的是所有IEEE 802.15.4的无线数据包。程序的界面如图1所示

图1

 

四、安装SmartRF Flash Programmer

      如同网卡的MAC地址,不同的Zigbee模块使用不同的IEEE地址(实用产品必须要向IEEE申请相关的地址),使用SmartRF Flash Programmer可以为zigbee模块烧写程序及IEEE地址,读出模块中的程序。从Ti的网站上下载swrc044f.zip,解压后安装。还有一个名为IEEE Address Program Software,只能读写IEEE地址,在swrc063.zip中,若安装了SmartRF Flash Programmer,无需安装IEEE Address Program SoftwareSmartRF Flash Programmer的程序界面如图2所示

图2

五、 初识协议栈目录

       在的目录结构如图3,主要有四个目录,中是相关的开发文档,中是两个开发辅助工具ZOAD和Z-Tool(安装完zigbee协议栈后就会有软件图标),ZOAD用于空中下载,Z-Tool可用于观察网络状态、串口输出内容显示等。与用户关系最大的是存放的硬件驱动代码,根据实际使用的开发板或产品的不同,cc2430的引脚功能配置不同,要修改、增加相关的驱动代码。中有多个例程用以学习Ti Zigbee的开发,提供了不同的程序框架,用户可以以相关的例程为模板创建自己的工程。

      对于硬件驱动代码对于不同的外设,除了定时器、AD等CC2430内部的外设不用自己编写驱动外,对于按键、LED、LCD要根据实际的连接要重写。一般情况下,不同厂商都采取了核心模块+扩展板的设计,各家厂商不同点在于扩展板的不同,Ti原厂的扩展板也分为DB和EB两种。各厂商在设计时都会对Ti的开发板有所参考,就我所用的无线龙开发板而言,其与Ti的CC2430EB最为相似,故之后使用的例程均为修改版的EB板例程。

       对于中的文档(包括其中的目录),初学者至少应阅读过以下几篇《Z-Stack Developer's Guide _F8W-2006-0022_》、《Z-Stack Sample Applications (F8W-2006-0023)》、《HAL Driver API _F8W-2005-1504_》、《Z-Stack Sample Application For CC2430DB_F8W-2007-0017_》、《Create New Application For The CC2430DB_F8W-2005-0033_》。在安装的zigbee协议债中


一、修改HAL

      HAL及所谓的Hardware Abstration Layer,通俗的了解即为开发板的硬件驱动,由于所用的是无线龙的开发板,与Ti的原装开发板有差异,需要对协议栈自带的HAL进行修改。HAL文件存放在目录中,里面有三个目录,中定义的与外设无关的硬 件操作,存放的是头文件,而存放的是目标文件,里面根据目标板的不同分为。所用的无线龙的开发板和CC2430EB最为相似,故修改中的内容。按键操作几乎在每个例程中都会用到,故此处以按键驱动的修改为例,演示HAL的修改。

        先了解下Ti和无线龙扩展板的不同之处。Ti的CC2430EB原理图在Ti文档SWRU133.pdf(位于SWRU133.zip中)。Page29是按键电路的原理图,如图1

图1(左上角是元件图)

      CC2430EB的按键其实是摇杆,上下左右四个方向和电阻网络相连,通过放大电路送到CC2430的P0.6脚,经AD采样后判断摇杆摆向哪个方向,按键编号为SW1~SW4摇杆也可像普通按键一样按下,产生一个直流电平变化,接到P0.5脚,按键编号为SW5。除此之外,还有一个独立按键连到P0.1脚,按键编号为SW6。

      无线龙的开发板则是用六个独立按键,上下左右四个按键和电阻网络相连,接P0.6,由AD采样得出是哪个键被按下。还有两个按键OK、Cancel分别直接和P0.5、P0.4相连。由于Ti和无线龙上下左右四个按键的电阻网络有差异,AD采样值有所不同,要予以修改。还有修改SW5、SW6的读取为的无线龙地OK、Cancel两个按键。

      要修改的文件为hal_key.c,要修改的部分宏定义、uint8 HalKeyRead ()、void HalKeyPoll ()。

      修改SW6的引脚定义,行156中的HAL_KEY_BIT1改为HAL_KEY_BIT4

   156: #define HAL_KEY_SW_6_BIT      HAL_KEY_BIT4 
      修改uint8 HalKeyRead ()中的SW5、SW6有关的内容,注释掉以下语句
#if defined (HAL_KEY_SW_6_ENABLE)
  if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT))    /* Key is active low */
  {
    keys |= HAL_KEY_SW_6;
  }
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
  if (HAL_KEY_SW_5_PORT & HAL_KEY_SW_5_BIT)       /* Key is active high */
  {
    keys |= HAL_KEY_SW_5;
  }
#endif
在对应位置添加
if (P0_5 == 0)
{
  keys |= 0x04;
}
 
if (P0_4 == 0)
{
  keys |= 0x20;
}
 
      修改用于判断哪个方向键被按下的P0.6采样值,do{}while中的条件语句注释掉,取之以下内容
if ((adc >= 0x55) && (adc <= 0x70))
{
   ksave0 |= HAL_KEY_UP;
}
else if ((adc >= 0x40) && (adc <= 0x50))
{
  ksave0 |= HAL_KEY_DOWN;
}
else if ((adc >= 0x18) && (adc <= 0x30))
{
  ksave0 |= HAL_KEY_LEFT;
}
else if (adc <= 10)
{
  ksave0 |= HAL_KEY_RIGHT;
}
else
{
}

     修改void HalKeyPoll ()中的有关的内容,修改同HalKeyRead ()。

     再把void HalKeyEnterSleep ( void )中所有内容注释掉,将uint8 HalKeyExitSleep ( void )中的

#if defined (HAL_KEY_SW_5_ENABLE)
  HAL_KEY_SW_5_INP |= HAL_KEY_SW_5_BIT;       /* Set pin input mode to tri-state */
#endif

注释掉,以上就完成了按键有关Hal修改。

 

二、Zigbee工程设置

      下面将以运行一个工程为例,介绍Zigbee工程设置。CC2430的开发环境为IAR,相信接触过MSP430的朋友对其不会陌生,有关Zigbee工程的设置实际上就是通过IAR的工程设置完成的。打开下的GenericApp.eww。

      打开工程后,界面如图2

图2

      同一个工程中包含的多个project,根据开发板的类型(EB和DB)、Zigbee网络中的节点类型(Coordinator、Router和EndDevice)一共有六个project,我们只需用到CoordinatorEBEndDeviceEB这两个project。先把project选择为CoordinatorEB,在GenericApp-CoordinatorEB上右击,选择Options…进入工程设置界面,选择C/C++ Compiler->Preprocessor,设置预编译项。去掉LCD_SUPPORTED=DEBUGHAL_UART在GenericApp中可加可不加,如图3所示

图3

通过条件编译选项设定是否添加相关的硬件驱动和调试选项,去掉LCD_SUPPORTED=DEBUG的原因是Ti的开发板和无线龙开发板的LCD接口不一样,而我们又没有修改相关Hal文件,无法使用LCD显示,加入HAL_UART的原因是我们将使用串口作为调试信息的输出

 

三、Zigbee工程启动流程解析

      用户相关的应用程序代码是在App类的文件当中,其与Zigbee是如何关联起来的呢?如何调配处理器时间去处理应用程序、响应外设和进行无线收发呢?Ti协议栈通过了一个小型的操作系统实现了复杂多样的事件处理。CC2430具有128KB ROM、8KB RAM,如此大的空间为SOC(System On Chip)提供了有力保障。操作系统的API介绍在《OSAL API_F8W-2003-0002_》中,在这个操作系统中提供了消息管理、任务同步、定时器管理、中断管理、任务管理、内存管理、电院管理和非易失内存管理等功能。用户的应用程序要以任务(task)的方式注册到系统中,一个task对应于自己的事件处理函数(event processor)。task的添加,event和task的关联,事件处理函数和对应task的关联将在以下的流程解析中讲解。

1、main()

      编译工程(快捷键F7),将模块连接到PC,调试工程(快捷键Ctrl + D)。进入到函数的入口——main()函数,位于ZMain Group的ZMain.c中。在main()中完成了相关硬件和软件的初始化,作为重点,我们要关注的是

   190: osal_init_system();

   206: osal_start_system(); // No Return from here

我们先来看osal_init_system()。在osal_init_system右击,选择Go to definition of osal_init_system,追踪其定义(注意“追踪”的方法,下文所述的追踪都是指用这个方法)

2、osal_init_system()

      osal_init_system()定义在OSAL Group的OSAL.c中,进行了操作系统相关的初始化工作,需要重点关注的是

   927: osalInitTasks();

追踪其定义。

3、osalInitTasks()和添加task到OS

      osalInitTasks()定义在App Group的OSAL_GenericApp.c中,函数内容如下

void osalInitTasks( void )
{
  uint8 taskID = 0;
 
  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
 
  macTaskInit( taskID++ );
  nwk_init( taskID++ );
  Hal_Init( taskID++ );
#if defined( MT_TASK )
  MT_TaskInit( taskID++ );
#endif
  APS_Init( taskID++ );
  ZDApp_Init( taskID++ );
  GenericApp_Init( taskID );
}

在这个函数中,我们看到了将task添加到系统的过程,每添加一个task,taskID就加1。最后一句GenericApp_Init()正是关注的重点,追踪它。

4、GenericApp_Init()和注册event到task中

      GenericApp_Init()位于GenericApp.c中,完成了endPoint的设定和注册、按键事件的注册,即把相关的enevt和相关的task进行了关联,当发生这些event时,会由和相应task关联的事件处理函数进行处理。到此,已经了解了如何添加task、如何关联event和task,那么task和事件处理函数的关联又是如何进行的呢?先找找GenericApp_Init对应的事件处理函数,解决这个问题,要回到ZMain.c中main()里的osal_start_system(),追踪它吧!

5、osal_start_system()和task及事件处理函数的关联

      osal_start_system()定义在OSAL.c中。osal_start_system()中最有可能是执行事件处理函数的就是行977了

   977: events = (tasksArr[idx])( idx, events );

tasksArr是函数数组,idx代表task的id,events代表相应的事件,猜测正确吗,追踪它。

      在OSAL_GenericApp.c中找到了tasksArr的定义,内容如下

const pTaskEventHandlerFn tasksArr[] = {
  macEventLoop,
  nwk_event_loop,
  Hal_ProcessEvent,
#if defined( MT_TASK )
  MT_ProcessEvent,
#endif
  APS_event_loop,
  ZDApp_event_loop,
  GenericApp_ProcessEvent
};

如果追踪里面的数组成员,它们都是事件处理函数,注意,里面的成员顺序和osalInitTasks()中task的添加顺序是一样的,即task的ID和其对应的事件处理函数在tasksArr中的序号是一样的,这样在调用(tasksArr[idx])( idx, events )时,完成了task和对应的事件处理函数的关联。

6、事件处理

      上面我们了解了task和对应事件处理函数关联方式,我们重点分析下用户自定义task的事件处理函数GenericApp_ProcessEvent()。通过行233

   233: if ( events & SYS_EVENT_MSG )

和行300

   300: if ( events & GENERICAPP_SEND_MSG_EVT )

可知,起码有两类事件:SYS_EVENT_MSG和GENERICAPP_SEND_MSG_EVT,显然,SYS_EVENT_MSG是协议栈已经定义好的系统事件,而GENERICAPP_SEND_MSG_EVT就是用户自定义的事件了。事件号是一个16bit的常量,使用独热码(one-hot code)编码,方便进行event的提取,这样一个task中最多可以有16个event,SYS_EVENT_MSG已经占用了0x8000,故自定义的事件只能有16个。由于事件号使用独热码,故事件的提取和清除可以用简单的位操作指令实现。事件提取events & GENERICAPP_SEND_MSG_EVT,事件清除events ^ GENERICAPP_SEND_MSG_EVT。系统事件包函了各种系统消息(message),系统事件中的消息号是一8bit常量,定义在ZComDef.h中,

  322: #define SPI_INCOMING_ZTOOL_PORT   0x21    // Raw data from ZTool Port (not implemented)
  323: #define SPI_INCOMING_ZAPP_DATA    0x22    // Raw data from the ZAPP port (see serialApp.c)
  324: #define MT_SYS_APP_MSG            0x23    // Raw data from an MT Sys message
  325: #define MT_SYS_APP_RSP_MSG        0x24    // Raw data output for an MT Sys message
  326:  
  327: #define AF_DATA_CONFIRM_CMD       0xFD    // Data confirmation
  328: #define AF_INCOMING_MSG_CMD       0x1A    // Incoming MSG type message
  329: #define AF_INCOMING_KVP_CMD       0x1B    // Incoming KVP type message
  330: #define AF_INCOMING_GRP_KVP_CMD   0x1C    // Incoming Group KVP type message
  331:  
  332: #define KEY_CHANGE                0xC0    // Key Events
  333:  
  334: #define ZDO_NEW_DSTADDR           0xD0    // ZDO has received a new DstAddr for this app
  335: #define ZDO_STATE_CHANGE          0xD1    // ZDO has changed the device's network state
  336: #define ZDO_MATCH_DESC_RSP_SENT   0xD2    // ZDO match descriptor response was sent
  337: #define ZDO_CB_MSG                0xD3    // ZDO incoming message callback

用户可以自定义系统事件的消息范围为0xE0~0xFF,现只针对SYS_EVENT_MSG中的两个处理函数进行说明。

      AF_INCOMING_MSG_CMD:当模块接收到属于自己的无线数据信息时,就会触发AF_INCOMING_MSG_CMD消息,由相关的处理函数处理接收到的信息。

      ZDO_STATE_CHANGE:当网络状态改变时就会触发此消息,其在这个工程中的作用是,当节点建立网络(作为协调器)、加入网络(作为终端)时,运行

osal_start_timerEx( GenericApp_TaskID,
                                GENERICAPP_SEND_MSG_EVT,
                                GENERICAPP_SEND_MSG_TIMEOUT );

osal_start_timerEx()的作用是启动一系统定时器,当其溢出(GENERICAPP_SEND_MSG_TIMEOUT)时,会触发task(GenericApp_TaskID)的事件(GENERICAPP_SEND_MSG_EVT)。又看到事件GENERICAPP_SEND_MSG_EVT了,它是用户定义的事件,分析之。

if ( events & GENERICAPP_SEND_MSG_EVT )
{
  // Send "the" message
  GenericApp_SendTheMessage();
 
  // Setup to send message again
  osal_start_timerEx( GenericApp_TaskID,
                      GENERICAPP_SEND_MSG_EVT,
                      GENERICAPP_SEND_MSG_TIMEOUT );
 
  // return unprocessed events
  return (events ^ GENERICAPP_SEND_MSG_EVT);
}
以上就是GENERICAPP_SEND_MSG_EVT的处理函数,先分析总的流程,GenericApp_SendTheMessage()发送信息,osal_start_timerEx()重新启动一系统定时器,同样是指向task(GenericApp_TaskID)的事件(GENERICAPP_SEND_MSG_EVT),返回时要注意清除当前事件(events ^ GENERICAPP_SEND_MSG_EVT),否则会反复处理同一个事件,陷入死循环。

四、Zigbee应用模型

     对数据进行收发还需对Zigbee的应用模型有所了解。Profile和Endpoint是Zigbee应用层中有关应用模型的,概念,先给出这两个概念的官方的定义:

Application Profiles are an agreement on a series of messages defining an application space (for example, “Home Automation” or “Smart Energy”) 
Endpoints are a logical extension added to a single ZigBee radio which permits support for multiple applications, addressed by the Endpoint number (1-240) 
Key Relationships: 
Maximum of 240 Endpoints per ZigBee Device (Endpoint 0 is reserved to describe the generic device capabilities and Endpoint 255 is reserved for broadcasting to all endpoints, Endpoints 241-254 are reserved for future use) 
One Application Profile described per Endpoint 。

我个人的理解是:某个具体的应用(application)的相关操作被定义成一个profile,profile和应用层的结合通过endpoint来实现,一个zigbee设备有240个endpoint。

      在GenericApp_Init( byte task_id )中有以下相关的定义

GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
GenericApp_epDesc.task_id = &GenericApp_TaskID;
GenericApp_epDesc.simpleDesc
          = (SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc;
GenericApp_epDesc.latencyReq = noLatencyReqs;

追踪相关的变量可知使用类型endPointDesc_t描述endpoint的类型,其定义在AF.h中

typedef struct
{
  byte endPoint;
  byte *task_id;  // Pointer to location of the Application task ID.
  SimpleDescriptionFormat_t *simpleDesc;
  afNetworkLatencyReq_t latencyReq;
} endPointDesc_t;

需要进一步了解的是SimpleDescriptionFormat_t,其定义也在AF.h中

typedef struct
{
  byte          EndPoint;
  uint16        AppProfId;
  uint16        AppDeviceId;
  byte          AppDevVer:4;
  byte          Reserved:4;             // AF_V1_SUPPORT uses for AppFlags:4.
  byte          AppNumInClusters;
  cId_t         *pAppInClusterList;
  byte          AppNumOutClusters;
  cId_t         *pAppOutClusterList;
} SimpleDescriptionFormat_t;

里面有AppProfId,即为Profile Id,可以从这个结构中看到,profile中有In、Out两种类型的Cluster,Cluster此处可理解为操作,每类Cluster对应着一个ClusterList,list中的每个成员对应一种操作,GenericApp的GenericApp_SimpleDesc为

const SimpleDescriptionFormat_t GenericApp_SimpleDesc =
{
  GENERICAPP_ENDPOINT,              //  int Endpoint;
  GENERICAPP_PROFID,                //  uint16 AppProfId[2];
  GENERICAPP_DEVICEID,              //  uint16 AppDeviceId[2];
  GENERICAPP_DEVICE_VERSION,        //  int   AppDevVer:4;
  GENERICAPP_FLAGS,                 //  int   AppFlags:4;
  GENERICAPP_MAX_CLUSTERS,          //  byte  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList,  //  byte *pAppInClusterList;
  GENERICAPP_MAX_CLUSTERS,          //  byte  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList   //  byte *pAppInClusterList;
};
 
 

五、运行例程

1、运行

      至此,对Zigbee的一些基本概念已经介绍完了,让我们运行第一个例程吧。先确认是否已按前文所述修改HAL及工程设置。由于每有移植LCD驱动,使用串口输出相关的信息。修改GenericApp.c中的void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )

void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
  switch ( pkt->clusterId )
  {
    case GENERICAPP_CLUSTERID:
      HalUARTWrite(HAL_UART_PORT_0, (uint8*)pkt->cmd.Data, pkt->cmd.DataLength);
      // "the" message
#if defined( LCD_SUPPORTED )
      HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" );
#elif defined( WIN32 )
      WPRINTSTR( pkt->cmd.Data );
#endif
      break;
  }
}

编译后分别下载,即可运行。

2、使用Packet Sniffer观察无线数据

    为了了解有关的无线信号,使用Packet Sniffer侦测空中的Zigbee信号,使用一个SmartRF04EB+CC2430模块配合Packet Sniffer软件即可完成上述功能。

    运行Packet Sniffer,先要选择协议及物理设备的芯片类型。

图4 协议及芯片类型

之后的显示面板的功能如图

图5

运行后会把数据划分后显示出来

图6

各部分含义请自行学习。观察两个模块相互发送的”Hello Word”,用户数据是在APS层,找到一段含APS Payload的数据包,内容为

图7

48 65 6C 6C 6F 20 57 6F 72 6C 64 00即为Hello World的字符串。





将对如何建立一个实用的WSN系统进行探讨。

      Zigbee的出现是为了满足WSN(Wireless Sensor Network)的要求,一般而言WSN有以下几个特征:

1.采集点众多,分布面积广

2.网络节点间的位置关系不定,节点动态加入或脱离网络

3.采集点无法和市电网络相连,依赖于电池供电,要求有很好的节电及电源管理

为了实现节能的特性,还跟CC2430模块以外的采集模块有关,本文主要关注的是CC2430自身的管理使用,故对外界数据的采集简化为从AD中采集数据。目标系统将具备以下功能:

1.协调器建立网络,终端节点加入网络

2.节点能采集多种数据

      从例程中选用一个合适的范例作为模板可以大大缩短开发时间,节约成本。选用SimpleApp作为模板。SimpleApp中有两个例程,一个是控制器-开关,一个是收集器-传感器,将使用收集器-传感器例程。收集器-传感器例程中以传感器终端的温度及电源电压为数据源,传感器定时采集这两个数据,送往收集器,收集器收到数据后通过串口传给PC机。可以说SimpleApp本身就是一个接近实用的WSN例程,本文的目标在于学习SimpleApp的使用,并加上一个通过AD采集数据的功能。此外,由于SimpleApp的传感器终端启动后就一直采集发送数据,无法由收集器控制其采集的开启/关停,将增添由PC发送指令到收集器,再由收集器发送指令控制某终端的某项采集功能的开启/关闭。

一、建立新工程

      为了和例程SimpleApp区分开,新建工程命名为WSNApp,其中只包含Collector和Sensor,且只为EB板设置,本节内容不会对程序的运行有实质影响,修改工程名只是为了管理上的方便。也可不修改工程名,只做功能上的修改,直接跳到下一节的内容。先原地复制SimpleApp工程文件夹,并修改目录名为WSNApp。WSNApp中有两个目录,为、,中的为应用程序,删除SimpleController.c和SimpleSwitch.c,将SimpleApp.h更名为WSNApp.h。中存放的具体的Project的设置,如果还没打开过相关工程的话,里面应该只有三个文件SimpleApp.ewd、SimpleApp.ewp和SimpleApp.eww,将这三个文件均改名为WSNApp,扩展名保持不变。这三个文件都是XML格式的文本文件,可以用记事本等工具打开修改,我使用的Notepad++,其支持XML语言的高亮显示及折叠功能。

      修改WSNApp.eww,先将所有的SimpleApp替换为WSNApp。里面的代码结构如下

<member>
  <project>WSNAppproject>
  <configuration>SimpleCollectorDBconfiguration>
member>
<member>
  <project>WSNAppproject>
  <configuration>SimpleCollectorEBconfiguration>
member>
<member>
  <project>WSNAppproject>
  <configuration>SimpleControllerDBconfiguration>
member>

每个member下是相应的project及相关的设置,删除所有的DB及Controller、Switch。最后只保留以下内容

<member>
  <project>WSNAppproject>
  <configuration>SimpleCollectorEBconfiguration>
member>
<member>
  <project>WSNAppproject>
  <configuration>SimpleSensorEBconfiguration>
member>

      修改WSNApp.ewp,WSNApp.ewp有8902行,如果所用的文本编辑器不支持XML语言会改得比较痛苦。先将所有的SimpleApp替换为WSNApp。WSNApp.ewp存放的是工程的目录结构,WSNApp.ewp中的configuration段定义的是各个project 的设置,还有group段定义的是各个project中各文件的关联关系。删除有关DB及Controller、Switch的configuration段。修改App的group段的中的DB及Controller、Switch段落。

      修改WSNApp.ewd,先删除有关DB及Controller、Switch的configuration段即可。

二、Collector的修改

      Collector程序要添加PC和Collector的间的通讯、通过无线发送指令到Sensor。要为Collector添加一个用户事件,用于发送数据。

1、串口设置

      Ti的协议栈中已经有了有关的串口通信的驱动,可直接调用,使用串口有关的指令,需包含"hal_uart.h"头文件,在SimpleCollector.c和sapi.c中均要包含。要添加预编译指令HAL_UART。使用halUARTCfg_t来配置串口,配置串口时需配置输入/输出缓冲区的大小等信息。由于用户task的初始化在sapi.c中,串口的初始化也放在sapi.c中。先在sapi.h中定义以下常量

#ifdef COLLECTOR
  #define SERIAL_PORT_THRESH 48
  #define SERIAL_PORT_RX_MAX  64
  #define SERIAL_PORT_TX_MAX  64
  #define SERIAL_PORT_IDLE  6
  #define SERIAL_PORT_RX_CNT  80
#endif
由于sapi.h和sapi.c是collector和sensor共用的,故使用的条件编译的方式定义常量。
      在sapi.c中,先定义串口的配置变量。
void SAPI_Init( byte task_id ) 
{ 
  uint8 startOptions; 
#ifdef COLLECTOR 
  halUARTCfg_t uartConfig; 
#endif
在SAPI_Init()的末尾初始化串口
 
#ifdef COLLECTOR
  uartConfig.configured           = TRUE;              // 2430 don't care.
  uartConfig.baudRate             = HAL_UART_BR_38400;  // CC2430 only allow 38.4k or 115.2k
  uartConfig.flowControl          = HAL_UART_FLOW_OFF;  // Turn off flow control to fit most serial ports' setting
  uartConfig.flowControlThreshold = SERIAL_PORT_THRESH;
  uartConfig.rx.maxBufSize        = SERIAL_PORT_RX_MAX;
  uartConfig.tx.maxBufSize        = SERIAL_PORT_TX_MAX;
  uartConfig.idleTimeout          = SERIAL_PORT_IDLE;   // 2430 don't care.
  uartConfig.intEnable            = TRUE;              // 2430 don't care.
  uartConfig.callBackFunc         = rxCB;
  HalUARTOpen (HAL_UART_PORT_0, &uartConfig);
#endif
 
  // Set an event to start the application
  osal_set_event(task_id, ZB_ENTRY_EVENT);
}
此处有两个需要注意的地方,串口波特率只能为38400或115.2k,接收数据的回调函数是rxCB。定义函数rxCB来处理来自串口的数据,收到数据后触发相关的事件。
      在SimpleCollector.c中,向PC机传送数据的指令位于zb_ReceiveDataIndication()中,为
#if defined( MT_TASK )
    debug_str( (uint8 *)buf );
#endif
由于使用自定义的串口操作,需在预编译中去除MT_TASK,故无法使用其debug_str()函数向PC发送数据,将相关代码替换为
HalUARTWrite ( HAL_UART_PORT_0, buf, 32 ); 

2、接收来自PC的数据

收到数据后要通知OS收到了串口数据,在SimpleCollector.c中定义了相关的事件。

#define SERIAL_PORT_MSG_RCV_EVT    0x0008

定义指向数据的指针和数据长度

static uint8 *otaBuf = NULL; 
static uint8 otaLen;

rxCB的定义如下

void rxCB( uint8 port, uint8 event )
{
  uint8 *buf, len;
 
  if (!otaBuf)
  {
    if ( !(buf = osal_mem_alloc( SERIAL_PORT_RX_CNT )) )
    {
      return;
    }
  }
 
  len = HalUARTRead( port, buf, SERIAL_PORT_RX_CNT );
 
  if ( !len )  // Length is not expected to ever be zero.
  {
    osal_mem_free( buf );
    return;
  }
  
  otaBuf = buf;
  otaLen = len;
 
  osal_set_event( sapi_TaskID, SERIAL_PORT_MSG_RCV_EVT );
}

当数据接收完毕后,触发事件SERIAL_PORT_MSG_RCV_EVT。

3、Collector向Sensor发送指令

      从sapi.c中的

if ( events & ( ZB_USER_EVENTS ) )
{
  // User events are passed to the application
  zb_HandleOsalEvent( events );
 
  // Do not return here, return 0 later
}

可知,在zb_HandleOsalEvent()中处理用户自定义的事件,当收到PC传来的数据后触发了SERIAL_PORT_MSG_RCV_EVT,将无线发送指令的程序作为此事件的响应,原有的SimpleCollector.c中的void zb_HandleOsalEvent( uint16 event )是空的,为其添加以下内容

void zb_HandleOsalEvent( uint16 event )
{
  if ( event & SERIAL_PORT_MSG_RCV_EVT )
  {
    Collector_SendData( otaBuf, otaLen );
  }
}

其中Collector_SendData()的定义如下

void Collector_SendData( uint8 *buf, uint8 len )
{
  volatile uint16 tempDstAddr;
  tempDstAddr = buf[0];
  tempDstAddr = tempDstAddr << 8;
  tempDstAddr += buf[1];
  zb_SendDataRequest( tempDstAddr, COLLECTOR_CMD_ID, 1, &buf[2], 0, 0, AF_DEFAULT_RADIUS );
  osal_mem_free( otaBuf );
} 

其中的zb_SendDataRequest()即为通过无线发送指令的函数,其中的参数COLLECTOR_CMD_ID是Endpoint中的发送cluster。原有的Collector程序只能接受无线数据,不能发送无线数据,是通过设置发送cluster为实现的,增添发送cluster

#define NUM_OUT_CMD_COLLECTOR                1
 
const cId_t zb_OutCmdList[NUM_OUT_CMD_COLLECTOR] =
{
  COLLECTOR_CMD_ID
};
 
const SimpleDescriptionFormat_t zb_SimpleDesc =
{
  MY_ENDPOINT_ID,             //  Endpoint
  MY_PROFILE_ID,              //  Profile ID
  DEV_ID_COLLECTOR,          //  Device ID
  DEVICE_VERSION_COLLECTOR,  //  Device Version
  0,                          //  Reserved
  NUM_IN_CMD_COLLECTOR,      //  Number of Input Commands
  (cId_t *) zb_InCmdList,     //  Input Command List
  NUM_OUT_CMD_COLLECTOR,     //  Number of Output Commands
  (cId_t *) zb_OutCmdList   //  Output Command List
};
COLLECTOR_CMD_ID定义在WSNApp.h中 
#define COLLECTOR_CMD_ID                  1

三、Sensor的修改

      Sensor要增添的内容有:1.在endpoint中增加接收cluster;2.添加指令处理内容;3.增添外部AD采集内容。

1、修改Endpoint描述

      在SimpleSensor.c中添加及修改以下内容

#define NUM_IN_CMD_SENSOR                 1
 
const cId_t zb_InCmdList[NUM_IN_CMD_SENSOR] =
{
  COLLECTOR_CMD_ID
};
 
const SimpleDescriptionFormat_t zb_SimpleDesc =
{
  MY_ENDPOINT_ID,             //  Endpoint
  MY_PROFILE_ID,              //  Profile ID
  DEV_ID_SENSOR,              //  Device ID
  DEVICE_VERSION_SENSOR,      //  Device Version
  0,                          //  Reserved
  NUM_IN_CMD_SENSOR,          //  Number of Input Commands
  (cId_t *) zb_InCmdList,             //  Input Command List
  NUM_OUT_CMD_SENSOR,         //  Number of Output Commands
  (cId_t *) zb_OutCmdList     //  Output Command List
};

2、指令处理

      指令为一个byte,其各个bit定义为各采集数据的开关,在SimpleSensor.c中定义了以下位选常量

#define GLOBAL_SWITCH               0x80
#define TEMP_SWITCH                 0x40
#define BATT_SWITCH                 0x20
#define ADC_SWITCH                  0x10
当Sensor接到数据后会调用函数zb_ReceiveDataIndication(),为其添加以下内容
void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData  )
{
  if (command == COLLECTOR_CMD_ID)
  {
    HalUARTWrite ( HAL_UART_PORT_0, pData, 1 );
    if (GLOBAL_SWITCH & pData[0])
    {
      if (TEMP_SWITCH & pData[0])
      {
        osal_start_timerEx( sapi_TaskID, MY_REPORT_TEMP_EVT, myTempReportPeriod );
      }
      else
      {
        osal_stop_timerEx( sapi_TaskID, MY_REPORT_TEMP_EVT );
      }
      if (BATT_SWITCH & pData[0])
      {
        osal_start_timerEx( sapi_TaskID, MY_REPORT_BATT_EVT, myBatteryCheckPeriod );
      }
      else
      {
        osal_stop_timerEx( sapi_TaskID, MY_REPORT_BATT_EVT );
      }
    }
    else
    {
      myApp_StopReporting();
    }
  }
}

根据不同位的值来做出是否关停所有采集或特定的采集功能。

      至此,就可以通过PC机发送指令到Collector,再由Collector发送到Sensor,控制某项采集功能的开启/关闭。如网络中有短地址为0x796F的节点,要关闭其所有的采集功能,PC向Collector发送0x79、0x6F、0x80即可。

四、增添采集外部数据功能

      要添加采集外部数据的功能,Collector及Sensor都要添加响应的内容。

1、Sensor

      通过对Sensor部分的分析,需要定义采集时间,添加事件及相关的处理。定义采集时间

static uint16 myADCCheckPeriod = 1000;   // milliseconds

每隔1s采集一次数据。定义ADC取值的函数

      添加新事件MY_REPORT_ADC_EVT

#define MY_REPORT_ADC_EVT           0x0010
 

      在zb_HandleOsalEvent()中添加有关的内容

if ( event & MY_REPORT_ADC_EVT )
{
  // Read ADC7 value
  pData[0] = ADC_REPORT;
  pData[1] =  HalAdcRead(0x07, HAL_ADC_RESOLUTION_8);
  zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, 2, pData, 0, AF_ACK_REQUEST, 0 );
  osal_start_timerEx( sapi_TaskID, MY_REPORT_ADC_EVT, myADCCheckPeriod );
}

其中的ADC_REPORT定义为

#define ADC_REPORT      0x03

HalAdcRead(0x07, HAL_ADC_RESOLUTION_8)是Ti的协议栈自带的ADC驱动,以8bit分辨率读取AD7的值。

      在zb_ReceiveDataIndication()中添加有关的内容

if (ADC_SWITCH & pData[0])
{
  osal_start_timerEx( sapi_TaskID, MY_REPORT_ADC_EVT, myADCCheckPeriod );
}
else
{
  osal_stop_timerEx( sapi_TaskID, MY_REPORT_ADC_EVT );
}  

      接下来是myApp_StartReporting()和myApp_StopReporting()

void myApp_StartReporting( void )
{
  osal_start_timerEx( sapi_TaskID, MY_REPORT_TEMP_EVT, myTempReportPeriod );
  osal_start_timerEx( sapi_TaskID, MY_REPORT_BATT_EVT, myBatteryCheckPeriod );
  osal_start_timerEx( sapi_TaskID, MY_REPORT_ADC_EVT, myADCCheckPeriod );
  HalLedSet( HAL_LED_1, HAL_LED_MODE_ON );
}
 
void myApp_StopReporting( void )
{
  osal_stop_timerEx( sapi_TaskID, MY_REPORT_TEMP_EVT );
  osal_stop_timerEx( sapi_TaskID, MY_REPORT_BATT_EVT );
  osal_stop_timerEx( sapi_TaskID, MY_REPORT_ADC_EVT );
  HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF );
}
 

2、Collector

      Collector中有关的修改不多,集中在zb_ReceiveDataIndication(),先要定义常量ADC_REPORT

#define ADC_REPORT      0x03

再将zb_ReceiveDataIndication()中的

if ( pData[0] == BATTERY_REPORT )
{
...
}
else
{
...
}

修改为switch-case结构

switch( pData[0] )
{
case BATTERY_REPORT:
  tmpLen = (uint8)osal_strlen( (char*)strBattery );
  pBuf = osal_memcpy( pBuf, strBattery, tmpLen );
  
  *pBuf++ = (sensorReading / 10 ) + '0';    // convent msb to ascii
  *pBuf++ = '.';                            // decimal point ( battery reading is in units of 0.1 V
  *pBuf++ = (sensorReading % 10 ) + '0';    // convert lsb to ascii
  *pBuf++ = ' ';
  *pBuf++ = 'V';
  break;
case TEMP_REPORT:
  tmpLen = (uint8)osal_strlen( (char*)strTemp );
  pBuf = osal_memcpy( pBuf, strTemp, tmpLen );
 
  *pBuf++ = (sensorReading / 10 ) + '0';    // convent msb to ascii
  *pBuf++ = (sensorReading % 10 ) + '0';    // convert lsb to ascii
  *pBuf++ = ' ';
  *pBuf++ = 'C';
  break;
case ADC_REPORT:
  tmpLen = (uint8)osal_strlen( (char*)strADC );
  pBuf = osal_memcpy( pBuf, strADC, tmpLen );
 
  *pBuf++ = (sensorReading / 10 ) + '0';    // convent msb to ascii
  *pBuf++ = (sensorReading % 10 ) + '0';    // convert lsb to ascii
  break;
default:
  {}
}

Collector的预编译内容为

CC2430EB
HOLD_AUTO_START
SOFT_START
REFLECTOR
NV_INIT
xNV_RESTORE
HAL_UART
COLLECTOR

 

Sensor的预编译内容为

CC2430EB
NWK_AUTO_POLL
HOLD_AUTO_START
REFLECTOR
POWER_SAVING
NV_INIT

编译,下载后即可运行,使用串口调试工具,可以接受数据显示,并可向collector发送指令,控制sensor的采集行为。

      以下为要注意的操作事项,节点首次下载后,要先按下SW6,停止LED1的闪烁。之后按下SW1即可。

五、可视化数据显示

      在实际应用中,collector要配置相关的存储外设记录数据,或是配合上位机来接收所有数据进行处理及显示。基于Qt编写了一个专用的上位机程序,用来显示收到的数据,程序源码不公开,但可到http://download.csdn.net/source/2482714下载可执行程序。

      再有上位机的情况下,只需传送数据,而不用传送ASCII码,故可将collector的zb_ReceiveDataIndication()修改为

void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData  )
{
  uint8 buf[32];
  uint8 *pBuf;
  uint8 tmpLen;
  uint8 sensorReading;
 
  if (command == SENSOR_REPORT_CMD_ID)
  {
    // Received report from a sensor
    sensorReading = pData[1];
 
    // If tool available, write to serial port
 
    tmpLen = (uint8)osal_strlen( (char*)strDevice );
    pBuf = osal_memcpy( buf, strDevice, tmpLen );
    _ltoa( source, pBuf, 16 );
    pBuf += 4;
    *pBuf++ = ' ';
    *pBuf++ = pData[0];
    *pBuf++ = sensorReading;
    
    *pBuf++ = '/r';
    *pBuf++ = '/n';
    *pBuf = '/0';
    tmpLen = (uint8)osal_strlen( (char*)buf );
    HalUARTWrite ( HAL_UART_PORT_0, buf, tmpLen );   
  }
}

保留发送字符串strDevice[] = "Device:0x"及"/r/n",用做传送帧的帧头和帧尾。

上位机程序如图1、2所示。<节点>为网络中各节点的短地址,选择一个节点为当前显示节点,可以切换显示其电池电量、温度、ADC采集值。数据值显示在上方的文本框,数据绘制成波形显示在下侧。

图1

图2

六、扩展

      此系统只是接近实用,还没达到实用的目的,最欠缺的是电源管理,当采集间隔较长时可将节点转入休眠状态。可作以下扩展:

1.加入休眠功能;

2.上位机可发送指令调节Sensor的采集间隔。

      Zigbee入门指导系列到此完结,希望大家能够通过此系列教程掌握相关的使用方法,为之后的学习和应用打下基础。



你可能感兴趣的:(zigbee)