辜渝傧

13037102709

027-87870986

教学实训

您当前的位置:首页 > 教学资源 > 实训方案 >

无线传感网络实训|实验5.3 Zstack无线收发实验之代码学习

发布者:唯众    布时间:2020-12-01 14:34:22    点击量:

1)、协议栈无线收发控制蜂鸣器

       ZIGBEE终端节点向协调器发送消息,协调器收到消息,则将蜂鸣器蜂鸣一下。

2)、Zstack工程讲解

打开“实验4.3 Zstack无线收发实验\ZStack-CC2530-2.3.0-1.4.0\Projects\zstack\Samples\SampleApp\CC2530DB”从软件开发专业角度讲建议大家复制工程到非中文目录,因为有些开发环境对中文路径支持的不好,虽然 IAR支持但在实际工作中你想别人看到你的工程,认为你很专业就照着上面做吧。我们演示就不修改,容易引起大家误会,打开工程如下图:

Zstack无线收发实验
带协议栈的工程怎么这么多文件夹和文件,都有什么用啊?现阶段大家只要带着这个疑问照着做实验就行了,后面实验接触多了自然就懂了。

3)、Zstack工程编译

编译协议器的程序,在Workspace下拉框中选择“CoordinatorEB-Pro”,在工程名 上点 右键选择”Rebuild All”,没错误提示再下载到开发板当中。尽量教大家用一些快捷方法。编译终端设备的程序选择“EndDeviceEB-Pro”编译下载即可。
Zstack无线收发实验
两个zigbee节点都下载好后,分别上电看效果吧。协调器、终端上电,组网成功后蜂鸣器蜂鸣。

4)、协议栈工作流程


看源码推荐大家使用Source_Insight,十分强大的工具,从事软件必备软件,除非你想做 菜鸟, 具体使用请参考相关资料与软件\Zigbee 参考资料\ Source Insight 使用教程.pdf。下面 列出实验中 涉及到比较重要的函数进行详解,由于是带协议 栈第一个实验,我们对源码也进行注 释,方 便习惯看源码的同志学习。我建议大家先看看下 面的文章,再阅读一次源码加深印象,后 面 的例子结构基本相 同,所以学好此实验,再做后 面的实验就得心应手了。用户自己添加的应 用任务程序在 Zstack 中的 。
调用过程: main()---> osal_init_system()---> osalInitTasks()---> SampleApp_Init()
下面我们就先从 main()函数开始吧。提示:如果你第一次接触 ZStack,第一个实验的代码看注释只须大概知道它们是做什么。

Zstack无线收发实验
5)、main函数讲解

int main(void)
{
osal_int_disable( INTS_ALL ); //关闭所有中断
HAL_BOARD_INIT(); //初始化系统时钟
zmain_vdd_check(); //检查芯片电压是否正常
InitBoard( OB_COLD ); //初始化 I/O ,LED 、Timer 等
HalDriverInit(); //初始化芯片各硬件模块
osal_nv_init( NULL ); //初始化 Flash 存储器
ZMacInit(); //初始化 MAC 层
zmain_ext_addr(); //确定 IEEE 64 位地址
zgInit(); //初始化非易失变量
#ifndef NONWK // Since the AF isn't a task, call it's initialization routine
afInit();
#endif osal_in it_system(); //初始化操作系统
osal_int_enable( INTS_ALL ); //使能全部中断
InitBoard( OB_READY ); //最终板载初始化
zmain_dev_info(); //显示设备信息
#ifdef LCD_SUPPORTED zmain_lcd_init(); //初始化 LCD
#endif
#ifdef WDT_IN_PM1 /* If WDT is used, this is a good place to enable it. */ WatchDogEnable( WDTIMX );
#endif osal_st art_system();// No Re turn from here 执行操作系统,进去后不会返回
return 0; // Shouldn't get here.
} // main()
       看了上面的代码后,可能感觉很多函数不认识。没关系刚开始大概了解流程即可,main 函 数 先执行初始化工作,包括硬件、网络层、任务等的初始化。然后执行 osal_start_system(); 操 作系统。进去后可不会回来了。 在这里,我们重点了解 2 个函数:初始化操作系统 osal_i nit_system();运行操作系统 osal_s tart_system();
       先来看 osal_init_system();系统初始化函数,进入函数。如果用 IAR 看代码可在函数名 上单击右键——go to definitio n of…,便可以进入函数。发现里面有 6 个初始化函数,这里 我们只关心 osalInitTasks(); 任务初始化函数,继续由该函数进入。先来看 osal_init_system();系统初始化函 数,进入函数。如果用 IAR 看代码可在函数名 上单击右键 ——go to definitio n of…,便可以进入函数。发现里面有 6 个初始化函数,这里 我们只关心 osalInitTasks(); 任务初始化函数,继续由该函数进入。
void osalInitTasks( void )
{
uint8 taskID = 0; // 分配内存,返回指向缓冲区的指针
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
// 设置所分配的内存空间单元值为 0
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
// 任务优先 级由高向低依次排列,高优先级对应 taskID 的值反而小 macTaskInit( taskID++ ); //macT askInit(0) ,用户不需考虑 nwk_init( taskID++ ); //nwk_ init(1),用户不需考虑
Hal_Init( taskID++ ); //Hal_I nit(2) ,用户需考虑
#if defined( MT_TASK ) //如果定义 MT_TASK 则调用 MT_TaskIn it() MT_TaskInit( taskID++ );
#endif APS_Init( taskID++ ); //APS_I nit(3) ,用户不需考虑
#if defined ( ZIGBEE_FRAGMENTATION ) APSF_Init( taskID++ );
#endif ZDApp_Init( taskID++ ); //ZDApp _Init(4) ,用户需考虑
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) ZDNwkMgr_Init( taskID++ );
#endif //用户创建的任务
SampleA pp_Init( taskID ); // SampleApp_Init _Init (5),用户需考虑。重要!
       函数对taskID进行初始化,每初始化一个,taskID++。大家看到了注释后面有些写着用户需要考虑,有些则写着用户不需考虑。没错,需要考虑的用户可以根据自己的硬件平台或者其他设置,而写着不需考虑的也是不能修改的。
       TI公司协议栈已完成。SampleApp_Init()是我们应用协议栈例程的必要函数,用户通常在这里初始化自己的东西。至此,osal_init_system();大概了解完毕。接下来看第二个函数osal_start_system();运行操作系统。同样用gotodefinition的方法进入该函数。
void osal_st art_system( void )
{
 #if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
uint8 idx = 0;
osalTimeUpdate(); //扫描哪个事 件被触发了,然后置相应的标志位 Hal_ProcessPoll(); //轮询 TIMER 与 UART
do {
if (tasksEvents[idx]) // Task is highest priorit y that is ready.
{
break; //得到待处理 的最高优先级任务索引号 idx
}
} while (++idx < tasksCnt);
if (idx < tasksCnt) {
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);// 进入临界区,保护
events = tasksEvents[idx]; //提取需要处理的任务中的事件
tasksEvents[idx] = 0; //清除本次任务的事件
HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区
events = (tasksArr[idx])( idx, events );//通过指针调 用任务处理函数,关键 HAL_ENTER_CRITICAL_SECTION(intState); //进入临界区
tasksEvents[idx] |= events;
/ / 保存未处理的事件 Add back unprocessed events to the current task. HAL_EXIT_CRITICAL_SECTION(intState); //退出临 界区
}
#if defined( POWER_SAVING )
else // Complete pass through all task events with no activity
{
osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
}
#endif
}
}
       我们看一下events=tasksEvents[idx];进入tasksEvents[idx]数组定义,发现恰好是osalInitTasks()函数里面分配空间初始化过的tasksEvents。而且taskID一一对应。这就是初始化与调用的关系。taskID把任务联系起来了。

6)、SampleApp_Init()用户应用初始化函数讲解

void SampleApp_Init( uint8 task_id )
{
SampleApp_TaskID = task_id;//osal 分配的任务 ID 随着用户添加任务的增多而改变
SampleApp_NwkState = DEV_INIT;//设备状态设 定为 ZDO 层中定义的初始化状态、初始化应用设备的网络类型,设备类型的改变都要产生一个事件—ZD O_STATE_CHANGE,从字面理解为 ZDO 状态发生了改变。所以在设备初始化的时候一定要 把它初始化为什么状态都没有。那么它就要去检测整个环境,看是否能重新建立或者加入存在的网络。但是有一种情况例外,就是
当 NV_ RESTORE 被设置的候(NV_RESTORE 是 把信息保存在非易失存储器中),那么需要恢复其网络状 态,而不需要重新建立或者加入网络了.这里需要设置 NV_RESTORE宏定义。
SampleApp_TransID = 0; //消息发送 I D(多消息时有顺序之分)
#if defined ( BUILD_ALL_DEVICES )
if ( readCoordinatorJumper() ) zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;
else
zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
 #endif // BUILD_ALL_DEVICES //该段的意思是,如果设置了 HOLD_AUTO_START 宏定义,将会在启动芯片的 时候会暂停启动 流程,只有外部触发以后才会启动芯片。其实就是需要一个按钮触发它 的启动流程。
#if defined ( HOLD_AUTO_START )
ZDOInitDevice(0);
#endif //设置发送数据的方式和目的地址寻址模式
//发送模式:广播发送
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;
//广播
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
//指定端点号
SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;
//指定目的网络地址为广播地址
//发送模式:组播发送 Setup for the flash command's destination address -Group 1
SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;
//组寻址
SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
//指定端点号
SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;
//组号 0 x0001
//定义本设 备用来通信的 APS 层端点描述
SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_epDesc.task_id = &SampleApp_TaskID; //SampleApp 描述符的任务 ID SampleApp_epDesc.simpleDesc = (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;//SampleApp 简单描述符
SampleApp_epDesc.latencyReq = noLatencyReqs; //延时策略
//向 AF 层登记描述符, 登记 endpoint description 到 AF,要对该应用进行初始化并在 AF 进行登记,告诉 应用层有这么一个 EP 已经开通可以使用,那么下层要是有关于该应用的信息或者应用要对下层做哪些操作,就自动得到下层的配合
afRegister( &SampleApp_epDesc ); //登记所有 的按键事件 RegisterForKeys( SampleApp_TaskID ); // By default, all devices start outin Group
SampleApp_Group.ID = 0x0001; //组号
osal_memcpy( SampleApp_Group.name, "Group 1", 7 ); //设定组名
aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//把该组登 记添加到 APS 中
#if defined ( LCD_SUPPORTED ) HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 );//如果支持 LCD,显示提示信息
#endif
}

7)、SampleApp_ProcessEvent() 用户应用任务的事件处理函数

uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
afIncomingMSGPacket_t *MSGpkt; (void)task_id; // Intentionally unreferenced paramete
if ( events & SYS_EVENT_MSG ) //接收系统消息再进行判断
{//接收属于本应用任务 SampleApp 的消息,以 SampleApp_TaskID 标记 MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) {// Received when a key is pressed
case KEY_CHANGE://按键事件
SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break;// Received when a messages is received (OTA) for this endpoint
case AF_INCOMING_MSG_CMD://接收数据事件,调用函数 AF_DataRequest()接收数据
SampleApp_MessageMSGCB( MSGpkt );//调用回调函数对收到的数据进行处理
break;
// Received whenever the device changes state in the network
case ZDO_STATE_CHANGE://只要网络状态发生改变,就通过 ZDO_STATE_CHANGE 事件通知所有的任务。同时 完成对协调器,路由器,终端的设置
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
//if ( (SampleApp_NwkState == DEV_ZB_COORD)//实验中协调器只接收数据所以取消发送事件
if ( (SampleApp_NwkState == DEV_ROUTER) || (SampleApp_NwkState == DEV_END_DEVICE) ) {//这个定时器只是为发送周期信息开启的,设备启动初始化后从这里开始触发第一个周期信息的发送,然后周而复始下去
osal_start_timerEx( SampleApp_TaskID, SAMPLEAPP_SEND_PERIODIC_MSG_EVT, SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
}
else {
// Device is no longer in the network
}
break;
default:
break;
} // Release the memory//事件处理完了,释放消息占用的内存 osal_msg_deallocate( (uint8 *)MSGpkt );//指针指向下一个放在缓冲区的待处理的事件,返回 while ( MSGpkt )重新处理事件,直到缓冲区没有等 待处理事件为止
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
}// return unprocessed events //返回未处理的事件
return (events ^ SYS_EVENT_MSG); }// Send a message out - This event is generated by a timer// (setup in SampleApp_Init()).
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT ) {//处理周期性事件,利用 SampleApp_SendPeriodicMessage()处理完当前的周期性事件,然后启动定时器 开启下一个周期性事情,这样一种循环下去,也即是上面说的周期性事件了,可以做为传感器定时采集、上传任务 SampleApp_SendPeriodicMessage();// Setup to send message again in normal period (+ a little jitter)
osal_start_timerEx(SampleApp_TaskID,SAMPLEAPP_SEND_PERIODIC_MSG _EVT, (SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );// return unprocessed events 返回未处理的事件
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
} // Discard unknown events
return 0;
}

8)、SampleApp_MessageMSGCB()数据接收函数

//接收数据,参数为接收到的数据
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
byte buf[3];
switch ( pkt->clusterId )//判断簇 ID
{
case SAMPLEAPP_PERIODIC_CLUSTERID://收到广播数据
osal_memset(buf, 0 , 3); osal_memcpy(buf, pkt->cmd.Data, 2);//复制数据到缓冲区中
if(buf[0]=='D' && buf[1]=='1')//判断收到的数据是否为“D1”
{
HalLedBlink(HAL_LED_1, 0, 50, 500);//如果是则 Led1 间隔 500ms 闪烁
#if defined(ZDO_COORDINATOR)//协调器收到"D1"后,返回"D1"给终端,让终端 Led1 也闪烁
SampleApp_SendPeriodicMessage();
#endif
}
else {
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
}
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;
}
}

9)、SampleApp_SendPeriodicMessage()发送周期信息函数

void SampleApp_SendPeriodicMessage( void )
{
byte SendData[3]="D1";// 调用 AF_DataRequest 将数据无线广播出去 if( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc, SAMPLEAPP_PERIODIC_CLUSTERID, 2, SendData, &SampleApp_TransID, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) {
} else { HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);// Error occurred in request to send.
}
}

10)、AF_DataRequest()发送函数

AF_DataRequest( &SampleApp_Periodic_DstAddr,//发送目的地址+端点地址和传送模式
&SampleApp_epDesc,//源(答复或确认)终端的描述(比如操作系统中任务 ID 等)源
EP SAMPLEAPP_PERIODIC_CLUSTERID,//被 Profile 指定的有效的集群号
2, //发送数据长度
SendData,//发送数据缓冲区
&SampleApp_TransID,//任务ID 号
AF_DISCV_ROUTE,//有效位掩码的发送选项
AF_DEFAULT_RADIUS )//传送跳数,通常设置为AF_DEFAULT_RADIUS 好了,第一次就讲这么多吧,内容很多但非常重要,最好理解后再去做后面的实现,打好坚实的基础后,再去看后面的实验相对容易很多。
 

7.实验步骤

1)、选择CoodinatorEB-Pro,下载到开发板A;作为协调器
2)、选择EndDeviceEB-Pro,下载到开发板B;作为终端设备
3)、给两块开发板上电,协调器蜂鸣器长鸣3秒表示有终端连接成功

8.实验现象

       先给协调器上电,然后再给终端节点上电,观察终端节点OLED显示的内容。当跳过开机“武汉唯众智创科技”的界面后,表示组网成功,此时按下按键,协调器将蜂鸣3秒,然后停止。如图:
Zstack无线收发实验
 


上一篇:无线传感网络实训|实验5.3 Zstack无线收发实验

下一篇:无线传感网络实训|实验5.4 Zstack串口实验