本文将以在GenericApp项目的基础上,一步一步地建立一个应用;需要实现的任务目标:1、使用Zigbee终端设备捕获串口中的字符“silverze” 。2、当Zigbee终端设备捕获到该字符串后,触发一个Zstack协议栈OSAL的任务消息 Get_Name。3、Zigbee终端设备在OSAL任务处理函数中处理Get_Name消息,并发送“Hello,silverze!”至协调器,协调器通过串口输出接收到Zigbee终端设备发送的内容。
首先,我们要让cc2530的串口在Zstack中使用起来,这一步骤可以参照我之前写的ZStack-CC2530-2.5.1a串口使用笔记一。
Zstack串口能够正常使用起来后,我们新建两个文件Silverze.c、Silverze.h保存在..\GenericApp\source\文件夹下并添加到IAR工程App虚拟目录下。
在MT_UART.c文件中,有一个串口接收数据的回调函数:
void MT_UartProcessZToolData ( uint8 port, uint8 event )
;为了减少对MT_UART.c文件的修改,我们将该回调函数指向到我们刚才新建立的Silverze.c文件中的函数:void Silverze_UartProcessZToolData ( uint8 port, uint8 event )
。
具体的函数代码:
/***************************************************************************************************
* @fn Silverze_UartProcessZToolData
*
* @brief 处理UART接收到的数据
*
* @param port - UART port
* event - Event that causes the callback
*
*
* @return None
***************************************************************************************************/
void Silverze_UartProcessZToolData ( uint8 port, uint8 event )
{
uint8 ch;
uint8 cnt = 0;
uint8* str = "silverze";
(void)event; // Intentionally unreferenced parameter
while (Hal_UART_RxBufLen(port))
{
HalUARTRead (port, &ch, 1);
if(ch == str[cnt])
cnt++;
else
cnt = 0;
if(cnt == strlen((char*)str))
{
HalLedSet( HAL_LED_3, HAL_LED_MODE_TOGGLE );//加入板子上LED toggle,便于调试
Silverze_SendGetNamdeOverMsg(); //接收到"silverze",发送OSAL任务消息
}
}
}
如果一切顺利,我们通过串口助手,发送包含“silverze”的字符串,就能看到开发板上(这个需要特定的开发板,根据使用的板子决定吧!)的LED3 Toggle。
完成步骤二的内容,后我们可以的应用已经能够通过串口捕获想要捕获的字符串了,而且我也将发送OSAL消息的函数结构给调用了。但这里还只是一个空壳,在完成该函数前;我们要先在Silverze.c文件中编写一个应用注册函数,这个函数很简单:
uint8 registTaskID;
/***************************************************************************************************
* @fn Silverze_RegisterTaskID
*
* @brief 注册该应用的任务ID
*
* @param task_id - 该应用任务的ID
*
* @return None
***************************************************************************************************/
void Silverze_RegisterTaskID( uint8 task_id )
{
registTaskID = task_id;
}
该函数会在GenericApp.c文件中的void GenericApp_Init( uint8 task_id )中调用,同时在Silverze.h文件中声明函数原型:
/*
* 注册该应用的任务ID
*/
extern void Silverze_RegisterTaskID( uint8 task_id );
我们在void GenericApp_Init( uint8 task_id )中调用上面的任务注册函数:
void GenericApp_Init( uint8 task_id )
{
GenericApp_TaskID = task_id;
GenericApp_NwkState = DEV_INIT;
GenericApp_TransID = 0;
// Device hardware initialization can be added here or in main() (Zmain.c).
// If the hardware is application specific - add it here.
// If the hardware is other parts of the device add it in main().
MT_UartInit (); //初始化串口
GenericApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
GenericApp_DstAddr.endPoint = 0;
GenericApp_DstAddr.addr.shortAddr = 0;
// Fill out the endpoint description.
GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
GenericApp_epDesc.task_id = &GenericApp_TaskID;
GenericApp_epDesc.simpleDesc
= (SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc;
GenericApp_epDesc.latencyReq = noLatencyReqs;
// Register the endpoint description with the AF
afRegister( &GenericApp_epDesc );
Silverze_RegisterTaskID( GenericApp_TaskID ); //注册自己的应用任务ID
// Register for all key events - This app will handle all key events
RegisterForKeys( GenericApp_TaskID );
// Update the display
#if defined ( LCD_SUPPORTED )
HalLcdWriteString( "GenericApp", HAL_LCD_LINE_1 );
#endif
ZDO_RegisterForZDOMsg( GenericApp_TaskID, End_Device_Bind_rsp );
ZDO_RegisterForZDOMsg( GenericApp_TaskID, Match_Desc_rsp );
#if defined( IAR_ARMCM3_LM )
// Register this task with RTOS task initiator
RTOS_RegisterApp( task_id, GENERICAPP_RTOS_MSG_EVT );
#endif
}
还有两个小步骤需要完成:
a、在Silverze.h头文件中定义一个结构:
typedef struct
{
osal_event_hdr_t hdr;
uint8* name;
} getName_t;
b、在comdef.h中定义宏
#define GET_NAME 0xE1 //串口获取到指定字符串事件
这里我们为什么要定义GET_NAME为0xE1呢?在ZComdef.h头文件中,我发现有很多系统消息被使用了,而且Zstack提示:
// OSAL System Message IDs/Events Reserved for applications (user applications)
// 0xE0 ?0xFC
所以,我们可以定义自己的应用消息范围0xE0 - 0xFC!
好了,现在准备开始编写Silverze_SendGetNamdeOverMsg( )函数的内容:
/***************************************************************************************************
* @fn Silverze_SendGetNamdeOverMsg
*
* @brief 发送从串口接收到指定数据后的消息给OSAL应用层
*
* @param None
*
* @return None
***************************************************************************************************/
static void Silverze_SendGetNamdeOverMsg( void )
{
getName_t *msgPtr;
msgPtr = (getName_t *)osal_msg_allocate( sizeof(getName_t) );//为带发送的消息指针申请内存空间
if(msgPtr)
{
msgPtr->hdr.event = GET_NAME;
msgPtr->name = "silverze";
osal_msg_send( registTaskID, (uint8 *)msgPtr); //将消息发给OSAL应用层
}
}
至此,我们已经将从串口捕获到指定字符串“silverze”的自定义应用消息发送给了GenericApp这个任务,接下来的事情就是到uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )函数中处理该应用消息了!
怎么样快速,确定我们上面的工作已经OK了?方法很简单啦,之前我们不是弄了一个LED灯来作为我们程序运行的指示吗。现在只要将之前在void Silverze_UartProcessZToolData ( uint8 port, uint8 event )中的
HalLedSet( HAL_LED_3, HAL_LED_MODE_TOGGLE );//加入板子上LED toggle,便于调试
注释掉,并移动到uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )函数中,而其捕捉到串口字符串后,我们还能看到相同的实验现象。
uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )
{
......
if ( events & SYS_EVENT_MSG )
{
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
......
case GET_NAME:
HalLedSet( HAL_LED_3, HAL_LED_MODE_TOGGLE );//加入板子上LED toggle,便于调试
break;
default:
break;
}
......
}
利用Zstack将数据发送出去,只需将终端设备、协调器建立连接后;调用
AF_DataRequest() 函数即可发送数据
根据该函数的参数,在GenericApp.c文件中定义目标地址类型参数,修改簇数组:
// This list should be filled with Application specific Cluster IDs.
const cId_t GenericApp_ClusterList[GENERICAPP_MAX_CLUSTERS] =
{
GENERICAPP_CLUSTERID,
SILVERZE_CLUSTERID //增加一个簇,在发送串口捕获成功信息时使用
};
afAddrType_t Silverze_DstAddr;//定义串口捕获字符串,数据发送地址类型
在GenericApp.h 增加一个簇 #define SILVERZE_CLUSTERID 2
并将簇的最大值修改为对应大小,以及在将簇数组初始化。
#define GENERICAPP_MAX_CLUSTERS 2 //增加一个簇 SILVERZE_CLUSTERID
#define GENERICAPP_CLUSTERID 1
#define SILVERZE_CLUSTERID 2
在void GenericApp_Init( uint8 task_id )函数中,初始化Silverze_DstAddr:
Silverze_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
Silverze_DstAddr.endPoint = 1;
Silverze_DstAddr.addr.shortAddr = 0;
接下来定义一个发送数据的函数,在该函数中调用AF_DataRequest()实现数据发送。
static void GenericApp_SendSilverzeMsg( char* name )
{
char hello[] = "Hello,"; //此处定义 char* hello = "Hello";strcat()函数会产生bug
char* say_hello = strcat( hello, name ); //拼接字符串为"Hello,silverze";此处注意strcat的使用!!
// printf("say_hello = %s\n", say_hello);
if ( AF_DataRequest( &Silverze_DstAddr, &GenericApp_epDesc,
SILVERZE_CLUSTERID,
strlen(say_hello) + 1,
(uint8*)say_hello,
&GenericApp_TransID,
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
// Successfully requested to be sent.
}
else
{
// Error occurred in request to send.
}
}
程序编写至此;我本以为在uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )函数中的switch-case语句中进行调用、发送即可:
case AF_INCOMING_MSG_CMD:
GenericApp_MessageMSGCB( MSGpkt );
break;
case GET_NAME:
GenericApp_SendSilverzeMsg( ((getName_t*)MSGpkt)->name );
break;
当然, GenericApp_MessageMSGCB( MSGpkt ) 函数已经修改成如下:
static void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
switch ( pkt->clusterId )
{
case GENERICAPP_CLUSTERID:
// "the" message
#if defined( LCD_SUPPORTED )
HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" );
#elif defined( WIN32 )
WPRINTSTR( pkt->cmd.Data );
#endif
#if debug
printf("绑定成功后,接收到周期数据!\n");
#endif
break;
case SILVERZE_CLUSTERID:
#if debug
HalLedSet( HAL_LED_3, HAL_LED_MODE_TOGGLE );//加入板子上LED toggle,便于调试
#endif
printf("%s\n", pkt->cmd.Data);
break;
}
}
此时,调试程序;发现ZStack触发不了 AF_INCOMING_MSG_CMD事件,后面想起该GenericApp需要操作开发板上按键,才能进行终端与协调绑定,建立连接。这里为了减少硬件的差异,我就让终端启动完成后,自动发起绑定请求,在uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )函数中添加下面代码:
if ( events & GENERICAPP_SEND_MSG_EVT )
{
if(cnt == 0)
{
cnt++;
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = 0x0000; // Coordinator
ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(),
GenericApp_epDesc.endPoint,
GENERICAPP_PROFID,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
FALSE );
}
完成,上面终端设备与协调器绑定后,且 AF_INCOMING_MSG_CMD事件的处理函数GenericApp_MessageMSGCB对应的SILVERZE_CLUSTERID簇id打印接收到的数据即可实现了。