Zigbee入门指导(二)——运行Zigbee例程

http://blog.csdn.net/logiclimit/article/details/5681932

 

在Zigbee入门指导(一)中讲解了基于CC2430的Zigbee开发环境的搭建,安装完Ti的协议栈后,里面有多个例程,帮助用户入门及作为自己工程的基本框架。在Zigbee入门指导(二)中,我们将通过演示执行相关的例程,了解Zigbee应用的启动流程(不是Zigbee网络的启动流程),了解运行一个自定义Zigbee工程所要作的软件方面的改动和工程选项的配置。所用的开发套件为无线龙的套件。

 

一、修改HAL

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

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

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第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工程设置

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

      打开工程后,界面如图2

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第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所示

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第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,先要选择协议及物理设备的芯片类型。

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第4张图片

图4 协议及芯片类型

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

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第5张图片

图5

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

Zigbee入门指导(二)&mdash;&mdash;运行Zigbee例程_第6张图片

图6

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

APS_HelloWorld

图7

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

六、进阶和提高

      自行学习以下内容

1、尝试运行其它例程,有使用LCD的地方改成用串口输出

2、编写相关的LCD驱动

3、配合Packet Sniffer了解Zigbee网络建立和数据收发过程

你可能感兴趣的:(Zigbee入门指导(二)——运行Zigbee例程)