ZIGBEE通讯-10.ZigBee协议栈的无线点灯

在ZIGBEE协议栈中已经自带了按键与LED的驱动与使用函数,所以只需要将按键与LED修改为使用的开发板所连接IO就可以使用了。接下来将主要分析在协议栈中按键的初始化、按键的检测以及按键事件的传递与处理。按键流程分析过后,着手于无线数据传输,而协议栈已经写好了无线广播,只需要直接调用就可以使用了。

1、修改LED灯IO

由于协议栈中按键与LED所配置IO口与使用的开发板不同,所以需要对按键LED的IO口进行修改。

在协议栈ZMain.c文件中的main函数中找到函数HAL_BOARD_INIT(),右键go to进入该函数,可以看到关于LED的配置,由于本实验使用的开发板只有两个LED,所以我们只需要修改LED1、LED2的IO口就可以了。同样的,利用右键找到LED底层定义的地方(文件)hal_board_cfg.h中,将其修改如下:

/* 1 - Green */
#define LED1_BV           BV(3) // BV(0)改为BV(3)
#define LED1_SBIT         P1_3  // P1_0改为P1_3
#define LED1_DDR          P1DIR
#define LED1_POLARITY     ACTIVE_HIGH

/* 2 - Red */
#define LED2_BV           BV(2) // BV(1)改为BV(2)
#define LED2_SBIT         P1_2 // P1_1改为P1_2
#define LED2_DDR          P1DIR
#define LED2_POLARITY     ACTIVE_HIGH

LED的开与关也需要进行修改,原理同不带协议栈的无线点灯实验。在函数HAL_BOARD_INIT()中,右键进入HAL_TURN_OFF_LED1()中,可以找到两个LED的开关定义,修改如下:

#define HAL_TURN_OFF_LED1()       st( LED1_SBIT = LED1_POLARITY (1); ) // 括号中0改为1
#define HAL_TURN_OFF_LED2()       st( LED2_SBIT = LED2_POLARITY (1); ) // 括号中0改为1
#define HAL_TURN_OFF_LED3()       st( LED3_SBIT = LED3_POLARITY (0); )
#define HAL_TURN_OFF_LED4()       HAL_TURN_OFF_LED1()

#define HAL_TURN_ON_LED1()        st( LED1_SBIT = LED1_POLARITY (0); ) // 括号中1改为0
#define HAL_TURN_ON_LED2()        st( LED2_SBIT = LED2_POLARITY (0); ) // 括号中1改为0
#define HAL_TURN_ON_LED3()        st( LED3_SBIT = LED3_POLARITY (1); )
#define HAL_TURN_ON_LED4()        HAL_TURN_ON_LED1()

2、按键IO修改与流程分析

在main函数中找到函数HalDriverInit(),右键进入其中,可以看到有很多外设的初始化,其他的在这里我们都不关心,我们只关注按键KEY,找到KEY的初始化函数右键进入其中,KEY_SW_6即为接下来我们要用的按键 ,JOY为摇杆按键,我们用不到,不用关心或者注释掉。右键进入到HAL_KEY_SW_6_SEL定义的地方,进行如下修改:

/* SW_6 is at P1.1 */
#define HAL_KEY_SW_6_PORT   P1 // P0改为P1
#define HAL_KEY_SW_6_BIT    BV(1)
#define HAL_KEY_SW_6_SEL    P1SEL // P0SEL改为P1SEL
#define HAL_KEY_SW_6_DIR    P1DIR // P0DIR改为P1DIR
/* SW_6 interrupts */
#define HAL_KEY_SW_6_IEN      IEN2  // IEN1改为IEN2
#define HAL_KEY_SW_6_IENBIT   BV(5) // 5改为4 IEN2第4位P1中断
#define HAL_KEY_SW_6_ICTL     P1IEN // P0IEN改为P1IEN
#define HAL_KEY_SW_6_ICTLBIT  BV(1) /* P1EN – P1.1 enable/disable bit */
#define HAL_KEY_SW_6_PXIFG    P1IFG /* Interrupt flag at source */

按键初始化后, InitBoard( OB_READY )用来配置按键触发方式以及按键的回调函数,如下:

/*********************************************************************
 * @fn      InitBoard()
 * @brief   Initialize the CC2420DB Board Peripherals
 * @param   level: COLD,WARM,READY
 * @return  None
 */
void InitBoard( uint8 level )
{
  if ( level == OB_COLD )
  {
    // IAR does not zero-out this byte below the XSTACK.
    *(uint8 *)0x0 = 0;
    // Interrupts off
    osal_int_disable( INTS_ALL );
    // Check for Brown-Out reset
    ChkReset();
  }
  else  // !OB_COLD
  {
    /* Initialize Key stuff */
    // 配置为按键中断不使能,扫描触发        回调函数
    HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE, OnBoard_KeyCallback);
  }
}

进入HalKeyConfig函数中,根据所传参数HAL_KEY_INTERRUPT_DISABLE,中断不使能的话,就会配置为定时检测,这个时候会触发HAL_KEY_EVENT事件:

else    /* Interrupts NOT enabled */
  {
    HAL_KEY_SW_6_ICTL &= ~(HAL_KEY_SW_6_ICTLBIT); /* don't generate interrupt */
    HAL_KEY_SW_6_IEN &= ~(HAL_KEY_SW_6_IENBIT);   /* Clear interrupt enable bit */

    osal_set_event(Hal_TaskID, HAL_KEY_EVENT); // 触发HAL_KEY_EVENT事件
  }

这个事件将会在HAL层事件处理函数中被处理,在OSAL_SampleApp.c文件中找到HAL层事件处理函数Hal_ProcessEvent,进入其中,可以看到这样一个判断:

if (events & HAL_KEY_EVENT) // HAL_KEY_EVENT事件
  {

#if (defined HAL_KEY) && (HAL_KEY == TRUE)
    /* Check for keys */
    HalKeyPoll(); // 检测按键

    /* if interrupt disabled, do next polling */
    if (!Hal_KeyIntEnable)
    {
      osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
    }
#endif // HAL_KEY

    return events ^ HAL_KEY_EVENT;
  }

如果判断成功,就会调用HalKeyPoll()对按键进行扫描,进入这个扫描函数,函数前一部分都是跟摇杆按键有关的,可以直接删除或者注释,或者将下部分对KEY_SW_6按键的判断放在前面,不然会影响按键的检测,如下:

/**************************************************************************************************
 * @fn      HalKeyPoll
 *
 * @brief   Called by hal_driver to poll the keys
 *
 * @param   None
 *
 * @return  None
 **************************************************************************************************/
void HalKeyPoll (void)
{
  uint8 keys = 0;


  if (HAL_PUSH_BUTTON1()) // 按键检测判断
  {
    keys |= HAL_KEY_SW_6;
  }

  /* Invoke Callback if new keys were depressed */
  if (keys && (pHalKeyProcessFunction))
  {
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL); // 调用回调函数
  }
}

由于官方板按键按下去是高电平,所以检测if判断里读取按键状态的函数需要进行修改,右键goto之后,将PUSH1_POLARITY的定义以及PUSH1_SBIT的定义进行修改,如下:

/* S1 */
#define PUSH1_BV          BV(1)
#define PUSH1_SBIT        P1_1 // P0_1 改为 P1_1
#define PUSH1_POLARITY    ACTIVE_LOW // ACTIVE_HIGH改为ACTIVE_LOW

最后函数调用按键回调函数将按键检测值传进去,又回到了一开始初始化时配置的回调函数。在回调函数OnBoard_KeyCallback()中,我们并不会在这里对按键事件进行处理,而是通过函数OnBoard_SendKeys( keys, shift )将检测值又传递到了其他地方,如下:

/*********************************************************************
 * @fn      OnBoard_SendKeys
 *
 * @brief   Send "Key Pressed" message to application.
 *
 * @param   keys  - keys that were pressed
 *          state - shifted
 *
 * @return  status
 *********************************************************************/
uint8 OnBoard_SendKeys( uint8 keys, uint8 state )
{
  keyChange_t *msgPtr;

  if ( registeredKeysTaskID != NO_TASK_ID )
  {
    // Send the address to the task
    msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );
    if ( msgPtr )
    {
      msgPtr->hdr.event = KEY_CHANGE; // 按键事件
      msgPtr->state = state;
      msgPtr->keys = keys;

      osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr ); // 把事件发往绑定的任务
    }
    return ( ZSuccess );
  }
  else
    return ( ZFailure );
}

在这个函数里,又将按键检测值标记为KEY_CHANGE事件,通过osal_msg_send()函数将事件打包发给了registeredKeysTaskID任务,registeredKeysTaskID为任务号,通过RegisterForKeys()进行注册,注册在APP层里面,现在我们去APP层的初始化任务中查找一下,可以通过osal_init_system()函数找到osalInitTasks()函数,在任务初始化函数中调用了APP层的初始化函数,在SampleApp_Init()中调用了RegisterForKeys(),把按键事件绑定在了APP层,如下:

// Register for all key events - This app will handle all key events
  RegisterForKeys( SampleApp_TaskID ); // 这句代码在SampleApp_Init()中

所以按键事件最后会到达APP层,被APP层事件处理函数处理,如下图:

ZIGBEE通讯-10.ZigBee协议栈的无线点灯_第1张图片

 最后如果事件判断成功,就会调用SampleApp_HandleKeys()函数,在这个函数中,我们就可以对按键事件作出相应,比如接下来我们要做的,通过调用无线发送函数,发送一个数据,如下:

void SampleApp_HandleKeys( uint8 shift, uint8 keys )
{
  (void)shift;  // Intentionally unreferenced parameter
  if(keys & HAL_KEY_SW_6)
  {
    SampleApp_SendPeriodicMessage(); // 广播发送一个数据
   }
}

在广播发送函数中,将会发送出去一个0,如下:

void SampleApp_SendPeriodicMessage( void )
{
  if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
                       SAMPLEAPP_PERIODIC_CLUSTERID, // 数据类型ID 现在为表示广播
                       1, // 发送数据长度
                       (uint8*)&SampleAppPeriodicCounter, // 发送数据的地址 变量值为0
                       &SampleApp_TransID,
                       AF_DISCV_ROUTE,
                       AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
  {
  }
  else
  {
    // Error occurred in request to send.
  }
}

3、LED灯响应

发送部分完成之后,再来看接收部分。小灯响应部分比较简单,首先需要关注数据的来源,来源就是无线广播接收,这个事件的处理也在APP层事件处理函数中,如图:

ZIGBEE通讯-10.ZigBee协议栈的无线点灯_第2张图片

 在事件处理函数中,会调用SampleApp_MessageMSGCB( MSGpkt )函数对接到的数据包进行处理,判断如果接到的数据为0,即为接收正确,就可以让小灯响应了,如下:

void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
  uint16 flashTime;

  switch ( pkt->clusterId )
  {
    case SAMPLEAPP_PERIODIC_CLUSTERID: // 判断为广播数据
      if( *pkt->cmd.Data == 0) // 从数据包中找到用户数据
      {
        HalLedSet(1,HAL_LED_MODE_TOGGLE); // 切换LED的状态
      }
      break;

    case SAMPLEAPP_FLASH_CLUSTERID:
      flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
      HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
      break;
  }
}

从数据包中找到用户数据有必要说明一下,首先函数传进来的参数类型为afIncomingMSGPacket_t,这个结构体内存放了所有的数据,如下:

typedef struct
{
  osal_event_hdr_t hdr;     /* OSAL Message header */
  uint16 groupId;           /* Message's group ID - 0 if not set */
  uint16 clusterId;         /* Message's cluster ID */
  afAddrType_t srcAddr;     /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP,
                               it's an InterPAN message */
  uint16 macDestAddr;       /* MAC header destination short address */
  uint8 endPoint;           /* destination endpoint */
  uint8 wasBroadcast;       /* TRUE if network destination was a broadcast address */
  uint8 LinkQuality;        /* The link quality of the received data frame */
  uint8 correlation;        /* The raw correlation value of the received data frame */
  int8  rssi;               /* The received RF power in units dBm */
  uint8 SecurityUse;        /* deprecated */
  uint32 timestamp;         /* receipt timestamp from MAC */
  uint8 nwkSeqNum;          /* network header frame sequence number */
  afMSGCommandFormat_t cmd; /* Application Data 用户数据*/
} afIncomingMSGPacket_t;

这个结构体里面又包含了一个结构体afMSGCommandFormat_t,这个结构体用于存放用户数据,如下:

typedef struct
{
  uint8   TransSeqNumber;
  uint16  DataLength; // Number of bytes in TransData
  uint8  *Data; // 数据
} afMSGCommandFormat_t;

而我们通过无线发送的数据就在以Data为首地址的内存中。

实验完成,将程序分别下载到协调器和终端节点。点击协调器的按键,终端节点的LED状态会随之切换。

你可能感兴趣的:(ZIGBEE通讯)