http://blog.csdn.net/logiclimit/article/details/5681932
在Zigbee入门指导(一)中讲解了基于CC2430的Zigbee开发环境的搭建,安装完Ti的协议栈后,里面有多个例程,帮助用户入门及作为自己工程的基本框架。在Zigbee入门指导(二)中,我们将通过演示执行相关的例程,了解Zigbee应用的启动流程(不是Zigbee网络的启动流程),了解运行一个自定义Zigbee工程所要作的软件方面的改动和工程选项的配置。所用的开发套件为无线龙的套件。
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
图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
#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;
}
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修改。
下面将以运行一个工程<GenericApp>为例,介绍Zigbee工程设置。CC2430的开发环境为IAR,相信接触过MSP430的朋友对其不会陌生,有关Zigbee工程的设置实际上就是通过IAR的工程设置完成的。打开<Projects/zstack/Samples/GenericApp/CC2430DB>下的GenericApp.eww。
打开工程后,界面如图2
图2
同一个工程中包含的多个project,根据开发板的类型(EB和DB)、Zigbee网络中的节点类型(Coordinator、Router和EndDevice)一共有六个project,我们只需用到CoordinatorEB和EndDeviceEB这两个project。先把project选择为CoordinatorEB,在GenericApp-CoordinatorEB上右击,选择Options…进入工程设置界面,选择C/C++ Compiler->Preprocessor,设置预编译项。去掉LCD_SUPPORTED=DEBUG,HAL_UART在GenericApp中可加可不加,如图3所示
图3
通过条件编译选项设定是否添加相关的硬件驱动和调试选项,去掉LCD_SUPPORTED=DEBUG的原因是Ti的开发板和无线龙开发板的LCD接口不一样,而我们又没有修改相关Hal文件,无法使用LCD显示,加入HAL_UART的原因是我们将使用串口作为调试信息的输出。
用户相关的应用程序代码是在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的关联将在以下的流程解析中讲解。
编译工程(快捷键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,追踪其定义(注意“追踪”的方法,下文所述的追踪都是指用这个方法)。
osal_init_system()定义在OSAL Group的OSAL.c中,进行了操作系统相关的初始化工作,需要重点关注的是
927: osalInitTasks();
追踪其定义。
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()正是关注的重点,追踪它。
GenericApp_Init()位于GenericApp.c中,完成了endPoint的设定和注册、按键事件的注册,即把相关的enevt和相关的task进行了关联,当发生这些event时,会由和相应task关联的事件处理函数进行处理。到此,已经了解了如何添加task、如何关联event和task,那么task和事件处理函数的关联又是如何进行的呢?先找找GenericApp_Init对应的事件处理函数,解决这个问题,要回到ZMain.c中main()里的osal_start_system(),追踪它吧!
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和对应的事件处理函数的关联。
上面我们了解了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);
}
对数据进行收发还需对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;
};
至此,对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;
}
}
编译后分别下载,即可运行。
为了了解有关的无线信号,使用Packet Sniffer侦测空中的Zigbee信号,使用一个SmartRF04EB+CC2430模块配合Packet Sniffer软件即可完成上述功能。
运行Packet Sniffer,先要选择协议及物理设备的芯片类型。
图4 协议及芯片类型
之后的显示面板的功能如图
图5
运行后会把数据划分后显示出来
图6
各部分含义请自行学习。观察两个模块相互发送的”Hello Word”,用户数据是在APS层,找到一段含APS Payload的数据包,内容为
图7
48 65 6C 6C 6F 20 57 6F 72 6C 64 00即为Hello World的字符串。
自行学习以下内容
1、尝试运行其它例程,有使用LCD的地方改成用串口输出
2、编写相关的LCD驱动
3、配合Packet Sniffer了解Zigbee网络建立和数据收发过程