在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层事件处理函数处理,如下图:
最后如果事件判断成功,就会调用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层事件处理函数中,如图:
在事件处理函数中,会调用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状态会随之切换。