zigbee学习参考(1~42 )


【原创】ZigBee学习之1——SPI&LCD - 小组 - EDN China

【原创】ZigBee学习之2——SPI&LCD - 小组 - EDN China

ZigBee学习之3——USART之UART - longhaihai's Blog -EDN Chi...

【原创】ZigBee学习之4——DMA - longhaihai's Blog -EDNChina...

【原创】ZigBee学习之6——无线电2 -小组 -EDN China

【原创】ZigBee学习之7——OSAL(操作系统抽象层)API解读 - longha...

 

【原创】ZigBee学习之8——对ZigBee地址的理解 - longhaihai's Bl...

【原创】ZigBee学习之9——Z-Stack编译选项.rtf - longhaihai's B...

ZigBee学习之10——MAC层API解读_百度文库

【原创】ZigBee学习之11——MAC层API解读2 - longhaihai's Blog -...

【原创】ZigBee学习之12——对ZDO的初步理解 - longhaihai's Blog...

【原创】ZigBee学习之13——ZStack API解读 - longhaihai's Blog ...

【原创】ZigBee学习之14——ZStack API解读2 - longhaihai's Blog...

 

【原创】ZigBee学习之15——ZStack API解读3 - longhaihai's Blog...

ZigBee学习之16——ZStack API解读4_百度文库

【原创】ZigBee学习之17——ZStack API解读5 - longhaihai's Blog...

【原创】ZigBee学习之18——ZCL解读 - longhaihai's Blog -EDN C...

【原创】ZigBee学习之19——如何创建自己的简单应用 - longhaihai...

 

【原创】ZigBee学习之20——SimpleAPP分析 - longhaihai's Blog -...

 

ZigBee学习之22—— HAL_BOARD_INIT()_百度文库

【原创】ZigBee学习之23—— zmain_vdd_check() - longhaihai's B...

【原创】ZigBee学习之24——zmain_ram_init() -小组 -EDN China

【原创】ZigBee学习之25——InitBoard -小组 -EDN China

ZigBee学习之26——HalDriverInit()_百度文库

 

【原创】ZigBee学习之27——osal_nv_init - longhaihai's Blog - ...

 ZigBee学习之28——zgInit() - longhaihai's Blog -EDN ...

...ZigBee学习之29——ZMacInit() - longhaihai's Blog -EDN Chi...

 

【原创】ZigBee学习之30——afInit() - longhaihai's Blog -EDN ...

【原创】ZigBee学习之31——osal_init_system - longhaihai's Blo...

【原创】ZigBee学习之32—— zmain_dev_info() -小组 -EDN China

【原创】ZigBee学习之33——osal_start_system() - longhaihai's ...

...ZigBee学习之35——按键部分及系统调用时钟的分析2 -小组 - E...

【原创】ZigBee学习之36——simpleapp分析-续 - longhaihai's Blo...

【原创】ZigBee学习之37——osalInitTasks()分析 - longhaihai's ...

【原创】ZigBee学习之38——初步理解Home Automation Profile - ...

【原创】ZigBee学习之39——Home Automation Profile2 - longhaih...

【原创】ZigBee学习之40——Home Automation Profile3 - longhaih...

【原创】ZigBee学习之41——SimpleSwitch - longhaihai's Blog - ...

【原创】ZigBee学习之42——协议栈中的串口操作 - longhaihai's B...

ZigBee学习之

学习之1——点灯

点灯点灯

点灯

我用的编译环境是IARfor51 7.30B,再介绍一下我的开发板的情况,ZigBee模块用的是CC2430.这块芯片是一款Soc

的芯片,集成了8051内核和ZigBee射频部分,只要很少的外部电路就可以搭建一个射频模块。因为其中的是8051的

内核,所有对于熟悉8051系列的同学们来说,基本的一些操作就很简单了,这里我们在温习一下,顺便对CC2430芯片

进行一下熟悉。嵌入式的一大特点是其底层的软件和硬件紧密相关,如果没有数字电路的知识,那么作起来是很困难的。

我们先介绍一下基本的管脚分配情况:P0的2和3接到了一个232电压转换芯片上,可以用来和PC进行通信,

LCD_RST,LCD_CS,接的是LCD的复位和片选端,这里我用到的LCD是兼容PCD8544的NOkia5110的LCD,为SPI

总线LCD,84X48点阵。CC2430_MOSI,CC2430_CLK都接到LCD上了。P1_0接到一个调试LED上面,为高电平点亮。

下面的第一个实验就很简单了,我们只要把P1_0配置为通用IO,输出方式,然后从这里输出高电平,那么就可以点亮

这个LED了。用到的寄存器为:

P1配置寄存器P1SEL,

P1方向寄存器P1DIR

程序如下:

#include<ioCC2430.h>

#define DEBUG_LED P1_0

void delay(unsigned int n){

//26 cycles delay

while(--n)asm("NOP");

}

void LEDInit(){

P1SEL &= 0XFE; //P1_0 defined General purpose I/O

P1DIR |= 0X01; //P1_0 defined Output

}

main(){

LEDInit();

while(1){

DEBUG_LED = 1; //Led light

delay(50000);

DEBUG_LED = 0;

delay(50000);

}

}

编译通过以后就用TI的官方工具SmartRF Flash Programmer将生成的HEX文件,烧录到CC2430吧,激动的时刻,呵

呵如果点亮了这个小小的LED那么恭喜你,你的硬件因该是没有问的了,一个小小的胜利后我们就有更多的勇气和信

心前进咯! ZigBee学习之

学习之学习之

学习之2——SPI&LCD 今天的任务是用SPI总线方式点亮LCD屏幕,呵呵这里要涉及到两个内容,一个是SPI总线,一个LCD。

CC2430的话已经集成了SPI总线,只要将IO口配置为外设,然后将USART配置成SPI方式就可以了。

关于LCD其实也很简单,很多没有接触过的同学可能将其想象的太过复杂了,就是将字或者图像转换为一个一个的点,

如果要这点显示东西呢,就把这点点亮,否则则不点亮。其实PCD8544已经将很多细节的东西做好了,我们要做的呢,

就是发送一串命令序列,然后发送要显示的数据就可以了。看看PCD8544的芯片资料,我们还会发现在最后它竟然给

出了操作实例,呵呵相信大家都能看懂的吧,如果有看不懂的可以给我留言或者是QQ联系我哦,我如果知道的话一定

给大家详细的答复!

CC2430的SPI的是放在USART外设里面的,和UART放在一起,同一个USART即可以配置成UART也可以配置成

SPI,SPI的主从模式通过相关的寄存器来选择。关于管脚的分配也不难,这里就不多说了,其实这里还算是8051的基

础实验,对于8051很熟悉的同学肯定觉得我是在说废话了,哈哈!

这里只提一下关于SPI应该注意的一点问题:

1、PCD8544 的SPI是高电平采样,所以,主机端必须是高电平之前要把数据准备好。所以主机端的发送(MOSI)因该

设为下降沿采样。

当用SPI和PCD8544通信时,速率不能太低,否则点不亮LCD,或者是出来乱码

下面是引用网友的SPI总线心得,我觉得看看很有益处:

SPI接口时钟配置心得:

在主设备这边配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,

因为主设备这边的时钟极性和相位都是以从设备为基准的。因此在时钟极性的配置上一定要

搞清楚从设备是在时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据。

但要注意的是,由于主设备的SDO连接从设备的SDI,从设备的SDO连接主设备的SDI,

从设备SDI接收的数据是主设备的SDO发送过来的,主设备SDI接收的数据是从设备SDO发送过来的,

所以主设备这边SPI时钟极性的配置(即SDO的配置)跟从设备的SDI接收数据的极性是相反的,

跟从设备SDO发送数据的极性是相同的。下面这段话是Sychip Wlan8100 Module Spec上说的,

充分说明了时钟极性是如何配置的:

The 81xx module will always input data bits at the rising edge of the clock,

and the host will always output data bits on the falling edge of the clock.

意思是:主设备在时钟的下降沿发送数据,从设备在时钟的上升沿接收数据。

因此主设备这边SPI时钟极性应该配置为下降沿有效。

又如,下面这段话是摘自LCD Driver IC SSD1289:

SDI is shifted into 8-bit shift register on every rising edge of SCK in the order

of data bit 7, data bit 6 …… data bit 0.

意思是:从设备SSD1289在时钟的上升沿接收数据,而且是按照从高位到地位的顺序接收数据的。

因此主设备的SPI时钟极性同样应该配置为下降沿有效。

时钟极性和相位配置正确后,数据才能够被准确的发送和接收。

因此应该对照从设备的SPI接口时序或者Spec文档说明来正确配置主设备的时钟。  老规矩,上程序:

先来一个不用SPI方式控制LCD显示的实例:

#include "periodef.h"

#include <string.h>

void delay(uint n){

//26 cycles delay

while(--n)asm("NOP");

}

void LCD_IOInit(){

P0SEL &=~ 0X03;

P0DIR |= 0X03;

P1SEL &=~ 0XF0;

P1DIR |= 0XF0;

}

void LCD_WriteOneByte(uchar data){

uchar i = 0;

for(i=0;i<8;i++){

LCD_SCK = 0;

if((data<<i)&0x80){

LCD_SIN = 1;

}

else{LCD_SIN = 0;}

LCD_SCK = 1;

}

}

void LCD_WriteMByte(uchar *data,uint num){

while(num--){

LCD_WriteOneByte(*data);

data++;

}

}

void LCD_Init(){

LCD_nCS = 0;

delay(100);

LCD_nRES = 0;

delay(100);

LCD_nRES = 1;

delay(100); LCD_DnC = 0;

LCD_WriteOneByte(0x21);

LCD_WriteOneByte(0xC8);

LCD_WriteOneByte(0x06);

LCD_WriteOneByte(0x13);

LCD_WriteOneByte(0x20);

LCD_WriteOneByte(0x0C);

LCD_DnC = 1;

}

main(){

uint i = 0;

uchar Hello[]={

0x00,0x7E,0x10,0x10,0x7E,0x00,/*"H",0*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x7E,0x52,0x52,0x42,0x00,/*"E",1*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",2*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",3*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x3C,0x42,0x42,0x3C,0x00,/*"O",4*/

/* (6 X 8 , 楷体_GB2312 )*/};

LCD_IOInit();

LCD_Init();

//for(i=0;i<30;i++){

//LCD_WriteOneByte(Hello[i]);

//}

LCD_WriteMByte(Hello,30);

LCD_nCS = 1;

}

下面是采用SPI总线方式控制LCD显示的例子:

//========================

//This file is applicable to Eshine EXBEE-DK V1

//Function:LCD test,the LCD use SPI BUS.The SPI BUS occupation P1-USART1-ALT.2

//InPut :

//OutPut :

//Created :longfan,2010.1.10

//Modify :

//========================

#include "periodef.h"

#include "font.h"

#include "TestBMP.h"

#include <string.h> void delay(uint n){

//26 cycles delay

while(--n)asm("NOP");

}

void LEDInit(){

P1SEL &= 0XFE; //P1_0 defined General purpose I/O

P1DIR |= 0X01; //P1_0 defined Output

DEBUG_LED = 1; //LED light up

}

//========================

//Initial P1-USART1-SPI

void SPIInit_U1_P1(void){

PERCFG |= 0X02; //USART1.alt.2,P1

P1SEL |= 0XE0; //P1,ISP,P1_4(LCD_DnC) defined general IO

P2SEL |= 0X40; //USART1 has priority

U1GCR |= 0x20; //MSB first,Negative clock polarity,Data is output on MOSI on the falling edge of CLK

U1GCR |= 19; //MAX Baud rate(17),falling edge

U1BAUD = 0;

UTX1IF = 0; //Clear interrupt

}

//========================

//P1-USART1-SPI Send One Byte

void SPI_SendOne_U1_P1(uchar data){

U1DBUF = data;

while(!UTX1IF);

UTX1IF = 0;

}

//========================

//P1-USART1-SPI Send Multily Byte

void SPI_Send_U1_P1(uchar *data,uint length){

while(length){

SPI_SendOne_U1_P1(*data);

data++;

length -= 1;

}

}

//========================

//LCD Use SPI BUS,Set the first display piex.

//When after call this function can direct call SPI data output function

//0<X<83,0<Y<5

void SPI_LCDSetPos(uchar X,uchar Y){

LCD_DnC = 0;

SPI_SendOne_U1_P1(0x80 | X); SPI_SendOne_U1_P1(0x40 | Y);

LCD_DnC = 1;

}

//========================

//LCD Use SPI BUS,Clear the LCD Display and its RAM.

void SPI_ClearLCD(){

uint i;

SPI_LCDSetPos(0,0);

for (i=0; i<504; i++){

SPI_SendOne_U1_P1(0);

}

}

//========================

//LCD Use SPI BUS,Send one LCD Command

void SPI_LCDWriteOneComm(uchar command){

LCD_DnC = 0;

SPI_SendOne_U1_P1(command);

}

//========================

//LCD Use SPI BUS,Send More than one LCD Command

void SPI_LCDWriteMulComm(uchar *command,uchar num){

LCD_DnC = 0;

SPI_Send_U1_P1(command,num);

}

//========================

//LCD Use SPI BUS,Send More than one data

void SPI_LCDWriteData(uchar *data,uchar length){

LCD_DnC = 1;

SPI_Send_U1_P1(data,length);

}

//========================

//LCD Use SPI BUS,Display a english character Without set position,The inverse argument control

//the inverse video mode.When use font library call this function to display.

//InPut :character:The character want to display

// inverse :Inverse video mode(1) or not(0)

//OutPut :

//Created :longfan,2010.1.10

//Modify :

//========================

void SPI_LCDWriteChar(uchar character,uchar inverse){

uchar line;

LCD_DnC = 1;

if(inverse){

for(line=0;line<6;line++){ SPI_SendOne_U1_P1(~font6x8[character-32][line]);

}

}else{

SPI_Send_U1_P1((uchar *)font6x8[character-32],6);

}

}

//========================

//LCD Use SPI BUS,Display a english character at (X,Y),The inverse argument control

//the inverse video mode.When use font library call this function to display.

//InPut :character:The character want to display

// X,Y :The position want to display

// inverse :Inverse video mode(1) or not(0)

//OutPut :

//Created :longfan,2010.1.10

//Modify :

//========================

void SPI_LCDDISPChar(uchar character,uchar X,uchar Y,uchar inverse){

uchar line;

SPI_LCDSetPos(X,Y);

if(inverse){

for(line=0;line<6;line++){

SPI_SendOne_U1_P1(~font6x8[character-32][line]);

}

}else{

SPI_Send_U1_P1((uchar *)font6x8[character-32],6);

}

}

//========================

//LCD Use SPI BUS,Display a english string at (X,Y)

void SPI_LCDWriteEnString(uchar *EnString,uchar X,uchar Y,uchar inverse){

uchar line;

SPI_LCDSetPos(X,Y);

while(*EnString){

uchar chara = *EnString;

chara -= 32;

if(inverse){

for(line=0;line<6;line++){

SPI_SendOne_U1_P1(~font6x8[chara][line]);

}

}else{

SPI_Send_U1_P1((uchar *)font6x8[chara],6);

}

EnString++;

} }

//========================

//LCD Use SPI BUS,Draw a AREA use Horizontal(thus the High pixel must be a multiple of 8).

//When use dot matrix to display use this function.

//InPut :data :The dot matrix of the data want to display

// X,Y :The position want to display

// wide,high:The pixel of wide and high

//OutPut :

//Created :longfan,2010.1.10

//Modify :

//========================

void SPI_LCDDrawArea_H(uchar *data,uchar X,uchar Y,uchar wide,uchar high){

uchar row;

uchar i;

SPI_LCDSetPos(X,Y); //Set position

row = high/8; //calculate the row number

for(i=0;i<row;i++){

SPI_Send_U1_P1(data+(i*wide),wide);

Y += 1;

SPI_LCDSetPos(X,Y); //Set position

}

}

//========================

//LCD Use SPI BUS,Initial LCD

void SPI_LCDInit(){

P0DIR |= 0X03; //P00(LCD_nCS),P01(LCD_nRES) Defined output

P1DIR |= 0X10; //P1_4(LCD_DnC) defined Outpu

P2DIR |= 1; //LCD_BKL

LCD_nCS = 0; //LCD Enable

delay(100);

LCD_nRES = 0; //LCD RESERT

delay(100);

LCD_nRES = 1;

delay(100);

LCD_BKL = 0; //D5 light up

//Send Initial command

//LCD_DnC = 0;

//SPI_SendOne_U1_P1(0x21); //Chip is active,Horizontal addressing,Use extended instruction set

//SPI_SendOne_U1_P1(0xC8); //Set Vop

//SPI_SendOne_U1_P1(0x06); //Temperature coefficient

//SPI_SendOne_U1_P1(0x13); //1:48

//SPI_SendOne_U1_P1(0x20); //Use basic instruction set

//SPI_SendOne_U1_P1(0x0C); //Display mode is Normal mode

uchar Init_Comm[]={0x21,0xC8,0x06,0x13,0x20,0X0C}; SPI_LCDWriteMulComm(Init_Comm,6);

SPI_ClearLCD(); //Clear Display RAM

SPI_LCDWriteOneComm(0X0C);

LCD_DnC = 1; //Send Display Data

}

main(){

uchar Hello[]={ 

0x00,0x7E,0x10,0x10,0x7E,0x00,/*"H",0*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x7E,0x52,0x52,0x42,0x00,/*"E",1*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",2*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",3*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x3C,0x42,0x42,0x3C,0x00,/*"O",4*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x7E,0x10,0x10,0x7E,0x00,/*"H",0*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x7E,0x52,0x52,0x42,0x00,/*"E",1*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",2*/

/* (6 X 8 , 楷体_GB2312 )*/

0x42,0x7E,0x42,0x40,0x40,0x00,/*"L",3*/

/* (6 X 8 , 楷体_GB2312 )*/

0x00,0x3C,0x42,0x42,0x3C,0x00,/*"O",4*/

/* (6 X 8 , 楷体_GB2312 )*/};

uchar *tring = "Hello,CC2430! i LOVE THIS!";

uchar *tring1= "Hello,CC2430!";

LEDInit();

SPIInit_U1_P1();

SPI_LCDInit();

//SPI_Send_U1_P1((uchar *)EshineLOGO,sizeof(EshineLOGO)/sizeof(uchar));

//delay(50000);

//delay(50000);

//delay(50000);

//delay(50000);

//delay(50000);

//SPI_ClearLCD();

//PI_Send_U1_P1(Hello,sizeof(Hello)/sizeof(uchar));

//SPI_LCDDISPChar('Q',0,0,1); //SPI_LCDWriteEnString(tring,10,0,0);

//SPI_LCDWriteEnString(tring,0,2,1);

//SPI_LCDDrawArea_H(Hello,40,4,30,16);

//SPI_ClearLCD();

//SPI_LCDSetPos(0,0);

//SPI_Send_U1_P1((uchar *)LOGO,sizeof(LOGO)/sizeof(uchar));

//SPI_LCDWriteData((uchar *)LOGO,sizeof(LOGO)/8);

SPI_LCDWriteData((uchar *)LOGO,sizeof(LOGO)/sizeof(uchar));

} ZigBee

学习之

学习之学习之

学习之3——USART之

之之

之UART UART在现在的PC是不常见了,但是在嵌入式领域里面依然是不可或缺的一个接口,不管是调试,红外还是其他基本

的通信,用UART相对于其他总线来说总是来的简单方便一下,而且由于技术成熟开发起来也更加快捷。

CC2430自带了USART,可以配置成UART或者SPI模式,由于CC2430管脚的限制,所以只能同时使用两个外设,我

就搞不懂为啥chipcon将CC2430的管脚的USART还非设个位置1位置2干什么,只要固定一个位置,可以配置不是一

样的灵活好用么?

加了位置可变反而要写更多的代码进行更多的配置不是变的麻烦了么?

关于CC2430的UART使用其实前面一节SPI&LCD已经有过涉及了,这里主要是要注意一下UART的数据传输格式和

波特率的配置,配置的波特率一定要和自己的串口调试助手是一样的不然的话出来乱码可不要怪我没有提醒啊,呵呵!

关键代码主要是UART的发送部分:

void UART_SEND_U0(uchar *data,int length){

//UART Send data

while(length--){

U0DBUF = 0X90;

U0DBUF = *data++;

while(!UTX0IF);

UTX0IF = 0;

}

}

我这里是通过查询传输完成中断标志来进行的,如果打开中断的话可以在中断服务程序中进行数据的处理,也就没必要

通过查询的方式来判断UART是否发送完数据了!  

 ZigBee学习之

学习之学习之

学习之4——DMA DMA(直接内存控制),DMA可以在外设之间传递数据而不要CPU的干预,节约了大量的CPU时间,

并且减轻了CPU的负担,是一种快速高效的通信方式,在CC2430中集成了DMA控制器,提供5个DMA

通道,并且官方推荐使用DMA方式传送RF寄存器中的数据,而且为了方便DMA对数据进行传输CC2430

中对RF寄存器,硬件寄存器等进行了映射,全部映射到了XDATA区域,这样就可以用DMA来读取寄存

器的数据,给操作带来了很大的方便。下面我们就对CC2430中集成的DMA进行试验。

关于DMA的寄存器并不多,但是DMA的操作和其他单片机寄存器的操作有一些不同的地方,就是DMA

市通过读配置的方式来实现DMA的初始化的,所有的配置信息都存放在一个固定的地址中,当药启动DMA

时只要先读入配置的地址然后启动相应地通道就可以了。

关于DMA的传输方式,在CC2430中集成的DMA控制器有四种传输模式:单模式:触发一次只能传送一

个字节(字),直到传送完指定的长度后通知CPU,并且解除DMA通道工作状态;块模式:当触发一到

就传输指定的长度,直到完成后就通知CPU;重复单一模式和重复块模式和非重复模式的区别在于当完成

指定长度数据的传输并通知CPU后并不解除DMA通道的工作状态,而是DMA通道重新进入工作状态。

关于DMA的触发时个很灵活的东西,比如我可以用某个特定的触发条件,那么在程序中我就要确保这个

触发条件在一定的情况下能够得到满足,否则得话DMA永远也不能进行传输,不过通过手动来第一次的

启动DMA好像也是个不错的方法哦,呵呵!

DMA处理流程:

1、写DMA配置参数数据结构;

2、设置DMA配置的读取地(将配置参数数据结构作为第几个DMA通道的配置);

3、关闭将要使用的通道;

4、启动相应的通道;

5、清除相应通道的DMA传输中断标志;

(6、手动触发相应的通道开始传输;)

6、等待DMAIRQ变化。

不管是什么设备中的DMA基本上都是这个流程。前几天帮同学调试赛灵思平台的SOC上的DMA也是这

样做的,可惜一直出现总线错误,找不到原因!只是不同的平台可能程序库的集成度不同,越高级的可能

使用起来就越方便。 ZigBee

学习之

学习之学习之

学习之5——无线电

无线电无线电

无线电 今天终于发了第一篇跟CC2430无线电有关的文章了,哈哈。其实无线单片机还是挺简单的,只要把相关的东西配置好,

然后按部就班的发送接收就OK了。对于包的处理无非就是封包和解包,怎么封就怎么解好了。

CC2430的无线电提供了灵活的配置,可以兼容IEEE802.15.4,也可以自己写一个协议,当然我们一般是用它来做ZigBee

的,当然是采取兼容IEEE802.15.4罗!手册中有CC2430的无线电控制状态图,我觉得还是应该好好看看的,其实把那

个图搞明白了,无线电的控制就没有问题了,剩下的就是按标准封包解包和功率控制的问题了。我看了下官方的例子程

序,再加入自己的与开发板相关的代码,烧录进去就能够很好的运行了。其中一个开发板按键,对应于另一个开发板显示相应的控制,当然只能点对点的,没有加入路由的东西在里面,也没有加入安全控制。不过TI的例子程序里面是有

简单的路由和安全控制的,感兴趣的朋友们可以自己加上,关于CC2430的无线电有几点自己觉得应该记下来的:

1、数据的发送首先是每字节分为两个符号,然后每个符号映射为32个芯片(其实一个芯片就是1bit),然后芯片以

2MChips/s的速度发送,且低位在前。

2、RF的控制多数是通过CSP命令选通来实现的,而CSP指令的实现,其实就是对RFST寄存器写入不同的值来实现

的。对其他寄存器的操作一般是配置RF传输参数。

3、SFD中断用来监测帧,FIFO中断可以用来监测数据是否读完。

下面我们来看一看无线电控制状态图

首先是打开模拟稳压器对无线电的供电,只要把RFPWR.RREG_RADIO_PD位清零即可,然后等待稳压器的稳定,当稳

压器稳定后会发送CPU中断,所以可以通过检测中断标志RFIF.IRQ_RREG_ON是否设置来判断稳压器是否稳定。然后

就是设置使用32Mhz的高速晶体振荡器,因为要使用无线电的话就必须使用32MHz的晶体振荡器,一直等到晶体振荡

器稳定工作,可以通过检测SLEEP.XOSC_STB位是否设置来实现。当然前面的部分不拘泥于先后顺序,有可能在使用

无线电之前就使用的是32Mhz的晶振了。

在空闲或者接收状态时通过发送STXON或者STXONCCA后8~12个符号周期后就可以进入发送状态,TX_CALIBRATE

下面的数字好像说到是每个状态在整个图中的顺序,然后就是发送帧前导和帧首界定符,这些都是自动加在帧前发送的,

只要在相关的寄存器中设置好就可以。然后就是发送FIFO中的数据,这里要注意FIFO的数据一般是整个MPDU+帧长

度域。发送完成后可以进入空闲模式也可以进入接收模式。

通过SRXON命令选通进入接收状态,然后CC2430会自动查找帧首界定符,当找到就进入帧接收状态,如果

FSMTC1.RX2RX_TIME_OFF为1,即不发送应答,或者地址识别失败或者应答发送失败,则重新进入帧首界定符查找

状态。帧接收完成后进入发送应答帧状态,在12~30个符号周期后发送应答帧首,然后发送应答帧。

这就是关于控制状态图的一些概要分析了。总的看来只要设置好各个控制寄存器然后通过发送CSP指令就可以完成无线

电传送了。 ZigBee

学习之

学习之学习之

学习之6——无线电

无线电无线电

无线电2 今天把TI的例子程序里的基本RF看了下,有的地方还没有完全看懂,发上来和大家分享一下 /********************************************************************************

*** Filename: basic_rf.c Description: Basic RF library ***********************************************************************************/ /*********************************************************************************** * INCLUDES */ //hal_int.h

包含了控制中断的宏 #include "hal_int.h"  // Using halMcuWaitUs(),hal_mcu.h中是一些延时函数 #include "hal_mcu.h" //通道编程的宏,以及硬件RF初始化,SECURITY_CCM宏是用来控制安全MAC的 #include "hal_rf.h" #ifdef SECURITY_CCM #include "hal_rf_security.h" #endif //basic_rf.h中定义了RF配置用的数据结构 #include "basic_rf.h" #ifdef SECURITY_CCM #include "basic_rf_security.h" #endif #include "util.h" // Using min() #include "string.h" /**********************************************************************************

* * CONSTANTS AND DEFINES */ // Packet and packet part lengths #define PKT_LEN_MIC 8 #define PKT_LEN_SEC PKT_LEN_UNSEC + PKT_LEN_MIC #define PKT_LEN_AUTH 8 #define PKT_LEN_ENCR 24 // Packet overhead ((frame control field, sequence number, PAN ID, // destination and source) + (footer)) // Note that the length byte itself is not included included in the packet length //MPDU

的长度宏,(2字节幀控制域 + 1字节数据序列号 + 2字节PAN ID + 2字节目标地址 +

2字节源地址 + 2字节MAC尾) #define BASIC_RF_PACKET_OVERHEAD_SIZE ((2 + 1 + 2 + 2 + 2) + (2)) //MPDU

最大有效载荷的长度(利用FIFO只有128字节空间),BASIC_RF_AUX_HDR_LENGTH

和 BASIC_RF_LEN_MIC是辅助安全头宏定义的长度分别是5和8,可以将其设置为0,即不具

备安全功能 #define BASIC_RF_MAX_PAYLOAD_SIZE (127 - BASIC_RF_PACKET_OVERHEAD_SIZE - \ BASIC_RF_AUX_HDR_LENGTH - BASIC_RF_LEN_MIC) //

应答帧的长度宏(2字节帧首界定符 + 1字节数据序列号 + 2字节帧校验序列),不包括帧长

度域 #define BASIC_RF_ACK_PACKET_SIZE 5 //MAC

帧尾的长度宏 #define BASIC_RF_FOOTER_SIZE 2 #define BASIC_RF_HDR_SIZE 10 // The time it takes for the acknowledgment packet to be received after the // data packet has been transmitted. #define BASIC_RF_ACK_DURATION (0.5 * 32 * 2 * ((4 + 1) + (1) + (2 + 1) + (2))) #define BASIC_RF_SYMBOL_DURATION (32 * 0.5) // The length byte #define BASIC_RF_PLD_LEN_MASK 0x7F //帧控制域 //帧类型子域: 000 信标帧 // 001 数据帧 // 010 应答帧 // 011 MAC命令 // 100~110 保留 //安全允许子域:0 帧无安全保护 // 1 帧采用安全保护 //帧待定子域: 0 传输没有附加数据的帧 // 1 当前传输后有附加的数据要发送 //应答请求子域: 0 不需要接收器发送应答帧 // 1 需要接收器发送应答帧 //内部PAN子域:决定MAC帧是在内部还是在其他PAN之间发送 // 0 如果目的地址和源地址都存在帧将包括目标和源PAN标志域 // 1 如果目的地址和源地址都存在帧不包括源PAN标志域 //目的地址模式子域:若为0,并且帧类型没有指定这是个应答帧或信标帧且源地址域非0,则

暗含此帧发送到PAN协调器将其PAN标志作为源PAN标志域 //

源地址模式子域:若为0,且帧类型没有指定这是个应答帧或信标帧,目的地址不为0,则暗

含此帧来自PAN协调器,用其PAN标志作为目的PAN标志域 //00 PAN

标志器且地址域未提交 //01 保留 //10 地址域包含16位的短地址 //11 地址域包含64位的扩展地址 // Frame control field //16位短地址模式,数据帧,没有安全保护 #define BASIC_RF_FCF_NOACK 0x8841 #define BASIC_RF_FCF_ACK 0x8861 #define BASIC_RF_FCF_ACK_BM 0x0020 #define BASIC_RF_FCF_BM (~BASIC_RF_FCF_ACK_BM) #define BASIC_RF_SEC_ENABLED_FCF_BM 0x0008 // Frame control field LSB #define BASIC_RF_FCF_NOACK_L LO_UINT16(BASIC_RF_FCF_NOACK) #define BASIC_RF_FCF_ACK_L LO_UINT16(BASIC_RF_FCF_ACK) #define BASIC_RF_FCF_ACK_BM_L LO_UINT16(BASIC_RF_FCF_ACK_BM) #define BASIC_RF_FCF_BM_L LO_UINT16(BASIC_RF_FCF_BM) #define

BASIC_RF_SEC_ENABLED_FCF_BM_L LO_UINT16(BASIC_RF_SEC_ENABLED_FCF_BM) // Auxiliary Security header #define BASIC_RF_AUX_HDR_LENGTH 5 #define BASIC_RF_LEN_AUTH BASIC_RF_PACKET_OVERHEAD_SIZE + \ BASIC_RF_AUX_HDR_LENGTH - BASIC_RF_FOOTER_SIZE #define BASIC_RF_SECURITY_M 2 #define BASIC_RF_LEN_MIC 8 #ifdef SECURITY_CCM #undef BASIC_RF_HDR_SIZE #define BASIC_RF_HDR_SIZE 15 #endif // Footer #define BASIC_RF_CRC_OK_BM 0x80 /**********************************************************************************

* * TYPEDEFS */ // The receive struct typedef struct { uint8 seqNumber; uint16 srcAddr; uint16 srcPanId; int8 length; uint8* pPayload; uint8 ackRequest; int8 rssi; volatile uint8 isReady; uint8 status; } basicRfRxInfo_t; // Tx state typedef struct { uint8 txSeqNumber; volatile uint8 ackReceived; uint8 receiveOn; uint32 frameCounter; } basicRfTxState_t; //

兼容IEEE 802.15.4 的MHR(MAC头)(2字节帧控制 + 1字节数据序列号 + 0~20字节地址

信息) // Basic RF packet header (IEEE 802.15.4) typedef struct { uint8 packetLength; uint8 fcf0; // Frame control field LSB uint8 fcf1; // Frame control field MSB uint8 seqNumber; uint16 panId; uint16 destAddr; uint16 srcAddr; #ifdef SECURITY_CCM uint8 securityControl; uint8 frameCounter[4]; #endif } basicRfPktHdr_t; /**********************************************************************************

* * LOCAL VARIABLES */ static basicRfRxInfo_t rxi= { 0xFF }; // Make sure sequence numbers are static basicRfTxState_t txState= { 0x00 }; // initialised and distinct. static basicRfCfg_t* pConfig; static uint8 txMpdu[BASIC_RF_MAX_PAYLOAD_SIZE+BASIC_RF_PACKET_OVERHEAD_SIZE+1]; static uint8 rxMpdu[128]; /*********************************************************************************** * GLOBAL VARIABLES */ /*********************************************************************************** * LOCAL FUNCTIONS */ /*********************************************************************************** * @fn basicRfBuildHeader * * @brief Builds packet header according to IEEE 802.15.4 frame format * * @param buffer - Pointer to buffer to write the header * destAddr - destination short address * payloadLength - length of higher layer payload * * @return uint8 - length of header */ //

构造兼容IEEE802.15.4 的帧头 static uint8 basicRfBuildHeader(uint8* buffer, uint16 destAddr, uint8 payloadLength) { //声明一个指向MAC帧头结构的指针 basicRfPktHdr_t *pHdr; uint16 fcf; pHdr= (basicRfPktHdr_t*)buffer; //payloadLength为有效载荷的长度,即要发送的数据的长度 // Populate packet header pHdr->packetLength = payloadLength + BASIC_RF_PACKET_OVERHEAD_SIZE; //pConfig为基本配置数据结构 /*typedef struct { uint16 myAddr; uint16 panId; uint8 channel; uint8 ackRequest; } basicRfCfg_t; */ //pHdr->frameControlField = pConfig->ackRequest ? BASIC_RF_FCF_ACK :

BASIC_RF_FCF_NOACK; fcf= pConfig->ackRequest ? BASIC_RF_FCF_ACK : BASIC_RF_FCF_NOACK; //

分别得到16位数的低8位和高8位 //#define HI_UINT16(a) (((uint16)(a) >> 8) & 0xFF) //#define LO_UINT16(a) ((uint16)(a) & 0xFF) pHdr->fcf0 = LO_UINT16(fcf); pHdr->fcf1 = HI_UINT16(fcf); //得到数据序列 pHdr->seqNumber= txState.txSeqNumber; pHdr->panId= pConfig->panId; pHdr->destAddr= destAddr; pHdr->srcAddr= pConfig->myAddr; #ifdef SECURITY_CCM // Add security to FCF, length and security header pHdr->fcf0 |= BASIC_RF_SEC_ENABLED_FCF_BM_L; pHdr->packetLength += PKT_LEN_MIC; pHdr->packetLength += BASIC_RF_AUX_HDR_LENGTH; pHdr->securityControl= SECURITY_CONTROL; pHdr->frameCounter[0]= LO_UINT16(LO_UINT32(txState.frameCounter)); pHdr->frameCounter[1]= HI_UINT16(LO_UINT32(txState.frameCounter)); pHdr->frameCounter[2]= LO_UINT16(HI_UINT32(txState.frameCounter)); pHdr->frameCounter[3]= HI_UINT16(HI_UINT32(txState.frameCounter)); #endif // Make sure bytefields are network byte order //这里把大的字节放到了前面,但有不是完全按从大到小的顺序排列字节,不知道为什么? UINT16_HTON(pHdr->panId); UINT16_HTON(pHdr->destAddr); UINT16_HTON(pHdr->srcAddr); //1字节长度,2字节FCF,1字节序列,2字节PanID,2字节目的地址,2字节源地址=10 return BASIC_RF_HDR_SIZE; } /**********************************************************************************

* * @fn basicRfBuildMpdu * * @brief Builds mpdu (MAC header + payload) according to IEEE 802.15.4 * frame format * * @param destAddr - Destination short address * pPayload - pointer to buffer with payload * payloadLength - length of payload buffer * * @return uint8 - length of mpdu */ //构造MPDU static uint8 basicRfBuildMpdu(uint16 destAddr, uint8* pPayload, uint8 payloadLength) { uint8 hdrLength, n; //txMpdu是定义

txMpdu[BASIC_RF_MAX_PAYLOAD_SIZE+BASIC_RF_PACKET_OVERHEAD_SIZE+1] hdrLength = basicRfBuildHeader(txMpdu, destAddr, payloadLength); //

将有效载荷数据放入MPDU单元中 for(n=0;n<payloadLength;n++) { txMpdu[hdrLength+n] = pPayload[n]; } return hdrLength + payloadLength; // total mpdu length } /**********************************************************************************

* * @fn basicRfRxFrmDoneIsr * * @brief Interrupt service routine for received frame from radio * (either data or acknowlegdement) * * @param rxi - file scope variable info extracted from the last incoming * frame * txState - file scope variable that keeps tx state info * * @return none */ //

接收中断服务程序 static void basicRfRxFrmDoneIsr(void) { basicRfPktHdr_t *pHdr; uint8 *pStatusWord; #ifdef SECURITY_CCM uint8 authStatus="0"; #endif // Map header to packet buffer pHdr= (basicRfPktHdr_t*)rxMpdu; //IM_FIFOP中断禁止,禁止RF总中断 // Clear interrupt and disable new RX frame done interrupt halRfDisableRxInterrupt(); //打开总中断 // Enable all other interrupt sources (enables interrupt nesting) halIntOn(); /*读取接收缓存,length指定要读取的字节数 void halRfReadRxBuf(uint8* pData, uint8 length) { while (length>0) { *pData++= RFD; length--; } } */ //读取MPDU的长度 // Read payload length. halRfReadRxBuf(&pHdr->packetLength,1); //指定MPDU的长度字节的高位默认固定为0,所以去掉高位,得到长度真实值 pHdr->packetLength &= BASIC_RF_PLD_LEN_MASK; // Ignore MSB // Is this an acknowledgment packet? // Only ack packets may be 5 bytes in total. if (pHdr->packetLength == BASIC_RF_ACK_PACKET_SIZE) { //如果只有5个字节长度,则此帧为应答帧 // Read the packet halRfReadRxBuf(&rxMpdu[1], pHdr->packetLength); // Make sure byte fields are changed from network to host byte order UINT16_NTOH(pHdr->panId); UINT16_NTOH(pHdr->destAddr); UINT16_NTOH(pHdr->srcAddr); #ifdef SECURITY_CCM UINT32_NTOH(pHdr->frameCounter); #endif rxi.ackRequest = !!(pHdr->fcf0 & BASIC_RF_FCF_ACK_BM_L); //条过RSSI指到CRC校验 // Read the status word and check for CRC OK pStatusWord= rxMpdu + 4; // Indicate the successful ACK reception if CRC and sequence number OK if ((pStatusWord[1] & BASIC_RF_CRC_OK_BM) && (pHdr->seqNumber == txState.txSeqNumber))

{ txState.ackReceived = TRUE; } // No, it is data } else { // It is assumed that the radio rejects packets with invalid length. // Subtract the number of bytes in the frame overhead to get actual payload. rxi.length = pHdr->packetLength - BASIC_RF_PACKET_OVERHEAD_SIZE; #ifdef SECURITY_CCM rxi.length -= (BASIC_RF_AUX_HDR_LENGTH + BASIC_RF_LEN_MIC); authStatus = halRfReadRxBufSecure(&rxMpdu[1], pHdr->packetLength, rxi.length, BASIC_RF_LEN_AUTH, BASIC_RF_SECURITY_M); #else halRfReadRxBuf(&rxMpdu[1], pHdr->packetLength); #endif // Make sure byte fields are changed from network to host byte order UINT16_NTOH(pHdr->panId); UINT16_NTOH(pHdr->destAddr); UINT16_NTOH(pHdr->srcAddr); #ifdef SECURITY_CCM UINT32_NTOH(pHdr->frameCounter); #endif rxi.ackRequest = !!(pHdr->fcf0 & BASIC_RF_FCF_ACK_BM_L); // Read the source address rxi.srcAddr= pHdr->srcAddr; // Read the packet payload rxi.pPayload = rxMpdu + BASIC_RF_HDR_SIZE; // Read the FCS to get the RSSI and CRC pStatusWord= rxi.pPayload+rxi.length; #ifdef SECURITY_CCM pStatusWord+= BASIC_RF_LEN_MIC; #endif rxi.rssi = pStatusWord[0]; // Notify the application about the received data packet if the CRC is OK // Throw packet if the previous packet had the same sequence number if( (pStatusWord[1] & BASIC_RF_CRC_OK_BM) && (rxi.seqNumber != pHdr->seqNumber) ) { // If security is used check also that authentication passed #ifdef SECURITY_CCM if( authStatus==SUCCESS ) { if ( (pHdr->fcf0 & BASIC_RF_FCF_BM_L) == (BASIC_RF_FCF_NOACK_L | BASIC_RF_SEC_ENABLED_FCF_BM_L)) { rxi.isReady = TRUE; } } #else if ( ((pHdr->fcf0 & (BASIC_RF_FCF_BM_L)) == BASIC_RF_FCF_NOACK_L) ) { rxi.isReady = TRUE; } #endif } rxi.seqNumber = pHdr->seqNumber; } // Enable RX frame done interrupt again halIntOff(); halRfEnableRxInterrupt(); } /**********************************************************************************

* * GLOBAL FUNCTIONS */ /*********************************************************************************** * @fn basicRfInit * * @brief Initialise basic RF datastructures. Sets channel, short address and * PAN id in the chip and configures interrupt on packet reception * * @param pRfConfig - pointer to BASIC_RF_CONFIG struct. * This struct must be allocated by higher layer * txState - file scope variable that keeps tx state info * rxi - file scope variable info extracted from the last incoming * frame * * @return none */ //

几个重要的底层函数: /*RF初始化,调用hal_rf.c中的函数 uint8 halRfInit(void) { uint8 i; // turning on power to analog part of radio and waiting for voltage regulator. //模拟稳压器上电,延时250uS上电 RFPWR = 0x04; //等待ADI_RADIO_PD为0,也可以通过检测中断标志RFIF.IRQ_RREG_ON为1来等待稳压器

稳定 while( RFPWR & 0x10 ); //

根据需要设置RF功能,自动CRC,自动应答0x03 // Setting for AUTO CRC and AUTOACK MDMCTRL0L |= (AUTO_CRC | AUTO_ACK); //打开自动收发转换,接收包后12个符号超时,接受应答包控制 // Turning on AUTO_TX2RX FSMTC1 = ((FSMTC1 & (~AUTO_TX2RX_OFF & ~RX2RX_TIME_OFF)) | ACCEPT_ACKPKT); //当SRXON发送时包接收不终止 // Turning off abortRxOnSrxon. FSMTC1 &= ~0x20; //FIFO门限设为最大值127 // Set FIFOP threshold to maximum IOCFG0 = 0x7F; //增益这部分没看懂,好像是用来优化的 // tuning adjustments for optimal radio performance; details available in datasheet */ RXCTRL0H = 0x32; RXCTRL0L = 0xF5; // Turning on receiver to get output from IF-ADC ISRXON(); halMcuWaitUs(1); //运行随机数发生器 // Enable random generator ADCCON1 &= ~0x0C; for(i = 0 ; i < 32 ; i++) { RNDH = ADCTSTH; // Clock random generator ADCCON1 |= 0x04; } ISRFOFF(); // Enable CC2591 with High Gain Mode halPaLnaInit(); //打开接收中断 halRfEnableRxInterrupt(); return SUCCESS; } //设置2.4G的RF通道。 void halRfSetChannel(uint8 channel) { uint16 freqMHz; //MIN_CHANNEL为11,CHANNEL_SPACING为5,在hal_rf.h中定义 freqMHz= 2405 + ((channel - MIN_CHANNEL) * CHANNEL_SPACING); // Calculate frequency freqMHz -= (uint32)2048; // Subtract; datasheet sect 14.16 FSCTRLL = LO_UINT16(freqMHz); FSCTRLH &= ~0x03; FSCTRLH |= (HI_UINT16(freqMHz) & 0x03); } //设置16位短地址 void halRfSetShortAddr(uint16 shortAddr) { SHORTADDRL= LO_UINT16(shortAddr); SHORTADDRH= HI_UINT16(shortAddr); } //设置PANID void halRfSetPanId(uint16 panId) { PANIDL= LO_UINT16(panId); PANIDH= HI_UINT16(panId); } */ uint8 basicRfInit(basicRfCfg_t* pRfConfig) { if (halRfInit()==FAILED) return FAILED; //关闭所有中断 halIntOff(); // Set the protocol configuration pConfig = pRfConfig; rxi.pPayload = NULL; txState.receiveOn = TRUE; txState.frameCounter = 0; //设置通道 // Set channel halRfSetChannel(pConfig->channel); //设置短地址和PAN id // Write the short address and the PAN ID to the CC2520 RAM halRfSetShortAddr(pConfig->myAddr); halRfSetPanId(pConfig->panId); // if security is enabled, write key and nonce #ifdef SECURITY_CCM basicRfSecurityInit(pConfig); #endif /* //设置接收中断函数 在hal_types.h中有中断函数的定义: #define HAL_ISR_FUNC_DECLARATION(f,v) \ _PRAGMA(vector=v) __near_func __interrupt void f(void) #define HAL_ISR_FUNC_PROTOTYPE(f,v) \ _PRAGMA(vector=v) __near_func __interrupt void f(void) #define HAL_ISR_FUNCTION(f,v) \ HAL_ISR_FUNC_PROTOTYPE(f,v); HAL_ISR_FUNC_DECLARATION(f,v) 可以看出HAL_ISR_FUNCTION(f,v)其实就定义了中断函数了。然后中断函数如下: HAL_ISR_FUNCTION( macMcuRfIsr, RF_VECTOR ) { uint8 rfim; uint8 x; HAL_INT_LOCK(x); rfim = RFIM; if ((RFIF & IRQ_FIFOP) & rfim) { (pfISR)(); // Execute the custom ISR S1CON= 0; RFIF&= ~IRQ_FIFOP; } HAL_INT_UNLOCK(x); } 进入中断后会检查中断类型,然后执行pfISR指针指向的中断函数。 typedef void (*ISR_FUNC_PTR)(void); //定义中断服务程序的函数指针 static ISR_FUNC_PTR pfISR= NULL; void halRfRxInterruptConfig(ISR_FUNC_PTR pf) { uint8 x; HAL_INT_LOCK(x); pfISR= pf; HAL_INT_UNLOCK(x); //#define HAL_INT_LOCK(x) st( (x) = EA; EA = 0; ) //#define HAL_INT_UNLOCK(x) st( EA = (x); ) //中断锁。实际上就是在操作之前关闭总的中断,等操作完成后再打开中断,避免产生不必要的

中断 } */ // Set up receive interrupt (received data or acknowlegment) halRfRxInterruptConfig(basicRfRxFrmDoneIsr); //

开所有中断 halIntOn(); return SUCCESS; } /**********************************************************************************

* * @fn basicRfSendPacket * * @brief Send packet * * @param destAddr - destination short address * pPayload - pointer to payload buffer. This buffer must be * allocated by higher layer. * length - length of payload * txState - file scope variable that keeps tx state info * mpdu - file scope variable. Buffer for the frame to send * * @return basicRFStatus_t - SUCCESS or FAILED */ /*发送函数 uint8 halRfTransmit(void) { uint8 status; ISTXON(); // Sending // Waiting for transmission to finish while(!(RFIF & IRQ_TXDONE) ); RFIF = ~IRQ_TXDONE; status= SUCCESS; return status; } */ uint8 basicRfSendPacket(uint16 destAddr, uint8* pPayload, uint8 length) { uint8 mpduLength; uint8 status; /* 打开接收 #define FLUSH_RX_FIFO() st( ISFLUSHRX(); ISFLUSHRX(); ) void halRfReceiveOn(void) { //发送CPS指令 FLUSH_RX_FIFO(); ISRXON(); } */ // Turn on receiver if its not on if(!txState.receiveOn) { halRfReceiveOn(); } // Check packet length length = min(length, BASIC_RF_MAX_PAYLOAD_SIZE); /*等待传输完成 void halRfWaitTransceiverReady(void) { while (RFSTATUS & (1<<1) | (1<<4) )); } */ // Wait until the transceiver is idle halRfWaitTransceiverReady(); // Turn off RX frame done interrupt to avoid interference on the SPI interface halRfDisableRxInterrupt(); mpduLength = basicRfBuildMpdu(destAddr, pPayload, length); #ifdef SECURITY_CCM halRfWriteTxBufSecure(txMpdu, mpduLength, length, BASIC_RF_LEN_AUTH,

BASIC_RF_SECURITY_M); txState.frameCounter++; // Increment frame counter field #else halRfWriteTxBuf(txMpdu, mpduLength); #endif // Turn on RX frame done interrupt for ACK reception halRfEnableRxInterrupt(); // Send frame with CCA. return FAILED if not successful if(halRfTransmit() != SUCCESS) { status = FAILED; } // Wait for the acknowledge to be received, if any if (pConfig->ackRequest) { txState.ackReceived = FALSE; // We'll enter RX automatically, so just wait until we can be sure that the ack reception should have finished // The timeout consists of a 12-symbol turnaround time, the ack packet duration, and a small margin //BASIC_RF_SYMBOL_DURATION

为0.5us,因为2MChip/s的速度是固定的 halMcuWaitUs((12 * BASIC_RF_SYMBOL_DURATION) + (BASIC_RF_ACK_DURATION) + (2

* BASIC_RF_SYMBOL_DURATION) + 10); // If an acknowledgment has been received (by RxFrmDoneIsr), the ackReceived flag should be set status = txState.ackReceived ? SUCCESS : FAILED; } else { status = SUCCESS; } // Turn off the receiver if it should not continue to be enabled if (!txState.receiveOn) { halRfReceiveOff(); } if(status == SUCCESS) { txState.txSeqNumber++; } #ifdef SECURITY_CCM halRfIncNonceTx(); // Increment nonce value #endif return status; } /*********************************************************************************** * @fn basicRfPacketIsReady * * @brief Check if a new packet is ready to be read by next higher layer * * @param none * * @return uint8 - TRUE if a packet is ready to be read by higher layer */ //检查包是否已准备好被上层读取 uint8 basicRfPacketIsReady(void) { return rxi.isReady; } /********************************************************************************** * @fn basicRfReceive * * @brief Copies the payload of the last incoming packet into a buffer * * @param pRxData - pointer to data buffer to fill. This buffer must be * allocated by higher layer. * len - Number of bytes to read in to buffer * rxi - file scope variable holding the information of the last * incoming packet * * @return uint8 - number of bytes actually copied into buffer */ uint8 basicRfReceive(uint8* pRxData, uint8 len, int16* pRssi) { // Accessing shared variables -> this is a critical region // Critical region start halIntOff(); memcpy(pRxData, rxi.pPayload, min(rxi.length, len)); if(pRssi != NULL) { if(rxi.rssi < 128){ *pRssi = rxi.rssi - halRfGetRssiOffset(); } else{ *pRssi = (rxi.rssi - 256) - halRfGetRssiOffset(); } } rxi.isReady = FALSE; halIntOn(); // Critical region end return min(rxi.length, len); } /********************************************************************************** * @fn basicRfGetRssi * * @brief Copies the payload of the last incoming packet into a buffer * * @param none * @return int8 - RSSI value */ int8 basicRfGetRssi(void) { if(rxi.rssi < 128){ //CC2430的rssi便宜为45左右 return rxi.rssi - halRfGetRssiOffset(); } else{ return (rxi.rssi - 256) - halRfGetRssiOffset(); } } /**********************************************************************************

* * @fn basicRfReceiveOn * * @brief Turns on receiver on radio * * @param txState - file scope variable * * @return none */ void basicRfReceiveOn(void) { txState.receiveOn = TRUE; halRfReceiveOn(); } /*********************************************************************************** * @fn basicRfReceiveOff * * @brief Turns off receiver on radio * * @param txState - file scope variable * * @return none */ void basicRfReceiveOff(void) { txState.receiveOn = FALSE; halRfReceiveOff(); } /**********************************************************************************

* Copyright 2007 Texas Instruments Incorporated. All rights reserved. **********************************************************************************/ ZigBee

学习之

学习之学习之

学习之7-OSAL(操作系统抽象层

操作系统抽象层操作系统抽象层

操作系统抽象层)API解读

解读解读

解读 

根据Z-Stack1.4.3-1.2.0中OSAL API_F8W-2003-0002_.pdf文档翻译。

Z-Stack1.4.3及以后的版本中引入了一个OS的概念,把应用层和堆栈层进行了分离,但是这个操作系统并不是时实的操

作系统,所以有兴趣的话还可以将其改为时实的操作系统,或者用其他开源的实时操作系统取代,比如USOS,呵呵。

我将这个OS的API文档进行了一定的翻译,当然所谓一定,就是说有的地方没有翻译出来罗,要么是我不会的,要么

就是我觉得没必要翻译的东西,总之,提供给各位一个参考,最好是对照原文档来阅读拉。

没接触过操作系统,也是第一次搞Zigbee,错误的地方还请各位多多指正。 OSAL(

操作系统抽象层

操作系统抽象层操作系统抽象层

操作系统抽象层) API[OSAL API_F8W-2003-0002_.pdf] 这个层次主要是将Z-Stack软件组件从特殊的处理过程相分离,将其保护起来。一般来说提供如

下几个功能: ? 任务的注册、初始化、开始 ? 任务间的消息交换 ? 任务同步 ? 中断处理 ? 时间管理 ? 内存分配 消息管理

API 消息管理API提供任务(或处理单元)间的消息交换(比如中断服务事务,控制循环中的函数

调用)。这些API能用来允许任务分配和取消分配的消息缓存,发送对其他任务的命令消息,

接受应答消息。 byte *osal_msg_allocate(uint16 len) 分配一个消息缓存,随后调用它的任务

/函数将填充消息并调用osal_msg_send()将消息发送给其

他任务。 参数:消息长度; 返回值:指向消息缓存的指针,失败则为

NULL。 byte osal_msg_deallocate(byte *msg_ptr) 任务接收完消息后用来释放分配的消息缓存。 参数:要释放的消息缓存; 返回值:ZSUCCESS - 成功 INVALID_MSG_POINTER - 无效的消息指针 MSG_BUFFER_NOT_AVAIL - 缓存正在队列(queued) byte osal_msg_send(byte destination_task,byte *msg_ptr) 向其他任务或处理单元发送命令或数据消息。目标任务ID域必须有效的系统任务。任务ID由

开始任务时的osal_creat_task()分配。此函数将在目标任务事件列表中设置SYS_EVENT_MSG事

件。 参数:

 destination_task - 目标任务ID; msg_ptr - 指向包含消息的消息缓存的指针; 返回值:ZSUCCESS - 成功 INVALID_MSG_POINTER - 无效的消息指针 INVALID_TASK - 目标任务不正确 byte *osal_msg_receive(byte task_id) 提取接收到的命令消息,在处理完消息后必须用osal_msg_deallocate()释放消息缓存。 参数: task_id - 调用者的任务ID(即消息发往的目标任务); 返回值:指向包含消息的一段缓存,若没有接收到消息则为NULL。 任务同步API 允许任务等待事件发生并在等待时返回控制信息。用来为任务设置事件 byte osal_set_event( byte task_id, UINT16 event_flag ) 为任务设置事件标志。 参数: task_id - 将设置事件的任务ID; event_flag - 2字节的位图,其中每位指定了一个事件。只有一个系统事件

(SYSTEM_EVENT_MSG),其余的事件/位由接收任务指定; 返回值:

ZSUCCESS - 成功 INVALID_TASK - 任务不正确 时钟管理API 这些API允许内部(Z-Stack)任务和外部(应用层)任务用时钟。API提供启动,停止时钟的

函数。时钟能被设置成1ms的增量。 byte osal_start_timer(UINT16 event_id, UINT16 timeout_value) 启动时钟。当时钟超时,相关的事件位将设置。事件将设置在调用此函数的任务中。 参数:

 event_id - 用户定义的事件位,当时钟超时调用任务将被通知; timeout_value - 超时时间(ms); 返回值:ZSUCCESS - 成功 NO_TIMER_AVAILABLE - 失败 byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value) 在前一个函数的基础上调用者可以为别的任务设置时钟。 参数: taskID - 超时时接收事件的任务的任务ID; event_id - 用户定义的事件位,当时钟超时调用任务将被通知; timeout_value - 超时时间(ms); 返回值:ZSUCCESS - 成功 NO_TIMER_AVAILABLE - 失败 byte osal_stop_timer( UINT16 event_id ) byte osal_stop_timerEx( byte task_id, UINT16 event_id ) 指定停止那个任务的时钟。 参数: task_id - 要停止时钟的任务; event_id - 要停止的时钟的id; 返回值:ZSUCCESS - 成功 INVALID_EVENT_ID - 失败 uint32 osal_GetSystemClock( void ) 读取系统时钟(ms) 中断管理API 这些API提供任务和外部中断的接口。API中的函数允许任务和每个中断的服务程序相关联,可

以允许和禁止中断,在服务程序中可以为其他任务设置事件。 byte osal_int_enable( byte interrupt_id ) byte osal_int_disable( byte interrupt_id ) 参数:

 interrupt_id - 要允许的中断; 返回值:ZSUCCESS - 成功 INVALID_INTERRUPT_ID - 不可用的中断 任务管理API 添加和管理OSAL系统任务的API,这个应该是系统层的关键API吧。每个任务由初始化函数,

事件处理函数构成。OSAL调用osalInitTasks() 来初始化任务并利用任务表来为每个任务调用事

件处理。 byte osal_init_system( void ) 初始化

OSAL系统。需要在调用其他OSAL函数前调用。 void osal_start_system( void ) 这是任务系统的主循环函数,它将监视所有任务事件并负责调用事件处理函数,如果特定任务的

事件发生,它将调用此任务的事件处理函数来处理事件,其余的事件将返回主循环。如果没有事

件发生则使处理器进入睡眠模式。 注意在这个版本中已经不支持

osalTaskAdd () 了。 内存管理API void *osal_mem_alloc( uint16 size ) 参数: size - 要分配的字节数; 返回值:指向新分配空间的VOID指针,如果失败则返回指向NULL的指针。 void osal_mem_free( void *ptr ) 和前一个函数配对,用来释放其分配的内存空间。 参数: ptr - 指向已分配的内存的指针; 电源管理API 系统为应用/协议栈提供了一个方式用来通知OSAL什么时候可以安全关闭接收器或外部设备,

什么时候能进入休眠。 void osal_pwrmgr_state( byte pwrmgr_device ) 设置器件电源管理的全局开关状态。应该在中心控制实体(如

ZDO)中调用此函数。 参数: pwrmgr_device - 改变或设置电源节约模式, PWRMGR_ALWAYS_ON - 没有电源节约模式 PWRMGR_BATTERY - 打开电源节约模式 byte osal_pwrmgr_task_state( byte task_id, byte state ) 每个任务通过调用这个函数来声明是否需要节约电源。任务调用这个函数来进入节能模式或者保

持节能模式。默认每个任务创建后其电源模式都为节能模式。 参数:

 state - 改变一个任务的电源模式, PWRMGR_CONSERVE - 打开节能模式 PWRMGR_HOLD - 关闭节能模式 返回值:ZSUCCESS - 成功 INVALID_TASK - 不可用的任务 非易失储存器(NV)管理API 系统为应用提供了将信息持久保存在器件储存器中的方式。NV函数用来读写用户定义的具有独

特数据字节的数据,比如结构体或数组。用户能够读写整个数据或者设置合适的偏移和长度来读

取数据中某个元素。NV API独立于非易失性储存媒介,能用FLASH或EEPROM来实现。 每个

NV条目都有自己独立的ID,对于应用,ID值有特定的范围。如果你的应用创建了自己的

NV条目,就必须从应用范围中选择一个ID。 值

 用户 0x0000 保留 0x0001-0x0020 OSAL 0x0021-0x0040 NWK 0x0041-0x0060 APS 0x0061-0x0080 安全 0x0081-0x00A0 ZDO 0x00A1-0x0200 保留 0x0201-0x0FFF 应用 0x1000-0xFFFF 保留 注意1、这些API的调用可能会花费几毫秒的时候,特别是在写操作的时候,而且中断也会被禁

止几毫秒。 2

、尽量少执行NV写操作,因为它既费时又耗能。 3、如果一个或多个NV条目的数据结构发生改变,特别是在升级Z-Stack版本的时候,最好搽

除并重新初始化NV储存器。 byte osal_nv_item_init( uint16 id, uint16 len, void *buf ) 检查存在的

NV条目,如果不存在则用传入函数的数据创建和初始化。在调用osal_nv_read() 或

者osal_nv_write()前必须调用这个函数。 参数:

 id - 用户定义的条目ID len - 以字节为单位的条目长度 *buf - 指向条目初始化数据,如果没有则设为NULL 返回值:ZSUCCESS - 成功 NV_ITEM_UNINIT - 成功,但是条目不存在 NV_OPER_FAILED - 失败 byte osal_nv_read( uint16 id, uint16 offset, uint16 len, void *buf ) 从非易失性储存中读数据到*buf中 byte osal_nv_write( uint16 id, uint16 offset, uint16 len, void *buf ) 返回值:ZSUCCESS - 成功 NV_ITEM_UNINIT - 成功,但是条目不存在 NV_OPER_FAILED - 失败 osal_offsetof(type, member) 用来计算结构中特定成员的内存偏移量的宏。 参数: type - 结构类型 member - 结构成员  ZigBee学习之

学习之学习之

学习之8——对

对对

对ZigBee地址的理解

地址的理解地址的理解

地址的理解  Zigbee网络中有三类地址,呵呵有人说只有两类哦,这里是我自己的理解,如果不同意可以发表见解哈

第一类是IEEE地址,也叫做扩展地址。这是一个64位的地址,由设备商固化到设备中,地址由IEEE发

配,当然我们现在买到的开发板芯片上的IEEE地址一般应该为全F,这是一个无效地址,就是说这个芯片

还没有分配地址拉。可以用Ti的flash编程软件烧写一个IEEE地址。

第二类地址是所谓的网络地址,也就叫做短地址。这是一个16位的地址,其中有几个特殊的地址:

0xFFFF -这个一个对全网络中设备进行广播的广播地址

0xFFFD -如果在命令中将目标地址设为这个地址的话那么只对打开了接收的设备进行广播

0xFFFC -广播到协调器和路由器

0xFFFE -如果目的地址为这个地址的话,那么应用层将不指定目标设备,而是通过协议栈读取绑定表来获

得相应目标设备的短地址

此外的0x0000到0xFFF8都是有效的目的地址。每一个地址就只是了一个目标设备。

第三类地址是终端(endpoint)地址。这是一个8位的逻辑地址。每个物理设备节点内部含有256个可编址

的逻辑终端(endpoint),其中终端0就是ZDO,终端255是个广播地址,241-254保留为以后使用。Zigbee

的通信其实就是由叫做簇的数据结构在终端之间传播构成的。

关于终端和其操作还是有很多不明白的地方,只知道这是一个逻辑设备,每个终端可以作为一个应用项目,

但是怎么操作这个终端呢?是个问题,后面要研究一下,这个样的话是不是可以在一个物理设备上完成几

个应用的功能呢?比如一个物理节点即是灯光控制器又是温度控制器?好像有可能,恩,一定要研究研究! ZigBee

学习之

学习之学习之

学习之9-Z-Stack编译选项

编译选项编译选项

编译选项.rtf 编译环境:IAR EW8051[Z-Stack Compile Options_F8W-2005-0038_.pdf]

选择逻辑器件类型

ZigBee器件可以被配置为三种器件中的一种:ZigBee协调器;ZigBee路由器;ZigBee节点设备

定位编译选项

编译选项在两个位置,其中一个在链接器控制文件,这些选项很少改变;另一个在IAR项目文件中,是用户定义的选项,

通过改变其选项可以允许和禁止某些特性。

链接器控制文件的编译选项

打开一个项目后,在项目管理树形目录下打开Tools文件夹,这里是一些链接控制文件。 

f8wConfig.cfg中定义了通用编译选项,比如通道,PAN ID等。他允许开发者建立自己的私人通道。

f8wCoord.cfg, f8wEndev.cfg, f8wRouter.cfg中包含了器件配置的细节。很明显一个器件类型对应一个配置文件了。

IAR项目文件编译选项

选择Project——Options菜单,然后选择C/C++ Compiler项目下的Preprocessor选项卡,Defined sysbols框中的就是配置

选项。要禁用某个编译选项只要条目的最左边加一个x就可以了  ZigBee学习之

学习之学习之

学习之10-MAC层

层层

层API解读

解读解读

解读 其实也算不上什么解读拉,基本上是把官方的文档翻译了一下。在Zigbee中,是分层结构的,这样做有很

多的好处,每一层只负责自己的东西,数据传输更加透明和有效,好了闲话不说,我们开始解读MAC层

API,当然是针对Z-Stack的,呵呵 MAC API[802.15.4 MAC API _F8W-2005-1503_.pdf] 常量和结构体 typedef uint8 sAddrExt_t[8]; typedef struct { union { uint16 shortAddr; sAddrExt_t extAddr; } addr; uint8 addrMode; } sAddr_t; shortAddr - 16

位MAC 短地址 extAddr - 64位MAC 扩展地址 addrMode - 地址模式 SADDR_MODE_NONE - 地址不存在 SADDR_MODE_SHORT - 使用16位短地址 SADDR_MODE_EXT - 使用64位扩展地址 状态值 标准状态值 MAC_SUCCESS 操作成功 MAC_BEACON_LOSS 同步请求中丢失信标 MAC_CHANNEL_ACCESS_FAILURE 由于通道活跃,数据请求失败 MAC_COUNTER_ERROR 接收到的帧的发送源的帧计数器不可用 MAC_DENIED MAC不能进入低功耗模式 MAC_FRAME_TOO_LONG 接收到的帧或者操作产生的帧或数据请求太长 MAC_IMPROPER_KEY_TYPE 接收到的帧的发送源的KEY不可用 MAC_IMPROPER_SECURITY_LEVEL 接收到的帧的发送源安全等级和最低等级不匹配 MAC_INVALID_ADDRESS 由于没有源地址或目的地址,数据请求失败 MAC_INVALID_HANDLE 清除请求包含不可用的处理 MAC_INVALID_PARAMETER API函数参数超出范围 MAC_LIMIT_REACHED 由于PAN描述储存达到界限,扫描中止 MAC_NO_ACK 由于没有收到应答,操作或数据请求失败 MAC_NO_BEACON 由于没有收到信标,扫描请求失败 MAC_NO_DATA 由于没有收到关联应答,关联请求失败 MAC_NO_SHORT_ADDRESS 开始请求的短地址错误 MAC_PAN_ID_CONFLICT 检测到一个PAN ID冲突 MAC_READ_ONLY 拥有只读标记的请求 MAC_REALIGNMENT 接收到协调器重排列 MAC_SCAN_IN_PROGRESS 扫描正在进行,新的扫描请求失败 MAC_SECURITY_ERROR 接收到的安全帧密码处理失败 MAC_SUPERFRAME_OVERLAP 信标开始时间超出协调器传输时间 MAC_TRACKING_OFF 没有找到其协调器的信标,开始请求失败 MAC_TRANSACTION_EXPIRED 关联应答,解关联请求,间接数据传输失败 MAC_TRANSACTION_OVERFLOW 数据缓存溢出,操作失败 MAC_UNAVAILABLE_KEY 安全密钥不可用 MAC_UNSUPPORTED_ATTRIBUTE 由于不支持的特性指令或请求失败 MAC_UNSUPPORTED_LEGACY 不支持的安全方式 MAC_UNSUPPORTED_SECURITY 接收到的帧的安全方式不支持 私有状态值 MAC_UNSUPPORTED 当前配置不支持的操作 MAC_BAD_STATE 当前状态不支持的操作 MAC_NO_RESOURCES 内存资源不足 MAC时间值 aBaseSuperframeDuration 构成超帧的符号周期 960 15.36ms(2.4G) aUnitBackoffPeriod 构成CSMA-CA算法的时间周期的符号周期 20 320us(2.4G) 初始化接口 初始化接口函数都是直接执行函数 void MAC_Init(void) 初始化MAC子系统 void MAC_InitDevice(void) 初始化MAC关联到一个非信标网络,使用此函数初始化一个RFD设备,如果使用此函数,要

在调用其它数据和管理API之前调用 void MAC_InitCoord(void) 初始化

MAC能进行协调器的操作。此函数用来初始化一个FFD设备,要在调用其它数据和管

理API之前调用 void MAC_InitSecurity(void) 使

MAC能使用安全功能,要在调用其它数据和管理API之前调用 void MAC_InitBeaconCoord(void) 初始化MAC在信标网络中能进行协调器的操作。此函数要在调用其它数据和管理API之前调用 void MAC_InitBeaconDevice(void) 初始化MAC能关联到一个信标网络,此函数要在调用其它数据和管理API之前调用 提供一些ZigBee的配置事例,其他的网络配置请看TI的文档 ZigBee节点设备 ZigBee路由器 ZigBee协调器 MAC_InitDevice(); MAC_InitDevice(); MAC_InitCoord(); MAC_InitCoord(); 数据接口 MAC层用来发送和接收数据的API 数据结构 typedef struct { uint8 *p; uint8 len; } sData_t; p – 指向数据 len – 数据的字节长度 数据常量 MAC_MAX_FRAME_SIZE 102 不包含安全域的最大数据长度 MAC_DATA_OFFSET 24 MAC头要求的数据偏移 MAC_ENC_OFFSET 5 加密头要求的数据偏移 MAC_MIC_32_LEN 4 32位认证代码要求的长度 MAC_MIC_64_LEN 8 64位认证代码要求的长度 MAC_MIC_128_LEN 16 128位认证代码要求的长度 void MAC_McpsDataReq(macMcpsDataReq_t *pData) 将应用数据发送到MAC。若MAC拥堵或者不能接受数据请求则发送状态为

MAC_TRANSACTION_OVERFLOW的MAC_MCPS_DATA_CNF,最终MAC将变得不拥堵并

且为一个缓存的请求发送MAC_MCPS_DATA_CNF。所以应用能在任何时候发送数据,只是数

据将被队列。 应用必须分配一定字节的缓存,数目是:

MAC_DATA_OFFSET+MAC_ENC_OFFSET(如果使

用加密安全)。可用使用MAC_McpsDataAlloc()来方便准确的分配这个缓存。最大的数据帧长度

为MAC_MAX_FRAME_SIZE,如果使用加密安全,则还要减去MAC_ENC_OFFSET及其相关

的加密代码区域:比如使用AES-MIC128,则最大的数据帧长度为:max =

MAC_MAX_FRAME_SIZE - MAC_ENC_OFFSET – MAC_MIC_128_LEN; 参数: typedef struct { sAddr_t dstAddr; uint16 dstPanId; uint8 srcAddrMode; uint8 mdsuHandle; uint8 txOptions; uint8 channel; uint8 power; } macDataReq_t; typedef struct { macEventHdr_t hdr; sData_t msdu; macTxIntData_t internal; macSec_t sec; macDataReq_t mac; } macMcpsDataReq_t; hdr

和internal是内部使用的,mac.mdsuHandle是应用定义的关于数据请求的句柄值,

mac.txOptions为TX参数位掩码,有以下一些值,其他的都很好理解了。 MAC_TXOPTION_ACK

应答传输。如果没有收到应答将重传 MAC_TXOPTION_GTS GTS传输(unused). MAC_TXOPTION_INDIRECT 间接传输。MAC将队列数据等待目标设备请求此数据。只有协调

器才可用这种方式 MAC_TXOPTION_NO_RETRANS

无中继传输。 MAC_TXOPTION_NO_CNF 无确认。这将阻止为此帧发送MAC_MCPS_DATA_CNF事件 MAC_TXOPTION_ALT_BE Use PIB value MAC_ALT_BE for the minimum backoff exponent. MAC_TXOPTION_PWR_CHAN 用macDataReq_t结构中的电源和通道值而不用PIB中的值来传

输 void MAC_McpsPurgeReq(uint8 msduHandle) 从

MAC数据队列中清空并丢弃数据请求,当完成操作后MAC发送MAC_MCPS_PURGE_CNF macMcpsDataReq_t *MAC_McpsDataAlloc(uint8 len, uint8 securityLevel, uint8 keyIdMode) 如果用此函数分配了MAC_McpsDataReq()所需的数据缓存,在收到MAC_MCPS_DATA_CNF

后要通过osal_msg_deallocate(pBuffer)来释放。若不使用安全则设置securityLevel和keyIdMode

为MAC_SEC_LEVEL_NONE和MAC_KEY_ID_MODE_NONE 回调函数事件 这些回调函数由应用调用,用来将事件或者数据从

MAC传到应用。 MAC_MCPS_DATA_IND 从MAC发送数据到应用。这个事件的参数指向一个动态分配的缓存,当应用用完数据后必须调

用osal_msg_deallocate(pData)来释放缓存。MAC还能为应用定义的数据分配额外的空间,应用

定义的数据的大小由MAC_MlmeSetReq()的属性MAC_DATA_IND_OFFSET设置。 参数: typedef struct { sAddr_t srcAddr; sAddr_t dstAddr; uint32 timestamp; uint16 timestamp2; uint16 srcPanId; uint16 dstPanId; uint8 mpduLinkQuality; uint8 correlation; uint8 rssi; uint8 dsn; } macDataInd_t; typedef struct { macEventHdr_t hdr; sData_t msdu; macRxIntData_t internal; macSec_t sec; macDataInd_t mac; } macMcpsDataInd_t; mac.timestamp –

接收到帧的时间,单位为aUnitBackoffPeriod mac.timestamp2 – 接收到帧的时间,单位为内部MAC定时器单元 mac.correlation – The raw correlation value of the received data frame. This value depends on the

radio. See the chip data sheet for details. dsn –

接收到的帧的数据序列 MAC_MCPS_DATA_CNF 每当调用MAC_McpsDataReq()时就会向应用发送这个事件,这个事件返回数据请求的状态这个

事件同样返回指向数据缓存的指针,应用能利用这个指针来释放空间。 typedef struct { macEventHdr_t hdr; uint8 msduHandle; macMcpsDataReq_t *pDataReq; uint32 timestamp; uint16 timestamp2; } macMcpsDataCnf_t; hdr.status

有如下的值 MAC_SUCCESS 操作成功 MAC_CHANNEL_ACCESS_FAILURE 通道繁忙,请求失败 MAC_FRAME_TOO_LONG 数据太长 MAC_INVALID_PARAMETER 参数超出范围 MAC_NO_ACK 没有收到应答 MAC_TRANSACTION_EXPIRED 传输期到没有收到响应 MAC_TRANSACTION_OVERFLOW 数据buffer溢出 MAC_MCPS_PURGE_CNF 当调用MAC_McpsPurgeReq()时向应用发送这一事件 typedef struct { macEventHdr_t hdr; uint8 msduHandle; } macMcpsPurgeCnf_t; hdr.status - 清空请求的状态 MAC_SUCCESS 成功 MAC_INVALID_HANDLE 清空请求包含不可用的处理 管理接口 通用常量和数据结构 通道掩码 MAC_CHAN_11_MASK - MAC_CHAN_28_MASK 通道11到28的掩码 比如要使用通道11,12,23则:uint32 chan = MAC_CHAN_11_MASK | MAC_CHAN_12_MASK

| MAC_CHAN_23_MASK; 通道 MAC_CHAN_11 - MAC_CHAN_28

通道11到28 通道页 2.4G只用通道0页MAC_CHANNEL_PAGE_0 性能信息 这些掩码指示了器件的性能信息,在关联操作中将用到这些信息 MAC_CAPABLE_PAN_COORD 器件可以作为PAN协调器 MAC_CAPABLE_FFD 器件是FFD MAC_CAPABLE_MAINS_POWER 器件用的是主干线而不是电池 MAC_CAPABLE_RX_ON_IDLE 空闲时也打开接收 MAC_CAPABLE_SECURITY 能够发送和接收安全帧 MAC_CAPABLE_ALLOC_ADDR 在关联工程中请求分配短地址 属性 可用通过 MAC_MlmeGetReq()和MAC_MlmeSetReq()来读取和设置,实在是太都了,大家看文

档把。 超帧协议 MAC_SFS_BEACON_ORDER(s)

返回信标顺序 MAC_SFS_SUPERFRAME_ORDER(s) 返回超帧顺序 MAC_SFS_FINAL_CAP_SLOT(s) 返回最后的CAP槽 MAC_SFS_BLE(s) 返回电池延寿位 MAC_SFS_PAN_COORDINATOR(s) 返回PAN协调器位 MAC_SFS_ASSOCIATION_PERMIT(s) 返回关联许可位 void MAC_MlmeAssociateReq(macMlmeAssociateReq_t *pData) 向协调器发送关联请求,当请求完成后MAC发送MAC_MLME_ASSOCIATE_CNF给应用 参数: typedef struct { uint8 logicalChannel; uint8 channelPage; sAddr_t coordAddress; uint16 coordPanId; uint8 capabilityInformation; macSec_t sec; } macMlmeAssociateReq_t; void MAC_MlmeAssociateRsp(macMlmeAssociateRsp_t *pData) 给发送关联请求的设备发送应答,在接收到 MAC_MLME_ASSOCIATE_IND后应该调用此函

数,当答复完成后MAC将发送MAC_MLME_COMM_STATUS_IND typedef struct { sAddrExt_t deviceAddress; uint16 assocShortAddress; uint8 status; macSec_t sec; } macMlmeAssociateRsp_t; assocShortAddress

分配给器件的短地址,只有关联成功且器件请求短地址时才会设置此值 void MAC_MlmeDisassociateReq(macMlmeDisassociateReq_t *pData) 已关联的设备通知协调器脱离PAN,或者是协调器指示一个已关联设备脱离PAN,解关联完成

后MAC发送 MAC_MLME_DISASSOCIATE_CNF typedef struct { sAddr_t deviceAddress; uint16 devicePanId; uint8 disassociateReason; bool txIndirect; macSec_t sec; } macMlmeDisassociateReq_t; uint8 MAC_MlmeGetReq(uint8 pibAttribute, void *pValue) 从MAC PIB取得属性 pibAttribute 属性项 pValue 指向属性值的指针 返回值: MAC_SUCCESS 操作成功 MAC_UNSUPPORTED_ATTRIBUTE 没有找到对应的属性 void MAC_MlmeOrphanRsp(macMlmeOrphanRsp_t *pData) 回应节点的orphan宣告 void MAC_MlmePollReq(macMlmePollReq_t *pData) 从协调器请求未决数据,当完成后MAC发送MAC_MLME_POLL_CNF和

MAC_MCPS_DATA_IND typedef struct { sAddr_t coordAddress; uint16 coordPanId; macSec_t sec; } macMlmePollReq_t; uint8 MAC_MlmeResetReq(bool setDefaultPib) 重置

MAC,在系统启动时,这个函数必须被调用一次,并且用设置setDefaultPib为true用MAC

PIB作为默认值 void MAC_MlmeScanReq(macMlmeScanReq_t *pData) 能量检测,激活或失活扫描,在扫描器期间器件不能执行其他的

MAC管理操作,也不能收发

MAC数据 typedef struct { uint32 scanChannels; uint8 scanType; uint8 scanDuration; uint8 channelPage; uint8 maxResults; macSec_t sec; { uint8 *pEnergyDetect; macPanDesc_t *pPanDescriptor; } result; } macMlmeScanReq_t; uint8 MAC_MlmeSetReq(uint8 pibAttribute, void *pValue) 设置属性值到MAC PIB中 返回值: MAC_SUCCESS 操作成功 MAC_UNSUPPORTED_ATTRIBUTE 不支持的属性 MAC_INVALID_PARAMETER 值越界 MAC_READ_ONLY 属性为只读 void MAC_MlmeStartReq(macMlmeStartReq_t *pData) 协调器或者PAN协调器调用此函数开始或者重新配置一个网络。在开始一个网络前,器件必须

设置短地址,PAN协调器通过设置属性MAC_SHORT_ADDRESS来设置短地址,协调器通过关

联设置短地址。 typedef struct { uint32 startTime; uint16 panId; uint8 logicalChannel; uint8 channelPage; uint8 beaconOrder; uint8 superframeOrder; bool panCoordinator; bool batteryLifeExt; bool coordRealignment; macSec_t realignSec; macSec_t beaconSec; } macMlmeStartReq_t; startTime -

开始传输信标的时间,若是PAN协调器或者是非信标网络则忽略此项 panId - 如果panCoordinator为FALSE则忽略此项 logicalChannel - 如果panCoordinator为FALSE则忽略此项 channelPage - 如果panCoordinator为FALSE则忽略此项 beaconOrder - 计算信标帧间隔的指数,对于非信标帧则设为15 superframeOrder-用来计算超帧时间,非信标网络忽略此项 batteryLifeExt- 若设为真,则MAC_BATT_LIFE_EXT_PERIODS完全信标帧帧间后的完全

backoff期间,关闭接收,非信标网络忽略此项 coordRealignment-

当设为真时在改变超帧前发送协调器重列 void MAC_MlmeSyncReq(macMlmeSyncReq_t *pData) 请求MAC通过信标统协调器同步,推荐在和信标网络关联前和协调器同步,如果信标不能被定

为,则MAC向应用发送包含MAC_BEACON_LOSS状态的MAC_MLME_SYNC_LOSS_IND。

在调用这个函数前必须设置用来同步的协调器的地址。MAC成功同步以后将发送

MAC_MLME_BEACON_NOTIFY_IND,应用在收到这个事件后将设置MAC_AUTO_REQUEST

为真用来停止接收信标宣言。 typedef struct { uint8 logicalChannel; uint8 channelPage; bool trackBeacon; } macMlmeSyncReq_t; trackBeacon - 设置为真将继续跟踪同步信标后的其他信标,设为假则只用第一个信标同步。如

果已开始跟踪,设为FALSE则停止跟踪。

今天先来这么多,剩下的部分我再研究研究以后再发生来,呵呵 ZigBee

学习之11——MAC层API解读2  好了,今天把MAC的API 剩下的部分解决了吧,呵呵 回调函数事件 呵呵,名字很恐怖,其实就是用来应答请求函数的函数调用而已,不要想的太深奥哦! MAC_MLME_ASSOCIATE_IND 当MAC从其他器件接收到关联请求时发送给应有。应用在接收到这个事件后必须调用

MAC_MlmeAssociateRsp(),这个事件只发送给FFD应用,且这个FFD的PIB属性

MAC_ASSOCIATION_PERMIT为真。 MAC_MLME_ASSOCIATE_CNF 作为对

MAC_MlmeAssociateReq()的回应,MAC发送这个事件到应用,此事件指示了关联的状

态,如果关联成功且请求了短地址,则短地址将被包含在事件中,否则短地址参数不可用。 MAC_MLME_DISASSOCIATE_IND 指示器件已经解关联 MAC_MLME_DISASSOCIATE_CNF 作为

MAC_MlmeDisassociateReq()的回应发送给应用,此事件指示解关联操作的状态。 MAC_MLME_BEACON_NOTIFY_IND 当MAC接收到一个信标帧并且信标帧包含负载数据或者MAC_AUTO_REQUEST属性为FALSE

时发送次事件到应有,此事件也包含LQI测量值、信标帧接收时间。 MAC_MLME_ORPHAN_IND MAC_MLME_SCAN_CNF MAC_MLME_START_CNF MAC_MLME_SYNC_LOSS_IND MAC_MLME_POLL_CNF MAC_MLME_COMM_STATUS_IND 发送这个事件的原因有多种,比如指示

MAC_MlmeAssociateRsp()和MAC_MlmeOrphanRsp()的

状态,也可以指示MAC接收到安全帧,但是安全处理出错。 MAC_MLME_POLL_IND 只有在

MAC用一系列间接数据配置,设置macCfg.appPendingQueue为真,才能发送这个事件。

当从其他设备接收到数据请求命令帧时发送此事件,应用应该调用MAC_McpsDataReq()来发送

数据到那个设备,并且配置TX属性为MAC_TXOPTION_NO_RETRANS设置。 扩展接口 提供了一些非

802.15.4协议定义的函数,比如电源管理。 通用常量和数据结构 电源模式 MAC_PWR_ON MAC和无线电电源打开 MAC_PWR_SLEEP_LITE MAC和无线电部分电源关闭 MAC_PWR_SLEEP_DEEP MAC和无线电完全电源关闭 uint8 MAC_PwrOffReq(uint8 mode) 请求MAC关闭无线电硬件电源并进入睡眠模式, 返回值: MAC_SUCCESS 操作成功 MAC_DENIED MAC不能关闭电源 void MAC_PwrOnReq(void) 请求MAC打开无线电硬件电源并唤醒设备 uint8 MAC_PwrMode(void) 返回MAC当前电源模式 uint32 MAC_PwrNextTimeout(void) 返回下一次MAC定时器超时,如果没有定时器运行则返回0 uint8 MAC_RandomByte(void) 从MAC随机数产生器返回一个随机字节 回调接口 void MAC_CbackEvent(macCbackEvent_t *pData) 发送MAC事件到应用,这个函数的执行需要分配一个OSAL消息,然后将事件参数拷贝到消息,

然后将消息发送到应用OSAL事件处理函数。这个函数必须从任务或者是中断上下文执行,所

以它是可重入的。 参数: typedef struct { uint8 event; uint8 status; } macEventHdr_t; typedef union { macEventHdr_t hdr; macMlmeAssociateInd_t associateInd; macMlmeAssociateCnf_t associateCnf; macMlmeDisassociateInd_t disassociateInd; macMlmeDisassociateCnf_t disassociateCnf; macMlmeBeaconNotifyInd_t beaconNotifyInd; macMlmeOrphanInd_t orphanInd; macMlmeScanCnf_t scanCnf; macMlmeStartCnf_t startCnf; macMlmeSyncLossInd_t syncLossInd; macMlmePollCnf_t pollCnf; macMlmeCommStatusInd_t commStatusInd; macMlmePollInd_t pollInd; macMcpsDataCnf_t dataCnf; macMcpsDataInd_t dataInd; macMcpsPurgeCnf_t purgeCnf; } macCbackEvent_t; hdr.event域为下面一些值 MAC_MLME_ASSOCIATE_IND Associate indication. MAC_MLME_ASSOCIATE_CNF Associate confirm. MAC_MLME_DISASSOCIATE_IND Disassociate indication. MAC_MLME_DISASSOCIATE_CNF Disassociate confirm. MAC_MLME_BEACON_NOTIFY_IND Beacon notify indication. MAC_MLME_ORPHAN_IND Orphan indication. MAC_MLME_SCAN_CNF Scan confirm. MAC_MLME_START_CNF Start confirm. MAC_MLME_SYNC_LOSS_IND Sync loss indication. MAC_MLME_POLL_CNF Poll confirm. MAC_MLME_COMM_STATUS_IND Communication status indication. MAC_MLME_POLL_IND Poll indication. MAC_MCPS_DATA_CNF Data confirm. MAC_MCPS_DATA_IND Data indication. MAC_MCPS_PURGE_CNF Purge confirm. MAC_PWR_ON_CNF Power on confirm. uint8 MAC_CbackCheckPending(void) 返回应用中的间接消息队列 配置 配置常量 MAC用数据结构来包含多种用户配置参数。这个数据结构叫macCfg在mac_cfg.c中定义 typedef struct { uint8 txDataMax; uint8 txMax; uint8 rxMax; uint8 dataIndOffset; bool appPendingQueue; } macCfg_t; txDataMax 传输数据队列中的最大数据帧队列数目,范围:1-255,默认值: 2 txMax 传输数据队列中的最大帧队列数目,范围:1-255,默认值: 5 rxMax 接收数据队列中的数据帧队列数目,范围:1-255,默认值: 2 dataIndOffset 为应用定义的头发配额外的数据字节,范围:0-127,默认值: 0 appPendingQueue当设置为真时,在从其他设备接收到数据请求帧将发送

MAC_MLME_POLL_IND,默认值为FALSE 编译时配置 MAC

源码有一些编译时配置的参数。 MAC_NO_PARAM_CHECK 设置为FALSE,则会检测API函数的参数是否符合IEEE规范。设

置为TRUE则可以减少代码量,默认为FALSE MACNODEBUG

如果定义了这个宏,则允许MAC_ASSERT()进行运行时检测。不定义这个宏可

以优化代码量 MAC_RX_ONOFF_DEBUG_LED

如果为TRUE,LED将会随着接收的开关而开关。 在文档的最后画出了如果用API函数构建网络,进行通信的梯形图,呵呵早知道有这个,就直

接看这个了,不细心阿!不过看来这个文档之后对ZigBee的组网方式有了一定的了解,起码心

里有个底了,知道网络是如何建立的,一个新设备如何加入到一个网络中。 

ZigBee学习之

学习之学习之

学习之12-对

对对

对ZDO的初步理解

的初步理解的初步理解

的初步理解 一直把

ZDO当成一个节点设备,其实ZDO是ZigBee协议栈中的一个协议,负责所有设备的管

理,安全方案。ZDO就好像是一个驻留在所有ZigBee节点中特殊应用对象,是应用层其他端点

与应用子层管理实体交互的中间件。ZDO的配置叫做ZDP(ZigBee设备配置ZigBee Device

Profile)ZDP可以被应用终端(application end points)和ZigBee节点访问。ZDO占用每个节点

(node)的0终端(Endpoint0)(我晕,又搞不清什么是Endpoint什么事node了!理解起来好

像Endpoint是应用对象,node好像是一个硬件节点,一个节点可以有最多254个终端,也就是

说一个节点可以配置为254种应用来使用,在两个通信端点只见能让多个应用循环使用APS,

这就是ZigBee的应用支持子层提供的多路选择功能。)这里摘录一下网络收集的资料: 1.3.3.3 ZigBee

设备对象ZDO ZDO是一个特殊的应用层的端点(Endpoint)。它是应用层其他端点与应用子层管理实体交互的

中间件。它主要提供的功能如下: (

1)初始化应用支持子层,网络层。 (2)发现节点和节点功能。在无信标的网络中,加入的节点只对其父节点可见。而其他节点可

以通过ZDO的功能来确定网络的整体拓扑结构已经节点所能提供的功能。 (

3)安全加密管理:主要包括安全key的建立和发送,已经安全授权。 (4)网络的维护功能。 (5)绑定管理:绑定的功能由应用支持子层提供,但是绑定功能的管理却是由ZDO提供,它

确定了绑定表的大小,绑定的发起和绑定的解除等功能。 (

6)节点管理:对于网络协调器和路由器,ZDO提供网络监测、获取路由和绑定信息、发起脱

离网络过程等一系列节点管理功能。 ZDO

实际上是介于应用层端点和应用支持子层中间的端点,其主要功能集中在网络管理和

维护上。应用层的端点可以通过 ZDO提供的功能来获取网络或者是其他节点的信息,包括

网络的拓扑结构、其它几点的网络地址和状态以及其他几点的类型和提供的服务等信息。 ZigBee

学习之

学习之学习之

学习之13-ZStack API解读

解读解读

解读 Z-Stack API[Z-Stack API _F8W-2006-0021_.pdf] 这个文档是一个关键了,Z-Stack的应用程序接口。我们在程序中基本上应该调用的是这些API

吧。 ZDO

层层

层API

实现了所有ZDP(ZigBee Device Profile)定义的命令和回应所需要的函数。ZDP描述了ZDO如

何实现普通ZigBee设备的特性,它定义了设备描述和簇,ZDP为ZDO和应用提供一下功能: ? 设备网络建立 ? 设备和服务发现 ? 节点设备邦定和解邦定服务 ? 网络管理服务  设备发现是ZigBee设备发现其他ZigBee设备的过程。比如将已知的IEEE地址作为数据载荷广

播到网络的NWK地址请求,相关设备必须回应并告知其网络地址。服务发现提供了PAN中一

个设备发现其他设备提供的服务的能力。它利用多种描述符去指定设备的能力。 当用户需要邦定控制器与被控设备(比如开关和灯)时,将用到邦定和解邦定服务。特别是终端

设备邦定支持利用用户的输入来定义控制/被控设备对的简单邦定方法。邦定和解邦服务可以创

建和删除邦定表入口。 网络管理服务主要提供给用户和调试工具网络管理的能力,它能够从设备重新获得管理信息,包

括网络发现结果,路由表的内容,邻居节点链路质量,邦定表内容。也可以通过解关联将设备从

PAN中脱离来控制网络关联。 ZDO

设备网络建立

设备网络建立设备网络建立

设备网络建立

在ZigBee网络中默认ZDApp_Init()[in ZDApp.c]开始器件的启动,但是应用能覆盖整个默认行为,

为了使应用接管网络的启动,必须在编译选项中包含HOLD_AUTO_START,推荐同时包含

NV_RESTORE(储存ZigBee网络状态到NV)编译选项,包含这个两个编译选项后就需要调用

ZDOInitDevice() 来启动网络中的设备。 uint8 ZDOInitDevice( uint16 startDelay ) 启动网络中的设备。如果应用想要强制一个

“新的”加入(即不恢复网络状态),则应该先调用

zgWriteStartupOptions(ZG_STARTUP_SET, ZCD_STARTOPT_DEFAULT_NETWORK_STATE)

来设置ZCD_NV_STARTUP_OPTION 中的ZCD_STARTOPT_DEFAULT_NETWORK_STATE

位。 startDelay -

启动设备的延时。这个延时有一个抖动: (NWK_START_DELAY + startDelay) + (osal_rand() & EXTENDED_JOINING_RANDOM_MASK) 返回值: ZDO_INITDEV_RESTORED_NETWORK_STATE 设备的网络状态恢复 ZDO_INITDEV_NEW_NETWORK_STATE 网络状态被初始化。即一个强制“新的”加入,或者没

有可供恢复的状态 ZDO_INITDEV_LEAVE_NOT_STARTED

重置前,发送了网络脱离并且重加入被设置为TRUE。

因此器件没有启动,但是下次调用此函数将启动。 ZDO

消息回馈

消息回馈消息回馈

消息回馈

通过ZDO_RegisterForZDOMsg()注册消息,应用就能接收任何空中(over the air)消息。 ZStatus_t ZDO_RegisterForZDOMsg( uint8 taskID, uint16 clusterID ) 调用此函数请求

over-the-air消息,此消息的备份将以OSAL消息发送到任务,接收到消息的任

务可以自己解析消息,也可以调用ZDO解析函数来解析消息。只有响应消息有ZDO解析函数。 消息注册并被接收后(

OTA),将作为ZDO_CB_MSG (OSAL Msg)发送到应用/任务。消息体

(zdoIncomingMsg_t – defined in ZDProfile.h)包含有OTA消息。 taskID -

用来发送OSAL消息的应用任务ID; clusterID -想要接收的OTA消息的clusterID。如:NWK_addr_rsp。这些ID定义在ZDProfile.h 返回值:ZStatus_t -ZComDef.h中ZStatus_t定义的状态值 ZStatus_t ZDO_RemoveRegisteredCB( uint8 taskID, uint16 clusterID ) 为OTA消息移除一个请求。 参数必须和ZDO_RegisterForZDOMsg()中的参数相同 ZDO发现

发现发现

发现API

这些API用来建立和发送ZDO设备和服务发现请求和回复。ZDO API和ZDP命令一一对应,

ZDP命令可以在ZigBee协议规范中找到更详细的介绍。 ZDO API Function ZDP Discovery Command ZDP_NwkAddrReq() NWK_addr_req ZDP_NWKAddrRsp() NWK_addr_rsp ZDP_IEEEAddrReq() IEEE_addr_req ZDP_IEEEAddrRsp() IEEE_addr_rsp ZDP_NodeDescReq() Node_Desc_req ZDP_NodeDescRsp() Node_Desc_rsp ZDP_PowerDescReq() Power_Desc_req ZDP_PowerDescRsp() Power_Desc_rsp ZDP_SimpleDescReq() Simple_Desc_req ZDP_SimpleDescRsp() Simple_Desc_rsp ZDP_ComplexDescReq() Complex_Desc_req ZDP_ActiveEPIFReq() Active_EP_req ZDP_ActiveEPIFRsp() Active_EP_rsp ZDP_MatchDescReq() Match_Desc_req ZDP_MatchDescRsp() Match_Desc_rsp ZDP_UserDescSet() User_Desc_set ZDP_UserDescConf() User_Desc_conf ZDP_UserDescReq() User_Desc_req ZDP_UserDescRsp() User_Desc_rsp ZDP_EndDeviceAnnce() Device_annce ZDP_ServerDiscReq() System_Server_Discovery_req ZDP_ServerDiscRsp() System_Server_Discovery_rsp afStatus_t ZDP_NwkAddrReq( byte *IEEEAddress, byte ReqType,byte StartIndex, byte

SecuritySuite ) 发送为一个

IEEE地址已知的设备请求16位短地址的消息,消息将以广播的形式发送。 IEEEAddress -发送请求的设备的IEEE地址 ReqType -希望收到的答复类型 ZDP_NWKADDR_REQTYPE_SINGLE 返回设备的短地址和扩展地址 ZDP_NWKADDR_REQTYPE_EXTENDED 返回设备的短地址和扩展地址,以及所有关联设备的

短地址 StartIndex -

应答设备可能含有多条符合应答消息的应答条目,此参数指定请求开始检索的应答条

目 SecuritySuite -

消息的安全类型 返回值:afStatus_t -次函数用AF来发送消息,所以状态值为ZComDef.h中ZStatus_t定义的AF

状态值 afStatus_t ZDP_NWKAddrRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte *IEEEAddrRemoteDev, byte ReqType, uint16 nwkAddr, byte NumAssocDev, byte StartIndex, uint16 *NWKAddrAssocDevList, byte SecuritySuite ); 这个函数实际上是调用

ZDP_AddrRsp()的宏,用来建立和发送网络地址应答 TranSeq – 报文序号 DstAddr - 目标地址 Status – ZDP_SUCCESS 0 ZDP_INVALID_REQTYPE 1 ZDP_DEVICE_NOT_FOUND 2 Reserved 0x03-0xff IEEEAddrRemoteDev - 远端设备的64位地址 ReqType – 请求类型 nwkAddr – 远端设备的16位地址 NumAssocDev - 和远端设备相关联的设备及其16位短地址的数目 StartIndex - 应答消息的开始序号 NWKAddrAssocDevList -相关联的16位地址列表 SecuritySuite - 消息安全类型 afStatus_t ZDP_IEEEAddrReq( uint16 shortAddr, byte ReqType, byte StartIndex, byte SecuritySuite ) 已知设备的16位短地址请求64位IEEE地址。 呵呵好多参数都是一看就知道是什么了呢,那就不写出来了,下面的函数也一样的处理了。 afStatus_t ZDP_IEEEAddrRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte *IEEEAddrRemoteDev, byte ReqType, uint16 nwkAddr, byte NumAssocDev, byte StartIndex, uint16 *NWKAddrAssocDevList, byte SecuritySuite ); 这个函数实际上是调用ZDP_AddrRsp()的宏,用来建立和发送IEEE地址应答 afStatus_t ZDP_NodeDescReq( zAddrType_t *dstAddr, uint16 NWKAddrOfInterest, byte

SecuritySuite ); 构建并向目标地址域发送节点描述请求 afStatus_t ZDP_NodeDescMsg( byte TransSeq, zAddrType_t *dstAddr, byte Status, uint16 nwkAddr, NodeDescriptorFormat_t *pNodeDesc, byte SecuritySuite ); DstAddr -

目标地址 NWKAddrOfInterest - 要搜寻的16位短地址 SecuritySuite - 消息的安全类型 afStatus_t ZDP_NodeDescMsg( byte TransSeq, zAddrType_t *dstAddr, byte Status, uint16 nwkAddr, NodeDescriptorFormat_t *pNodeDesc, byte SecuritySuite ); 回应节点描述(Node Descriptor)请求。 Status - SUCCESS 0 DEVICE_NOT_FOUND 1 pNodeDesc -指向节点描述的指针(定义在AF.h中) afStatus_t ZDP_PowerDescReq( zAddrType_t *dstAddr, int16 NWKAddrOfInterest,byte

SecuritySuite ); 构建和发送电源描述请求。实际是调用宏

ZDP_NWKAddrOfInterestReq() afStatus_t ZDP_PowerDescMsg( byte TranSeq, zAddrType_t *dstAddr, byte Status, int16 nwkAddr, NodePowerDescriptorFormat_t *pPowerDesc, byte SecuritySuite ); 回应电源描述请求。 Status - SUCCESS 0 DEVICE_NOT_FOUND 1 pPowerDesc -指向电源描述的指针(定义在AF.h中) afStatus_t ZDP_SimpleDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte epIntf, byte

SecuritySuite ); 构建和发送简单描述请求。 epIntf -

希望的应用终端/接口 afStatus_t ZDP_SimpleDescRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, SimpleDescriptionFormat_t *pSimpleDesc, byte SecuritySuite ); 回应简单描述请求。 Status - SUCCESS 0 INVALID_EP 1 NOT_ACTIVE 2 DEVICE_NOT_FOUND 3 afStatus_t ZDP_ComplexDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte SecuritySuite ); 构建和发送复杂描述请求。实际是调用宏ZDP_NWKAddrOfInterestReq() zigbee设备以描述项数据结构刻画自己,包含在描述项中的具体数据在描述中定义,有5种描述:

节点,电源,简化,复杂和用户。 afStatus_t ZDP_ActiveEPIFReq( zAddrType_t *dstAddr, uint16 NWKAddrOfInterest,byte SecuritySuite ); 构建和发送活动终端

/接口请求,实际是调用宏ZDP_NWKAddrOfInterestReq(),用来请求远端设

备上所有活动的终端/接口 NWKAddrOfInterest -

搜寻的16位短地址 afStatus_t ZDP_ActiveEPIFRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, uint16 nwkAddr, byte Count, byte *pEPIntfList, byte SecuritySuite ); 回应发送的活动终端/接口请求,实际是调用宏ZDP_EPIFRsp() Status - SUCCESS 0 DEVICE_NOT_FOUND 1 Count – pEPIntfList中活动终端(endpoint)/接口数目 pEPIntfList – 包含器件上终端(endpoint)/接口的数组 afStatus_t ZDP_MatchDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, uint16 ProfileID, byte NumInClusters, byte *InClusterList, byte NumOutClusters, byte *OutClusterList, byte SecuritySuite ); 构建并发送匹配描述请求,用来搜寻符合应用列表中某些输入输出簇得器件/接口 ProfileID - cluster ID相关的ProfileID NumInClusters - 输入簇中的cluster ID数目 InClusterList - 输入cluster IDs的数组 afStatus_t ZDP_MatchDescRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, uint16 nwkAddr, byte Count, byte *pEPIntfList, byte SecuritySuite ); Status - SUCCESS 0 DEVICE_NOT_FOUND 1 Count – pEPIntfList中活动终端(endpoint)/接口数目 pEPIntfList – 包含器件上终端(endpoint)/接口的数组 afStatus_t ZDP_UserDescSet( zAddrType_t *dstAddr,uint16 nwkAddr, UserDescriptorFormat_t *UserDescriptor, byte SecurityEnable ); 构建并发送User_Desc_set消息来设置远端设备的用户描述,这个请求单播到包含有发现信息的

远端设备。远端设备需要定义NV_RESTORE来使能整个函数。 UserDescriptor –

配制的用户描述,包含最多16个字符的ASCII字符串,若不足16个字符,则用

空字符(0x20)填充到16个 afStatus_t ZDP_UserDescConf( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte SecurityEnable ); 调用这个函数来回应

User_Desc_Conf Status - SUCCESS 0x00 INV_REQUESTTYPE 0x80 DEVICE_NOT_FOUND 0x81 NOT_SUPPORTED 0x84 afStatus_t ZDP_UserDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte SecurityEnable ); 构建并发送User_Desc_Req ZStatus_t ZDP_UserDescRsp( byte TransSeq, zAddrType_t *dstAddr, uint16 nwkAddrOfInterest, UserDescriptorFormat_t *userDesc, byte SecurityEnable ); userDesc -本地设备的用户描述 afStatus_t ZDP_EndDeviceAnnce( uint16 nwkAddr, byte *IEEEAddr,byte capabilities, byte

SecurityEnable ); 为

ZigBee节点设备构建并发送End_Device_annce命令,通知其他ZigBee设备,此设备已加入

或者已重新加入网络。此命令包含节点设备的新16位网络地址和64位IEEE地址,即以节点设

备的功能。此消息已广播式发送。 afStatus_t ZDP_ServerDiscReq( uint16 serverMask, byte SecurityEnable ); 构建并发送包含

16位服务器掩码的System_Server_Discovery_req请求消息,以发现特殊系统服

务器的位置或者服务器掩码指示的服务器。消息包含RxOnWhenIdle,以广播式发送。 ZStatus_t ZDP_ServerDiscRsp( byte transID, zAddrType_t *dstAddr, byte status, uint16 aoi, uint16 serverMask, byte SecurityEnable ); ZigBee

学习之

学习之学习之

学习之14——ZStack API解读

解读解读

解读2 ZDO邦定

邦定邦定

邦定API 绑定机制允许一个应用服务在不知道目标地址的情况下向对方(的应用服务)发送数据包。

发送时使用的目标地址将由应用支持子层从绑定表中自动获得,从而能使消息顺利被目标节点的

一个或多个应用服务,乃至分组接收。 由于所有邦定信息都在

Zigbee协调器中,所以只有协调器才能接收邦定请求。 ZDO Binding API ZDP Binding Service Command ZDP_EndDeviceBindReq() End_Device_Bind_req ZDP_EndDeviceBindRsp() End_Device_Bind_rsp ZDP_BindReq() Bind_req  ZDP_BindRsp() Bind_rsp ZDP_UnbindReq() Unbind_req ZDP_UnbindRsp() Unbind_rsp afStatus_t ZDP_EndDeviceBindReq( zAddrType_t *dstAddr,uint16 LocalCoordinator, byte epIntf, uint16 ProfileID,byte NumInClusters, byte *InClusterList, byte NumOutClusters, byte *OutClusterList,byte SecuritySuite ); 构建并发送节点设备邦定请求(Hand Bingding)。 LocalCoordinator - 设备父协调器的16位网络地址 NumInClusters - 输入簇中的cluster ID数目 InClusterList - 输入cluster IDs的数组 afStatus_t ZDP_EndDeviceBindRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte SecurityEnable ); Status - SUCCESS 0 NOT_SUPPORTED 1 TIMEOUT 2 NO_MATCH 3 afStatus_t ZDP_BindReq( zAddrType_t *dstAddr, byte *SourceAddr, byte SrcEPIntf, byte ClusterID, byte *DestinationAddr, byte DstEPIntf, byte SecuritySuite ); 请求协调器利用cluster ID邦定应用 ClusterID – 要邦定的cluster ID DestinationAddr – 接收消息的设备的64位地址 afStatus_t ZDP_BindRsp( byte TranSeq, zAddrType_t *dstAddr,byte Status, byte SecurityEnable ); Status - SUCCESS 0 NOT_SUPPORTED 1 TABLE_FULL 2 afStatus_t ZDP_UnbindReq( zAddrType_t *dstAddr, byte *SourceAddr, byte SrcEPIntf, byte ClusterID, byte *DestinationAddr, byte DstEPIntf, byte SecuritySuite ); 请求Zigbee协调器移除邦定。 afStatus_t ZDP_UnbindRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status,byte SecurityEnable ); Status - SUCCESS 0x00 NOT_SUPPORTED 1 NO_ENTRY 2 ZDO管理

管理管理

管理API 这些消息用来取得设备状态和更新表格 ZDP Management API ZDP Network Management Service Command ZDP_MgmtNwkDiscReq() Mgmt_NWK_Disc_req ZDP_MgmtNwkDiscRsp() Mgmt_NWK_Disc_rsp ZDP_MgmtLqiReq() Mgmt_Lqi_req ZDP_MgmtLqiRsp() Mgmt_Lqi_rsp ZDP_MgmtRtgReq() Mgmt_Lqi_req ZDP_MgmtRtgRsp() Mgmt_Rtg_rsp ZDP_MgmtBindReq() Mgmt_Bind_req ZDP_MgmtBindRsp() Mgmt_Bind_rsp ZDP_MgmtLeaveReq() Mgmt_Leave_req ZDP_MgmtLeaveRsp() Mgmt_Leave_rsp ZDP_MgmtDirectJoinReq() Mgmt_Direct_Join_req ZDP_MgmtDirectJoinRsp() Mgmt_Direct_Join_rsp ZDP_MgmtPermitJoinReq() Mgmt_Permit_Join_req ZDP_MgmtPermitJoinRsp() Mgmt_Permit_Join_rsp afStatus_t ZDP_MgmtNwkDiscReq( zAddrType_t *dstAddr, uint32 ScanChannels, byte StartIndex, byte SecurityEnable ); 如果设备支持这个命令,调用此函数将为目标设备产生一个扫描网络的请求。只有设置

ZDO_MGMT_NWKDISC_REQUEST编译选项(ZDConfig.h)才能调用此函数。 afStatus_t ZDP_MgmtNwkDiscRsp( byte TranSeq, zAddrType_t *dstAddr,byte Status, byte NetworkCount, byte StartIndex, byte NetworkCountList, networkDesc_t *NetworkList, byte SecurityEnable ); 若设置

ZDO_MGMT_NWKDISC_RESPONSE编译选项,当接收到“Management Network

Discovery Request”消息后将自动产生这个消息。 afStatus_t ZDP_MgmtLqiReq ( zAddrType_t *dstAddr,byte StartIndex, byte SecurityEnable ); 若设置

ZDO_MGMT_LQI_REQUEST编译选项,调用此函数将为目标设备产生返回邻居列表的

请求。 ZStatus_t ZDP_MgmtLqiRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte NeighborLqiEntries, byte StartIndex, byte NeighborLqiCount, neighborLqiItem_t *NeighborLqiList, byte SecurityEnable ); afStatus_t ZDP_MgmtRtgReq( zAddrType_t *dstAddr, byte StartIndex, byte SecurityEnable ); 若设置

ZDO_MGMT_RTG_REQUEST编译选项,调用此函数将为目标设备产生返回路由列表的

请求。 ZStatus_t –

状态值,定义在ZComDef.h中的ZStatus_t ZStatus_t ZDP_MgmtRtgRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte RoutingTableEntries, byte StartIndex, byte RoutingListCount, rtgItem_t *RoutingTableList, byte SecurityEnable ); 若设置ZDO_MGMT_RTG_REQUEST编译选项,当接收到“Management Routing Request”消息后

将自动产生这个消息。 afStatus_t ZDP_MgmtBindReq( zAddrType_t *dstAddr, byte StartIndex, byte SecurityEnable ); 若设置

ZDO_MGMT_BIND_REQUEST编译选项,调用此函数将为目标设备产生返回邦定表的

请求。 ZStatus_t ZDP_MgmtBindRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte BindingTableEntries, byte StartIndex, byte BindingTableListCount, apsBindingItem_t *BindingTableList, byte SecurityEnable ); afStatus_t ZDP_MgmtLeaveReq( zAddrType_t *dstAddr,byte *IEEEAddr, byte SecurityEnable ); 若设置ZDO_MGMT_LEAVE_REQUEST编译选项,调用此函数将请求目标设备脱离网络或者请

求其他设备脱离网络 ZStatus_t ZDP_MgmtLeaveRsp( byte TranSeq, zAddrType_t *dstAddr,byte Status, byte SecurityEnable ); 若设置

ZDO_MGMT_LEAVE_REQUEST编译选项,当接收到“Management Leave Reques”消息后

将自动产生这个消息。 afStatus_t ZDP_MgmtDirectJoinReq( zAddrType_t *dstAddr,byte *deviceAddr, byte capInfo, byte SecurityEnable ); 若设置

ZDO_MGMT_JOINDIRECT_REQUEST编译选项,调用此函数将请求目标设备直接加入

其他设备。 deviceAddr –

要加入的设备的64位地址 capInfo – 要加入设备的性能 CAPINFO_ALTPANCOORD 0x01 CAPINFO_DEVICETYPE_FFD 0x02 CAPINFO_POWER_AC 0x04 CAPINFO_RCVR_ON_IDLE 0x08 CAPINFO_SECURITY_CAPABLE 0x40 CAPINFO_ALLOC_ADDR 0x80 ZStatus_t ZDP_MgmtDirectJoinRsp( byte TranSeq, zAddrType_t *dstAddr,byte Status, byte

SecurityEnable); afStatus_t ZDP_MgmtPermitJoinReq( zAddrType_t *dstAddr, byte duration, byte TcSignificance, byte SecurityEnable ); 实际是直接调用宏

ZDP_SendData (),函数构建并发送Mgmt_Permit_Joining_req来请求远端设

备允许或不允许关联。这个请求由调试工具或者网络管理设备产生, duration -

协调器或路由器允许关联的时间(单位为秒),0x00和0xff分别表示没有时间限制的

禁止和允许。 TcSignificance -

如果设为0x01且远端设备为真实中心设备的话,命令将影响中心设备的授权机

制。 ZStatus_t ZDP_MgmtPermitJoinRsp( byte *TransSeq,zAddrType_t *dstAddr, byte *Statue, byte SecurityEnable); ZDO

解析函数

解析函数解析函数

解析函数 用来解析接收到的消息 ZDO_NwkIEEEAddrResp_t *ZDO_ParseAddrRsp( zdoIncomingMsg_t *inMsg ); 解析NWK_addr_rsp和IEEE_addr_rsp消息 inMsg -指向接收到的消息的指针 ZDO_NwkIEEEAddrResp_t -指向解析后的结构的指针,结构体由osal_mem_alloc分配空间,所

以需要调用osal_mem_free()来释放空间 void ZDO_ParseNodeDescRsp( zdoIncomingMsg_t *inMsg,ZDO_NodeDescRsp_t *pNDRsp ); 解析

Node_Desc_rsp消息 pNDRsp -解析消息存放的地方 void ZDO_ParsePowerDescRsp( zdoIncomingMsg_t *inMsg,ZDO_PowerRsp_t *pNPRsp ); 解析Power_Desc_rsp消息 void ZDO_ParseSimpleDescRsp( zdoIncomingMsg_t *inMsg,ZDO_SimpleDescRsp_t

*pSimpleDescRsp ); pSimpleDescRsp -

存放解析后的消息 ZDO_ActiveEndpointRsp_t *ZDO_ParseEPListRsp( zdoIncomingMsg_t *inMsg ); 解析Active_EP_rsp或者Match_Desc_rsp的消息 #define ZDO_ParseBindRsp(a) ((uint8)(*(a->asdu))) 解析Bind_rsp, Unbind_rsp或End_Device_Bind_rsp消息 a -指向要解析的消息的指针 ZDO_MgmNwkDiscRsp_t *ZDO_ParseMgmNwkDiscRsp( zdoIncomingMsg_t *inMsg ); 解析Mgmt_NWK_Disc_rsp消息 ZDO_MgmtLqiRsp_t *ZDO_ParseMgmtLqiRsp( zdoIncomingMsg_t *inMsg ); ZDO_MgmtRtgRsp_t *ZDO_ParseMgmtRtgRsp( zdoIncomingMsg_t *inMsg ); ZDO_MgmtBindRsp_t *ZDO_ParseMgmtBindRsp( zdoIncomingMsg_t *inMsg ); #define ZDO_ParseMgmtDirectJoinRsp(a) ((uint8)(*(a->asdu))) #define ZDO_ParseMgmtLeaveRsp(a) ((uint8)(*(a->asdu))) #define ZDO_ParseMgmtPermitJoinRsp(a) ((uint8)(*(a->asdu))) ZDO_UserDescRsp_t *ZDO_ParseUserDescRsp( zdoIncomingMsg_t *inMsg ); void ZDO_ParseServerDiscRsp( zdoIncomingMsg_t *inMsg,ZDO_ServerDiscRsp_t *pRsp ); 解析Server_Discovery_rsp消息 void ZDO_ParseEndDeviceBindReq( zdoIncomingMsg_t *inMsg,ZDEndDeviceBind_t *bindReq ); void ZDO_ParseBindUnbindReq( zdoIncomingMsg_t *inMsg,ZDO_BindUnbindReq_t *pReq ); #define ZDO_ParseUserDescConf(a) ((uint8)(*(a->asdu))) void ZDO_ParseDeviceAnnce( zdoIncomingMsg_t *inMsg, ZDO_DeviceAnnce_t *pAnnce ); ZDO_MgmtNwkUpdateNotify_t *ZDO_ParseMgmtNwkUpdateNotify(zdoIncomingMsg_t *inMsg ); ZigBee学习之

学习之学习之

学习之15——ZStack API解读

解读解读

解读3 应用框架

应用框架应用框架

应用框架(

((

(AF)

))

) 应用框架层是应用道APS层的OTA数据接口。此层也接收数据消息的终端多路复用器。AF

为应用提供以下功能: ? 终端(

Endpoint)管理 ? 发送和接收数据 哈哈,这里的函数应该就是我们经常要用到的函数了。 终端管理

终端管理终端管理

终端管理 每个设备都是Zigbee中的节点,每个节点有长地址和短地址,短地址被其他设备用来发送数据。

每个节点又241个终端(0保留,1-240 可分配给应用)。每个终端可以独立设置地址;当设备

发送数据时必须指定目标设备的短地址和接收终端。一个应用必须注册一个或多个终端用来接收

或者发送数据。 简单描述符

简单描述符简单描述符

简单描述符-SimpleDescriptionFormat_t 每个终端都必须有一个Zigbee简单描述。这些描述对Zigbee网络刻画了这个终端,其他设备可

以询问这个终端以知道这个设备的类型。 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; EndPoint – 终端号:1-240 这是节点的子地址,用来接收数据 AppProfId – 定义了这个终端上支持的Profile ID(剖面ID), ID最好遵循由ZigBee联盟的分

配。 AppDeviceId –

终端支持的设备ID,ID最好遵循ZigBee联盟的分配。 AppDevVer –此终端上设备执行的设备描述的版本:0x00为Version 1.0. Reserved – 保留 AppNumInClusters – 终端支持的输入簇数目 pAppInClusterList – 指向输入Cluster ID列表的指针 AppNumOutClusters – 终端支持的输出簇数目 pAppOutClusterList – 指向输出Cluster ID列表的指针 终端描述符

终端描述符终端描述符

终端描述符-endPointDesc_t 节点中的每一个终端都必须有一个终端描述符 typedef struct { byte endPoint; byte *task_id; // Pointer to location of the Application task ID. SimpleDescriptionFormat_t *simpleDesc; afNetworkLatencyReq_t latencyReq; } endPointDesc_t; task_id -任务ID指针,当接收到消息时,此任务ID将指示消息传递目的。接收到的消息是以

OSAL消息形式包装的,将发送到一个任务 simpleDesc -

指向这个终端的ZigBee简单描述 latencyReq -必须用noLatencyReqs来填充 afStatus_t afRegister( endPointDesc_t *epDesc ); 为设备注册一个新的终端 epDesc -指向终端描述符 返回值:afStatus_t -若成功则返回ZSuccess,否则返回ZComDef.h中定义的错误 epList_t *afRegisterExtended( endPointDesc_t *epDesc, pDescCB descFn ); 在上面函数功能的基础上增加了回调函数,当终端的简单描述符被查询时将调用此回调函数。这

样应有就可以动态改变简单描述符而不用RAM/ROM来储存描述符了。 descFn -

回调函数指针。相关函数必须为简单描述符分配足够的空间,填充简单描述符,然后返

回指向简单描述符的指针,调用者将释放为描述符分配空间。 epList -

指向终端列表元件的指针,如果失败则为NULL endPointDesc_t *afFindEndPointDesc( byte endPoint ); 从一个终端找到终端描述符 endPoint -要寻找的终端描述符的终端号 endPointDesc_t -指向终端描述符的指针,若失败则为NULL byte afFindSimpleDesc( SimpleDescriptionFormat_t **ppDesc, byte EP ); 从一个终端找到终端描述符。若返回值非零则必须调用osal_mem_free()来释放描述符的内存占

用。 ppDesc -

指向指向简单描述符的指针。 EP –终端简单描述符需要 uint8 afGetMatch( uint8 ep ); 默认情况下,设备将响应ZDO匹配描述符请求。用这个函数来获得ZDO匹配描述符应答的设

置。 ep -

用来获得ZDO匹配描述符响应行为的终端 返回值:TRUE-允许响应,FALSE-不允许或者终端未找到 uint8 afSetMatch( uint8 ep, uint8 action ); 默认情况下,设备将响应ZDO匹配描述符。可以用这个函数来改变这个行为,比如ep为1,action

为FALSE,ZDO将不响应终端1的ZDO匹配面述符请求。 ep -

用来获得ZDO匹配描述符响应行为的终端 action -TRUE-允许响应,FALSE-不允许或者终端未找到 返回值:TRUE-成功,FALSE-失败或者终端未找到 byte afNumEndPoints( void ); 查找已注册的终端数目,返回此设备上已注册的终端数目,包括终端0 void afEndPoints( byte *epBuf, byte skipZDO ); 返回包含已注册的终端的一个数组。 epBuf – 指向存放终端的数组,每个终端占一个字节 skipZDO -设置为TRUE,则不包含ZDO终端(终端0) 发送数据

发送数据发送数据

发送数据 afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP, uint16 cID, uint16 len, uint8 *buf, uint8 *transID, uint8 options, uint8 radius ); dstAddr -目标地址指针。 afAddrNotPresent 由反射器(邦定源,也即路由器或者协调器)指定 afAddrGroup 发送到组 afAddrBroadcast 发送广播消息 afAddr16Bit 直接发送到节点(单播) srcEP -发送终端的终端描述符指针 cID -簇ID,cluster ID如同消息ID,并且在剖面(profile)中各不相同 len -要发送的字节数 buf -指向要发送的数据缓存的指针 transID -事务序列号指针。如果消息缓存发送,这个函数将增加这个数字 options -发送选项,可以由下面一项,或几项相或得到 AF_ACK_REQUEST 0x10 要求APS应答,这是应用层的应答,支在直接发送(单播)时使用。 AF_DISCV_ROUTE 0x20 总要包含这个选项 AF_SKIP_ROUTING 0x80 设置这个选项将导致设备跳过路由而直接发送消息。终点设备将不向

其父亲发送消息。在直接发送(单播)和广播消息时很好用。 radius –

最大的跳数,用默认值AF_DEFAULT_RADIUS afStatus_t – 成功则为ZSuccess(defined in ZComDef.h). 否则 Errors(defined in ZComDef.h) uint8 afDataReqMTU( afDataReqMTU_t* fields ); 找出基于输入参数的最大可发送字节数,返回能发送的最大字节数 fields -要发送的消息类型参数 typedef struct { uint8 kvp; APSDE_DataReqMTU_t aps; } afDataReqMTU_t; kvp – 设为false. typedef struct { uint8 secure; } APSDE_DataReqMTU_t; aps.secure – 设为false.如果在一个安全网络中此位将自动设置. ZigBee学习之

学习之学习之

学习之16——ZStack API解读

解读解读

解读4 应用支持子层

应用支持子层应用支持子层

应用支持子层(

((

(APS)

))

) 应用支持子层提供如下管理功能: ? 邦定表管理 ? 组表管理 ? 快速地址查找 除了管理功能外,APS还提供数据服务,只是应用不能访问数据服务。应用需要通过AF数据接

口AF_DataRequest()来发送数据。如果要使用邦定表函数需要包含BindingTable.h头文件。 邦定表管理

邦定表管理邦定表管理

邦定表管理 请注意,绑定服务只能在“互补”设备之间建立。那就是,只有分别在两个节点的简单描述结构体

(simple descriptor structure)中,同时注册了相同的命令标识符(command_id)并且方向相反(一

个属于输出指令“output”,另一个属于输入指令“input”),才能成功建立绑定。 APS

邦定表是在静态RAM中定义的一张表,定义在nwk_globals.c中。表的大小可以通过

f8wConfig.cfg中的[NWK_MAX_BINDING_ENTRIES和MAX_BINDING_CLUSTER_IDS]莱配

置。只有定义了REFLECTOR或者COORDINATOR_BINDING才能包含此表,用REFLECTOR

编译选项来支持APS层的源邦定。 邦定表结构

邦定表结构邦定表结构

邦定表结构 – BindingEntry_t typedef struct { uint16 srcIdx; // Address Manager index uint8 srcEP; uint8 dstGroupMode; // Destination address type; 0 - Normal address index, 1 - // Group address uint16 dstIdx; // This field is used in both modes (group and non-group) to // save NV and RAM space // dstGroupMode = 0 - Address Manager index // dstGroupMode = 1 - Group Address uint8 dstEP; uint8 numClusterIds; uint16 clusterIdList[MAX_BINDING_CLUSTER_IDS]; // Don't use MAX_BINDING_CLUSTERS_ID when // using the clusterIdList field. Use // gMAX_BINDING_CLUSTER_IDS } BindingEntry_t; srcIdx –源地址(绑定记录的源地址)的地址管理器索引,地址管理器保存着源地址的IEEE地址

和短地址。 srcEP -

源终端 dstGroupMode -目的地址类型。 0 普通地址 1 组地址 dstIdx -若dstGroupMode为0,则包含目的地址的地址管理器索引,若dstGroupMode为1,则包

含目的组地址 dstEP -

目的终端 numClusterIds -clusterIdList中的入口数目 clusterIdList -簇ID列表。列表的最大数目定义由MAX_BINDING_CLUSTER_IDS [f8wConfig.cfg]

指定 邦定表维护

邦定表维护邦定表维护

邦定表维护 BindingEntry_t *bindAddEntry( zAddrType_t *srcAddr, byte srcEpInt, zAddrType_t *dstAddr, byte dstEpInt, byte numClusterIds, uint16 *clusterIds ); 在邦定表中增加一个入口。由于每个入口可以有多个cluster ID,所以此函数有可能仅仅在已有邦

定条目上增加cluster ID srcAddr -

邦定记录源地址。为Addr16Bit或者Addr64Bit的addr数据结构 srcEpInt -邦定记录源终端 dstAddr -邦定记录目的地址,为Addr16Bit,Addr64Bit或AddrGroup addrMode,若为AddrGroup

则组ID(group ID)填充到addr.shortAddr dstEpInt -

邦定记录目标终端,若dstAddr为组地址,则忽略此位 clusterIds -指向要增加的cluster ID(16位)列表。 返回值:BindingEntry_t -指向一条新加入的邦定入口 byte bindRemoveEntry( BindingEntry_t *pBind ); pBind -指向邦定表中一个入口的指针 byte bindRemoveClusterIdFromList( BindingEntry_t *entry, uint16 clusterId ); 从已存在的邦定表入口的cluster ID列表中移除一个cluster ID。如果至少移除了一个cluster ID

则返回真。此函数不检查参数的正确性。 entry -

指向邦定表的指针 clusterId -要移除的16位的cluster ID byte bindAddClusterIdToList( BindingEntry_t *entry, uint16 clusterId ); 是上面那个函数的反操作函数 void bindRemoveDev( zAddrType_t *Addr ); 移除参数指定的所有邦定表入口,一旦Addr匹配了源地址或者目的地址,则对应的入口将被删

除 void bindRemoveSrcDev( zAddrType_t *srcAddr, uint8 ep ); 功能同上,只是限定了终端和源地址的匹配项目 void bindUpdateAddr( uint16 oldAddr, uint16 newAddr ); 交换邦定表中的短地址,所有oldAddr将被newAddr取代。 BindingEntry_t *bindFindExisting( zAddrType_t *srcAddr, byte srcEpInt, zAddrType_t *dstAddr, byte dstEpInt ); 按指定参数查找一个已存在的邦定表入口。若找到则返回指向此邦定表入口的指针,否则返回

NULL。 byte bindIsClusterIDinList( BindingEntry_t *entry, uint16 clusterId ); 检查此

cluster ID是否在簇ID列表中。如果在列表中则返回真。 邦定表统计函数

邦定表统计函数邦定表统计函数

邦定表统计函数 byte bindNumBoundTo( zAddrType_t *devAddr, byte devEpInt, byte srcMode ); 计算符合条件的邦定表入口数目。返回找到的邦定表入口数目。 devAddr -要查找的邦定表地址 devEpInt -终端 srcMode TRUE 查找源地址,FALSE 查找目的地址 uint16 bindNumOfEntries( void ); 返回邦定表中的条目数,以cluster计数。 void bindCapacity( uint16 *maxEntries, uint16 *usedEntries ); 返回邦定表的可能最大数目,和在用的最大数目,以入口记录计数。 maxEntries -指向最大入口数目的变量,绑定表的最大入口可以通过

NWK_MAX_BINDING_ENTRIES[f8wConfig.cfg]更改 usedEntries -

指向在用的入口数目的变量 邦定表的非易失性储存

邦定表的非易失性储存邦定表的非易失性储存

邦定表的非易失性储存 用这些API需要首先设置编译选项NV_RESTORE[f8wConfig.cfg],推荐用户使用BindWriteNV

函数,因为binding NV初始化和读取在设备启动的时候会自动执行。 void BindWriteNV( void ); 把绑定表写入非易失性储存器,如果用户应用改变了邦定表则可以调用此函数。如果邦定表通过ZDO更新,则ZDO会调用此函数,用户应用就不需调用了。 组表管理

组表管理组表管理

组表管理 APS组表是由分配的RAM[osal_mem_alloc()]定义得链表,因此当组表增加时,OSAL堆也将增

加。表定义在nwk_globals.c,通过调整APS_MAX_GROUPS[f8wConfig.cfg]来改变组表的最大大

小。用这些API需要包含aps_groups.h头文件。 组表结构体

组表结构体组表结构体

组表结构体 组:组是用来将一系列节点集合到一个单地址实体的方式。一个数据请求能够到达组中的每个节

点。在Zigbee协议中组是可选配置,但是在某些剖面中是必选的,比如家庭自动化剖面。组关

注的是一系列设备同时执行一个行为。 typedef struct { uint16 ID; // Unique to this table uint8 name[APS_GROUP_NAME_LEN]; // Human readable name of group } aps_Group_t; ID -16

位的组ID name -文本组名(人类语言)APS_GROUP_NAME_LEN为16且不可更改。 组表入口

组表入口组表入口

组表入口-apsGroupItem_t 组表记录(入口) typedef struct apsGroupItem { struct apsGroupItem *next; uint8 endpoint; aps_Group_t group; } apsGroupItem_t; next -指向组表的下一个入口(组表为链表结构),推荐使用组表查找和维护函数来遍历组表。 endpoint -接受消息的终端 group -组ID和组名 组表维护函数

组表维护函数组表维护函数

组表维护函数 ZStatus_t aps_AddGroup( uint8 endpoint, aps_Group_t *group ); 往组表中添加一个组。先定义aps_Group_t,然后填充这个结构,最后调用此函数来添加。若

NV_RESTORE使能,则函数将更新保存到非易失性储存。 返回值:成功则返回

ZSuccess,否则返回错误,错误可以是:

ZApsDuplicateEntry,ZApsTableFull,ZMemError[ZComDef.h] uint8 aps_RemoveGroup( uint8 endpoint, uint16 groupID ); 移除一个组,若

NV_RESTORE使能,则函数将更新保存到非易失性储存。 void aps_RemoveAllGroup( uint8 endpoint ); 按给定的参数,移除终端的所有组。 组表查询函数

组表查询函数组表查询函数

组表查询函数 aps_Group_t *aps_FindGroup( uint8 endpoint, uint16 groupID ); endpoint -将接收消息的终端 返回值:指向组元件的指针 uint8 aps_FindGroupForEndpoint( uint16 groupID, uint8 lastEP ); 从组ID中查找终端,这个函数用来跳过终端,然后返回下一个终端。 lastEP -返回的终端前要跳过的终端。用APS_GROUPS_FIND_FIRST来指定要查找的第一个终

端。 返回终端,或者

APS_GROUPS_EP_NOT_FOUND(没有找到或者找到多个) uint8 aps_FindAllGroupsForEndpoint( uint8 endpoint, uint16 *groupList ); 得到一个属于一个组的所有终端。 endpoint -要查找的终端 groupList -指向存放终端所有表的空间 uint8 aps_CountGroups( uint8 endpoint ); uint8 aps_CountAllGroups( void ); 组表的非易失性储存

组表的非易失性储存组表的非易失性储存

组表的非易失性储存 若定义了编译选项NV_RESTORE,则当组发生改变时会自动储存。组表的NV初始化和恢复在

器件启动时自动执行。若用户应用改变了组表的入口,则必须直接调用Aps_GroupsWriteNV() void aps_GroupsWriteNV( void ) 若是通过正常的组添加,移除函数的调用来更改组表,则不必调用此函数。 快速地址查找

快速地址查找快速地址查找

快速地址查找 APS提供了一对函数用来做快速地址转换(查找),用这些函数在IEEE和短地址之间作转换。 uint8 APSME_LookupExtAddr(uint16 nwkAddr, uint8* extAddr ); 基于短地址查找IEEE扩展地址。 nwkAddr -拥有的短地址,用来查找扩展地址。 extAddr -指向扩展地址存放的缓存 uint8 APSME_LookupNwkAddr( uint8* extAddr, uint16* nwkAddr ); ZigBee学习之

学习之学习之

学习之17——ZStack API解读

解读解读

解读5 网络层

网络层网络层

网络层(

((

(NWK)

))

) 网络层为高层提供下面函数功能: ? 网络管理 ? 地址管理 ? 网络变量和效能函数 除了管理功能外,NWK还提供数据服务,只是应用不能访问数据服务。应用需要通过AF数据

接口AF_DataRequest()来发送数据。 网络管理

网络管理网络管理

网络管理 ZStatus_t NLME_NetworkDiscoveryRequest( uint32 ScanChannels,byte ScanDuration ); 用来请求网络层发现邻居路由器。在加入操作执行网络扫描前应该调用此函数。扫描确认(结果)

将以回调函数ZDO_NetworkDiscoveryConfirmCB()返回。推荐用户使用ZDO_StartDevice()来代替

此函数。(除非你清楚的知道网络加入过程) ScanChannels -

执行发现的通道,2.4GHz条件下只能使用通道11-26(0x07FFF800) ScanDuration -新网络启动前,每个通道被其他网络扫描的时间 BEACON_ORDER_15_MSEC 0 15.36 milliseconds BEACON_ORDER_30_MSEC 1 30.72 milliseconds BEACON_ORDER_60_MSEC 2 61.44 milliseconds BEACON_ORDER_120_MSEC 3 122.88 milliseconds BEACON_ORDER_240_MSEC 4 245.76 milliseconds BEACON_ORDER_480_MSEC 5 491.52 milliseconds BEACON_ORDER_1_SECOND 6 983.04 milliseconds BEACON_ORDER_2_SECONDS 7 1966.08 milliseconds BEACON_ORDER_4_SECONDS 8 3932.16 milliseconds BEACON_ORDER_7_5_SECONDS 9 7864.32 milliseconds BEACON_ORDER_15_SECONDS 10 15728.64 milliseconds BEACON_ORDER_31_SECONDS 11 31457.28 milliseconds BEACON_ORDER_1_MINUTE 12 62914.58 milliseconds BEACON_ORDER_2_MINUTES 13 125829.12 milliseconds BEACON_ORDER_4_MINUTES 14 251658.24 milliseconds BEACON_ORDER_NO_BEACONS 15 No Beacons transmitted 返回值:ZStatus_t -ZComDef.h中定义的状态值 ZStatus_t NLME_NwkDiscReq2( NLME_ScanFields_t* fields ); 用来请求网络层发现邻居路由器。用此函数来执行一个网络扫描,但是不加入网络。扫描确认(结

果)将以回调函数ZDO_NetworkDiscoveryConfirmCB()返回。回调函数执行以后通过调用

NLME_NwkDiscTerm()清除此活动。 fields –

扫描结构体 typedef struct {  uint32 channels; uint8 duration; } NLME_ScanFields_t; void NLME_NwkDiscTerm( void ); ZStatus_t NLME_NetworkFormationRequest( uint16 PanId, uint32 ScanChannels, byte ScanDuration, byte BeaconOrder, byte SuperframeOrder, byte BatteryLifeExtension ); 函数允许上层请求设备构成网络并成为网络中的协调器。执行结果返回回调函数

ZDO_NetworkFormationConfirmCB()。推荐用户使用ZDO_StartDevice()来代替此函数。 PanId -

此设备建立网络所使用的ID,值范围:0~0x3FFF。如果0xFFFF被使用,则网络层将为

网络选择此PanID。如果发现网络的PAN ID相同,则增加PAN ID直到其唯一。 BeaconOrder – Zigbee 2006

中此参数为:BEACON_ORDER_NO_BEACONS. SuperframeOrder –Zigbee 2006中此参数为:BEACON_ORDER_NO_BEACONS. BatteryLifeExtension -为TRUE则请求协调器支持电池寿命扩展模式 ZStatus_t NLME_StartRouterRequest( byte BeaconOrder, byte SuperframeOrder, byte BatteryLifeExtension ); 函数允许上层请求设备以路由器功能启动。执行结果返回回调函数

ZDO_StartRouterConfirmCB()。推荐用户使用ZDO_StartDevice()来代替此函数。 BeaconOrder – Zigbee 2006

中此参数为:BEACON_ORDER_NO_BEACONS. SuperframeOrder –Zigbee 2006中此参数为:BEACON_ORDER_NO_BEACONS. BatteryLifeExtension -为TRUE则请求协调器支持电池寿命扩展模式 ZStatus_t NLME_JoinRequest( uint8 *ExtendedPANID, uint16 PanId, byte Channel, byte CapabilityInfo ); 函数允许上层发送设备加入网络的请求。执行结果返回回调函数 ZDO_JoinConfirmCB()。推荐

用户使用ZDO_StartDevice()来代替此函数。 ExtendedPANID -

想要加入的网络的扩展PAN ID CapabilityInfo -加入设备的操作性能 CAPINFO_ALTPANCOORD 0x01 CAPINFO_DEVICETYPE_FFD 0x02 CAPINFO_POWER_AC 0x04 CAPINFO_RCVR_ON_IDLE 0x08 CAPINFO_SECURITY_CAPABLE 0x40 CAPINFO_ALLOC_ADDR 0x80 ZStatus_t NLME_ReJoinRequest( void ); 已加入设备的重新加入。执行结果返回到回调函数ZDO_JoinConfirmCB() ZStatus_t NLME_OrphanJoinRequest( uint32 ScanChannels, byte ScanDuration ); 请求网络层孤儿节点的加入。此函数是一个暗含加入的扫描。执行结果返回到回调函数

ZDO_JoinConfirmCB()。推荐用户使用ZDO_StartDevice()来代替此函数。 以上的几个函数基本上是被

ZDO_StartDevice()的执行所取代了,呵呵! ZStatus_t NLME_PermitJoiningRequest( byte PermitDuration ); 定义了协调器或路由器在一个固定的周期内如何允许设备加入。 PermitDuration -在这个指定的时间(单位为秒)内允许设备关联。0x00和0xff表示分别禁止或

允许设备关联。 ZStatus_t NLME_DirectJoinRequest( byte *DevExtAddress, byte capInfo ); 请求协调器或路由器设备的网络层加入一个设备作为其子设备。 DevExtAddress -指向设备的IEEE地址 ZStatus_t NLME_LeaveReq( NLME_LeaveReq_t* req ); 请求自己或者其他设备脱离网络,执行此函数不会导致其父节点重新分配设备地址。 req – 脱离请求数据结构: typedef struct { uint8* extAddr; uint8 removeChildren; uint8 rejoin; uint8 silent; } NLME_LeaveReq_t; extAddr -要脱离的设备的扩展地址 removeChildren -true 子设备也脱离,false只脱离当前设备。目前只能用false rejoin -true 允许设备重新加入网络,false 不允许设备重新加入网络 silent – true will. false if the。 void NLME_RemoveChild( uint8* extAddr, uint8 dealloc ); dealloc – true will. false if the. ZMacStatus_t NwkPollReq( byte securityEnable ); 手动发送一个MAC数据请求(此函数只适用于终设备(end devices))。正常情况下终设备的

数据检测(poll)由网络层自动处理,应用可以通过调用NLME_SetPollRate()来改变速率,如果

速率设为0则可以调用此函数来手动推送数据。 securityEnable –

设为false. 返回值:ZMacStatus_t is the same as ZStatus_t –status values defined in ZComDef.h void NLME_SetPollRate( uint16 newRate ); (此函数只适用于终设备(end devices)) newRate -单位为ms,数据推送到父设备的时间。 void NLME_SetQueuedPollRate( uint16 newRate ); 设置/改变队列推送(poll)速率。(此函数只适用于终设备(end devices)),如果一个数据推

送产生了数据消息,则数据推送率马上设置到 Queued Poll Rate以排空父设备的队列数据。 newRate -

有效值为:0x01-0xff,0为禁止队列推送。 void NLME_SetResponseRate( uint16 newRate ); 设置/改变应答推送(poll)速率。(此函数只适用于终设备(end devices)),我们可以让应答

快一些。 newRate -

有效值为:0x01-0xff,0为禁止队列推送。 地

地地

地址管理

址管理址管理

址管理 地址管理模块提供底层的地址管理,用户不能直接访问这个模块。APSME_LookupExtAddr和

APSME_LookupNwkAddr提供本地地址查找,ZDP_IEEEAddrReq和ZDP_NwkAddrReq提供远

程地址查找。 byte *NLME_GetExtAddr( void ); 返回指向设备

64位IEEE地址的指针。 uint16 NLME_GetShortAddr( void ); 返回设备的16位网络地址(短地址)。 uint16 NLME_GetCoordShortAddr( void ); 返回设备父亲的网络地址(16位短地址),注意这个地址不是Zigbee协调器的短地址(总为

0x0000) void NLME_GetCoordExtAddr( byte *buf ); 返回设备父亲的

IEEE地址(64位),注意这个地址不是Zigbee协调器的扩展地址 ZStatus_t NLME_SetRequest( ZNwkAttributes_t NIBAttribute, uint16 Index, void *Value ); 设置NIB属性的值。 NIBAttribute -只支持nwkProtocolVersion属性 ZStatus_t NLME_GetRequest( ZNwkAttributes_t NIBAttribute, uint16 Index, void *Value ); NIBAttribute -只支持如下属性: nwkCapabilityInfo nwkNumNeighborTableEntries nwkNeighborTable nwkNumRoutingTableEntries nwkRoutingTable addr_filter_t NLME_IsAddressBroadcast(uint16 shortAddress); 基于设备性能,这个函数评估参数中提供的地址,决定是否是广播地址 shortAddress -要测试的地址 addr_filter_t - ADDR_NOT_BCAST 不是一个广播地址 ADDR_BCAST_FOR_ME 是一个广播地址,且这个设备是正确的设备类型 ADDR_BCAST_NOT_ME 是一个广播地址,这个设备是不是正确的目标 byte NLME_GetProtocolVersion(); 获得NIB中的协议版本 返回值: ZB_PROT_V1_0 1 ZB_PROT_V1_1 2 void NLME_SetBroadcastFilter(byte capabilities); 基于设备的性能设置位掩码,用来处理有效的广播地址 capabilities -用来决定设备能处理哪些广播消息 网络的非易失性储存

网络的非易失性储存网络的非易失性储存

网络的非易失性储存 若设置了NV_RESTORE,当设备加入时网络信息(NIB)会自动储存。当设备启动时会自动执

行NIB NV初始化和恢复。如果用户应用改变NIB(_NIB)则必须调用NLME_UpdateNV() void NLME_UpdateNV( byte enables ); enables -

位掩码 NWK_NV_NIB_ENABLE 0x01 粗存网络层NIB NWK_NV_DEVICELIST_ENABLE 0x02 储存设备列表 NWK_NV_BINDING_ENABLE 0x04 储存绑定表 NWK_NV_ADDRMGR_ENABLE 0x08 储存地址管理表  ZigBee学习之

学习之学习之

学习之18——ZCL解读

解读解读

解读 Zigbee Cluster Library(ZCL)(

((

(Zigbee簇群库

簇群库簇群库

簇群库)

))

)[Z-Stack ZCL API_F8W-2006-0020_.pdf] ZCL是Zigbee 1.1(Zigbee2006)协议版本中增加的一个重要的部分。在Zigbee中,一个簇群 就是一个容器,在容器中以命令结构体包含了一个或多个属于某个应用剖面的属性/消息,不管

应用剖面如何,相同的设备(比如开关)拥有相同的定义和功能。属性是设备的变量或特性,能

够设置或获得。比如设置自动调温器的加热点。ZCL提供了一种机制,利用这种机制设备能够

将变化异步地报告给属性(attribute),比如当空气变热时自动控温器服务器就将室温改变报告

给他的客户端,这个过程不需要客户端发起请求。 ZCL

采用客户端/服务器模块的模式,一般储存簇属性的作为服务器,影响或操作属性的作为客

户端。然而如果需要,属性也可以呈现在客户端上。例如,设备通过读写属性的命令来操作属性,

这些命令从客户端设备发送到服务器设备;对这些命令的应答从服务器设备发送到客户端设备;

但是报告属性命令是从服务器发送到客户端。 cluster ID

是每个簇的标志,由剖面分配,在内部使用的是逻辑簇ID,所以还有一个Cluster ID

转换表。 typedef struct { uint16 attrId; // Attribute ID uint8 dataType; // Data Type - defined in AF.h uint8 accessControl; // Read/write - bit field void *dataPtr; // Pointer to data field } zclAttribute_t; typedef struct { uint16 clusterID; // Real cluster ID zclAttribute_t attr; } zclAttrRec_t; typedef struct { zclGCB_BasicReset_t pfnBasicReset; // Basic Reset zclGCB_Identify_t pfnIdentify; // Identify Response zclGCB_IdentifyQueryRsp_t pfnIdentifyQueryRsp; // Identify Query Rsp zclGCB_OnOff_t pfnOnOff; // On/Off cluster zclGCB_LevelControlMoveToLevel_t pfnLevelControlMoveToLevel; // MoveToLevel zclGCB_LevelControlMove_t pfnLevelControlMove; // Move zclGCB_LevelControlStep_t pfnLevelControlStep; // Step zclGCB_LevelControlStop_t pfnLevelControlStop; // Stop zclGCB_GroupRsp_t pfnGroupRsp; // Group Response zclGCB_SceneStoreReq_t pfnSceneStoreReq; // Scene Store Request zclGCB_SceneRecallReq_t pfnSceneRecallReq; // Scene Recall Request zclGCB_SceneRsp_t pfnSceneRsp; // Scene Response zclGCB_Alarm_t pfnAlarm; // Alarm Req & Rsp zclGCB_Location_t pfnLocation; // RSSI Location zclGCB_LocationRsp_t pfnLocationRsp; // RSSI Location Rsp } zclGeneral_AppCallbacks_t; 创建一个

ZCL应用至少需要创建4个模块: ? zcl_<appname>.h 应用的定义,应用的终端也定义在此 ? zcl_<appname>_data.c 数据定义和声明,包含以下内容: 应用支持的所有簇属性; 属性表中每个属性包含一个zclAttrRec_t类型的入口; 分别包含应用指定的输入和输出cluster ID的输入cluster ID表和输出cluster ID表,这些表将和

简单描述表一起使用; SimpleDescriptionFormat_t[AF.h]

类型的简单描述表。 ? zcl_<appname>.c endPointDesc_t[AF.h]类型的应用终端表声明; 创建用以处理来自ZCL簇的命令的命令回调函数,这些函数和命令回调表一起使用; ZCL功能域(functional domains)的应用命令回调表声明。通用功能域的表类型为:

zclGeneral_AppCallbacks_t[zcl_general.h]; 为应用任务创建应用初始化函数

void zcl<AppName>_Init( byte task_id );此函数应该注册: 1、相应功能域的命令回调表,zclGeneral_RegisterCmdCallbacks()[zcl_general.c]用来注册通用簇

命令回调; 2

、用zcl_registerAttrList()[zcl.c]注册应用属性列表; 3、用afRegister()[AF.h]注册应用终端; 4、用RegisterForKeys()[OnBoard.c]注册所有处理按键事件的应用任务; 5、用zclHA_Init()[zcl_ha.c]注册HA剖面层的应用简单描述。 创建用于接收和处理消息以及应用任务队列中关键事件的函数uint16

zcl<AppName>_event_loop( uint8 task_id, uint16 events )。 ? OSAL_<AppName>.c 此模块应该包含

void osalAddTasks( void )函数,此函数包含添加到任务列表中的应用所需的任务

和应用任务本身。使用osalTaskAdd()[OSAL_Tasks.c]来完成任务的添加。在此展示一个最小的任

务列表,他们的添加顺序是一个简单ZCL应用所需的: 1. HAL 2. MAC 3. Network 4. APS 5. ZD Application 6. ZCL 7. ZCL Application 功能层

功能层功能层

功能层 功能层提供了操作属性和其他一般任务的一般命令: ? Read attributes ? Read attributes response ? Write attributes ? Write attributes undivided ? Write attributes response ? Write attributes no response ? Configure reporting ? Configure reporting response ? Read reporting configuration ? Read reporting configuration response ? Report attributes ? Default response ? Discover attributes ? Discover attributes response 通用功能域包含以下簇

通用功能域包含以下簇通用功能域包含以下簇

通用功能域包含以下簇:

::

: ? Basic ? Power Configuration ? Device Temperature Configuration ? Identity ? Groups ? Scenes ? On/Off ? On/Off Switch Configuration ? Level Control ? Alarms ? Time ? RSSI Indication 在文档的最后给出了ZCL的编译选项,也就是控制使用哪些ZCL功能的预编译项。

 ZigBee

学习之

学习之学习之

学习之19——如何创建自己的简

如何创建自己的简如何创建自己的简

如何创建自己的简单

单单

应用

应用应用

应用 创建一个简单的应用剖面

创建一个简单的应用剖面创建一个简单的应用剖面

创建一个简单的应用剖面(

((

(profile)

))

)[Simple API for Z-Stack _F8W-2007-0021_.pdf] ? 定义应用中的所有设备 如温度传感器,空间传感器,调温器,加热单元,远程控制, 为他们分配一个独立的设备ID(16位device_id) ? 定义设备间交换的“命令”,并为每个命令分配一个独立的16位ID(command_id) 如: 读取温度 读取空间占用情况 设置调温器 加热/制冷单元控制 ? 为每个“命令”定义设备的生产(输出)和消费(输入) 如: 读取温度是从温度传感器生产(输出),由调温器消费(输入到调温器) 读取空间占用情况命令从空间传感器设备生产(输出),由调温器消费(输入到调温器) ? 为每个设备创建简单描述结构,此结构应该包括以下信息: 为每个设备分配设备标志(ID)和设备版本, 为设备指定命令的输入和输出表, 指定一个独一无二的16位剖面ID(profile ID)。(由Zigbee联盟分配) ? 对于每个命令 定义交换的消息格式及其解释, 如: (格式)一个8位的值 (解释)0表示0摄氏度,255表示64摄氏度,精度0.25摄氏度 ? 为每个设备编写设备应用 拥有输出命令的设备应该能够产生包(周期性或者由外部事件触发) 拥有输入命令的设备应该能够接收包并解析有效载荷 ? 定义绑定策略,以便设备能正确地交换数据包。 此文档的第4节是关于Simple例子的解释,好好看看确实大有裨益,结合源码就最好了赫赫。 ZigBee学习之

学习之学习之

学习之20——SimpleAPP分析

分析分析

分析 下面来看看这个最简单例子的分析

我将做最详细的分析,相信是程序注释量最大的了额

SimpleApp分析

分析分析

分析 程序总是从

helloword开始的,如果能找到一个例子开始,那么是最好不多的了,还好Ti为我们

提供了很多的机会,让我们开始看一下simpleapp吧【Texas

Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SimpleApp】 SimpleApp

里面有两个应用,一个是收集传感器的值,其中有一个传感器设备和一个收集设备;

另一个应用时智能开关,有一个控制节点和一个灯节点。这里主要展示了如何网络建立,绑定和

解绑定的演示以及开发一个自己的应用。 双击打开

\Texas

Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SimpleApp\CC2430DB\SimpleApp.eww工

程,既然是51的内核那只能用C了,一个地球人都知道的原理就是C程序是从main()开始

的,而且整个程序就是一个main()函数,那么好办了,我们找到main()然后分析它就可以看清楚

整个程序的结构了。 在工作空间中有

8种项目配置,因为TI提供了两种开发板,所以实际上4种项目配置,分别配

置成应用中4种设备。 我们直接看智能灯的应用,跟这个应用相关的配置是(以

DB开发板为原型):SimpleSwitchDB

和SimpleControllerDB。SimpleSwitchDB是终端设备(我的理解是这里是灯的开关),

SimpleControllerDB是控制设备是协调器或者路由器。这里之所以把和灯相连的设备作为协调器或者是路

由器我认为是为了和家庭或者是场景中的其他设备进行交互所需要的。因为灯的状态还必须被其他的设备知道,就必须

通知其他设备当前的状态! 演示效果:控制设备

-SW1:以协调器启动,SW2:以路由器启动 启动控制设备将进入允许绑定状态,此时在10S内按动终端设备上的S1将发送绑定请求,成功

绑定后LED1将点亮(如果打开节能选项,将是闪烁状态);按动终端设备上的S2将发送切换

命令,将导致控制设备上的LED1开关状态的切换,按S3将此设备接绑定。效果清楚了接下来

就是看看程序怎样了。 找到

ZMain.c,看看函数的描述Description: Startup and shutdown code for ZStack(启动和关闭

Z-Stack)看来来对地方了哈。 第一个宏:

MINIMIZE_ROOT(

((

(line 126)

))

) 如果定义这个宏那么ZMAIN中的API函数将被在ROOT段外实现以为用户提供更多的空间来

定义自己的常量,默认这个编译选项是不定义的。所谓的ZMAIN API函数定义在line138-line144。

在line152终于看到了期待的main()

ZSEG int main( void )

{

// Turn off interrupts

osal_int_disable( INTS_ALL ); // Initialize HAL

//处理器运行时钟,LED IO口设置

HAL_BOARD_INIT();

// Make sure supply voltage is high enough to run

zmain_vdd_check();

// Initialize stack memory

zmain_ram_init();

// Initialize board I/O

InitBoard( OB_COLD );

// Initialze HAL drivers

HalDriverInit();

// Initialize NV System

osal_nv_init( NULL );

// Determine the extended address

zmain_ext_addr();

// Initialize basic NV items

zgInit();

// Initialize the MAC

ZMacInit();

#ifndef NONWK

// Since the AF isn't a task, call it's initialization routine

afInit();

#endif

// Initialize the operating system

osal_init_system();

// Allow interrupts

osal_int_enable( INTS_ALL );

// Final board initialization

InitBoard( OB_READY );

// Display information about this device

zmain_dev_info(); /* Display the device info on the LCD */

#ifdef LCD_SUPPORTED

zmain_lcd_init();

#endif

osal_start_system(); // No Return from here

} // main() 

 

 

ZigBee学习之

学习之学习之

学习之21-osal_int_disable(INTS_ALL); osal_int_disable( INTS_ALL );

//关中断 //【OSAL.C】参数为INTS_ALL则关闭所有中断,否则关闭相应中断 byte osal_int_disable( byte interrupt_id ) { if ( interrupt_id == INTS_ALL ) { HAL_DISABLE_INTERRUPTS(); //关闭所有中断响应【hal_mcu.h】 //#define HAL_DISABLE_INTERRUPTS() st( EA = 0; ); //st的定义在【hal_defs.h】,其实就是为了保证宏能够准确的被执行用的 //#define st(x) do { x } while (__LINE__ == -1); } else return ( INVALID_INTERRUPT_ID ); //返回禁止相应的中断【ZComDef.h】 //#define INVALID_INTERRUPT_ID 9 return ( ZSUCCESS ); //执行成功就返回成功标志,其实按程序流程来看这个函数总是会返回成功标志的 //#define ZSUCCESS 0 } ZigBee学习之

学习之学习之

学习之22-HAL_BOARD_INIT(); HAL_BOARD_INIT();//初始化板子硬件部分 //板子初始化函数其实是一个宏,定义在【hal_board_cfg.h】“\”符号其实是编译环境下面的行中断符,表示这一样没有结

束,其实是一个“\”。  #define HAL_BOARD_INIT() { \

uint16 i; \

\

SLEEP &= ~OSC_PD; /* turn on 16MHz RC and 32MHz XOSC */\

//操作SLEEP寄存器,给16MHz高速振荡器和32M外部晶体振荡上电

//【hal_mcu.h】

//#define OSC_PD 0x04

while (!(SLEEP & XOSC_STB)); /* wait for 32MHz XOSC stable */\

asm("NOP"); /* chip bug workaround */\

for (i=0; i<504; i++) asm("NOP"); /* Require 63us delay for all revs */\

// SLEEP寄存器中XOSC_STB位设置后还需要等待额外的64us,手册上是这么说的呵

CLKCON = (0x00 | OSC_32KHZ); /* 32MHz XOSC */\

//设置32M和外部32.768K时钟为源

while (CLKCON != (0x00 | OSC_32KHZ)); \

//等待时钟稳定

SLEEP |= OSC_PD; /* turn off 16MHz RC */\

//关闭没有用到的时钟源

//上面这段用来设置系统时钟,步骤为:所有时钟源上电;等待时钟稳定;延时64us等待确切的稳定;设置

系统时钟和32K时钟的时钟源;等待设置完成,关闭没有用到的时钟源。 \

/* set direction for GPIO outputs */ \

LED1_DDR |= LED1_BV; \

//【hal_board_cfg.h】

// #define LED1_DDR P1DIR

// #define LED1_BV BV(0)

//【hal_defs.h】

// #define BV(n) (1 << (n));//这是将某位置位的宏

LED2_DDR |= LED2_BV; \

//【hal_board_cfg.h】

// #define LED2_DDR P1DIR

//#define LED2_BV BV(1) //

上面是对板上LED的初始化,完成的操作时将P1_0,P1_1设为输出(因为这两个IO

口具有20ma的电流输出能力)!这里是跟硬件紧密相关的,需要根据自己硬件的具体情况

来设。我的开发板上加上液晶指示的LED才两个,真是捉襟见肘啊,液晶背光灯接P2_0,

调试LED接P1_0。设计的都不是很合理呢。看来在我的开发板上要完全的屏蔽掉LED2呢

呵呵。进行相应更改:(这些更改都在文件hal_board_cfg.h中越在line94~100) #define LED1_POLARITY ACTIVE_HIGH #define LED2_BV BV(0) #define LED2_SBIT P2_0 #define LED2_DDR P2DIR

\

/* configure tristates */ \

P2INP |= PUSH2_BV; \

//这是对Joystick的配置,在【hal_board_cfg.h】 //#define PUSH2_BV BV(0);这里将Joystick的中心按键的IO设为三态状态,我的板子上没有这个东东哦,

所以把Joystick也给屏蔽了,注释掉【hal_board_cfg.h】中约line120的东东

\

/* configure software controlled peripheral VDD */ \

//配置软件控制的外设VDD

VDD_SW_DDR |= VDD_SW_BV; \

VDD_SW_SBIT = 0; \

//【hal_board_cfg.h】,这里实际上是将P1_2配置为输出,然后输出一个低电平 //

貌似我的板子上也用不到这个东西,屏蔽掉

//#define VDD_SW_BV BV(2)

//#define VDD_SW_SBIT P1_2

//#define VDD_SW_DDR P1DIR

} //

好了,以上就是硬件底层的一些初始化工作,主要是配置时钟和一些端口的状态。 

 ZigBee

学习之

学习之学习之

学习之23—— zmain_vdd_check() zmain_vdd_check();//确认VDD是否达到运行处理器的要求,如果没有达到就会闪烁LED //【ZMain.c】line210左右

//重复检测VDD的状态,直到成功检测的次数达到规定过的要求为止。估计是等电源稳定吧,

呵呵

static ZSEG void zmain_vdd_check( void )

{

uint8 vdd_passed_count = 0;

bool toggle = 0;

while ( vdd_passed_count < MAX_VDD_SAMPLES )

//#define MAX_VDD_SAMPLES 3; 【ZMain.c】

//循环检测3次

{

if ( HalAdcCheckVdd (ZMAIN_VDD_LIMIT) )

//【ZMain.c】

// #define ZMAIN_VDD_LIMIT HAL_ADC_VDD_LIMIT_4

//【hal_adc.h】

// #define HAL_ADC_VDD_LIMIT_4 0x04

{

vdd_passed_count++; // Keep track # times Vdd passes in a row

MicroWait (10000); // 延时10毫秒

//微秒级延时函数【OnBoard.h】

//#define MicroWait(t) Onboard_wait(t)

//void Onboard_wait( uint16 timeout )

{

while (timeout--)

 {

asm("NOP");

asm("NOP");

asm("NOP");

}

}

}

else

{

vdd_passed_count = 0; // Reset passed counter

MicroWait (50000); // Wait 50ms

MicroWait (50000); // Wait another 50ms to try again

}

// HalAdcCheckVdd ()【hal_adc.c】函数用来检查VDD是否大于或等于最小的要求

bool HalAdcCheckVdd (uint8 limit)

{

uint16 value;

//如果芯片修订版本号小于REV_D(0x03)就直接完成电压检测并返回TRUE。

CHVER是修订版本号寄存器,此寄存器是只读的。

if (CHVER < REV_D)

//检查芯片版本【hal_mcu.h】

//#define REV_A 0x00

//#define REV_D 0x03

{

return TRUE;

}

//清除ADC中断标志

ADCIF = 0;

//设置新的转换状态

//【hal_adc.c】

//使用内部1.25参考电压

// #define HAL_ADC_REF_125V 0x00

// #define HAL_ADC_DEC_064 0x00 /* Decimate by 64 : 8-bit resolution */8位精度

// #define HAL_ADC_CHN_VDD3 0x0f /* VDD/3 */以AVDD_SOC/3为输入,检测电压

ADCCON3 = (HAL_ADC_REF_125V | HAL_ADC_DEC_064 |

HAL_ADC_CHN_VDD3);

//等待转换完成

while ( !ADCIF );

//取得转换值

value = ADCL;

value |= ((uint16) ADCH) << 8;

//检测

return ( value >= HalAdcVddLimit[limit] );

} // HalAdcVddLimit为一个数组

static __code const uint16 HalAdcVddLimit[] =

{

0x369C, /* VDD Limit - 1.6v */

0x3A06, /* VDD Limit - 1.7v */

0x3D70, /* VDD Limit - 1.8v */

0x40D9, /* VDD Limit - 1.9v */

0x4443, /* VDD Limit - 2.0v */

0x47AD, /* VDD Limit - 2.1v */

0x4B17, /* VDD Limit - 2.2v */

0x4E81, /* VDD Limit - 2.3v */

0x51EA, /* VDD Limit - 2.4v */

}; //

关于电池电压的测量与计算在文档:Using the ADC to Measure Supply Voltage.pdf中有详细的示

例和说明,现在摘录其中一段解说一下: // Max ADC input voltage = reference voltage => // (VDD/3) max = 1.25 V => max VDD = 3.75 V // 12 bits resolution means that max ADC value = 0x07FF = 2047 (dec) // (the ADC value is 2’s complement) // Battery voltage, VDD = adc value * (3.75 / 2047) //

其中有两点很重要:1、最大的ADC输入值=参考电压;2、ADC的值以2的补码形式储存,

也就是说12位的精度因为有1位是符号位所以相对于精度为11,即2^11=2048。因为以VDD/3

为输入电压,以内部1.25为参考电压,所以VDD/3最大值=1.25,得出最大的VDD=3.75;以12

位精度计算,电压值划分为2.47等分,所以测出来的电压值为adc value*(3.75/2047) //

注意这里并没有操作ADCCON1来启动ADC转换。让我们来看看原因: //数据手册133页ADCCON3中ECH的说明: // Extra channel select. Selects the channel number of the extra conversion that is carried out after a conversion sequence has ended. This bit field must be written for an extra conversion to be performed. If the ADC is not running, writing to these bits will trigger an immediate single conversion from the selected extra channel. The bits are automatically cleared when the extra conversion has finished. //这3位是用来选择转换序列完成以后额外的一次转换。其中有一句:If the ADC is not running,

writing to these bits will trigger an immediate single conversion from the selected extra channel.(若

ADC没有运行,对这几位的写入操作将会立即开始一个对指定通道的转换),这就是关键了,

即使ADC没有运行,我们只要往这3位中写入我们希望转换的通道,那么ADC会马上开始运

行,当然执行完毕后这几位会被清除。

//切换LED1和LED2

if (vdd_passed_count == 0)

{

if ((toggle = !(toggle)))

HAL_TOGGLE_LED1();

//【hal_board_cfg.h】 //#define HAL_TOGGLE_LED1() st( if (LED1_SBIT) { LED1_SBIT = 0; } else { LED1_SBIT =

1;} )

//这句很明显了,就是切换LED1的状态。这里一直没有改变toggle的值,为什么还要对它做一

个判断呢?搞不明白

else

HAL_TOGGLE_LED2();

}

}

//关闭LED

//【hal_board_cfg.h】

//#define HAL_TURN_OFF_LED1() st( LED1_SBIT = LED1_POLARITY (0); )

//#define HAL_TURN_OFF_LED2() st( LED2_SBIT = LED2_POLARITY (0); )

//#define LED1_POLARITY ACTIVE_HIGH

//#define LED2_POLARITY ACTIVE_LOW

//#define ACTIVE_LOW !

//#define ACTIVE_HIGH !! /* double negation forces result to be '1' */

//这里的宏定义真的是太繁琐了,真不晓得TI这样弄有多大的意义,转了两个弯才看明白原来

LED1是高电平点亮

HAL_TURN_OFF_LED1();

HAL_TURN_OFF_LED2();

} ZigBee

学习之

学习之学习之

学习之24——zmain_ram_init() zmain_ram_init();//初始化堆栈内存空间 //初始化堆栈内存,以“高水位线”为准。这段程序真的是没有看懂,这段程序跟链接器,程序段在链接文件中的存放,

以及IAR C/C++编译器都有莫大的关系,真的没看懂只知道是初始化了调用和返回时要用到的堆栈空间,暂

时不管,用吧!

static ZSEG void zmain_ram_init( void )

{

uint8 *end;

uint8 *ptr;

// Initialize the call (parameter) stack

end = (uint8*)CSTK_BEG; // Lower end

ptr = (uint8*)(*( __idata uint16*)(CSTK_PTR)); // Upper end

while ( --ptr > end )

*ptr = STACK_INIT_VALUE;

// Initialize the return (address) stack

ptr = (uint8*)RSTK_END - 1; // Upper end

while ( --ptr > (uint8*)SP )

*(__idata uint8*)ptr = STACK_INIT_VALUE;

}  ZigBee学习之

学习之学习之

学习之25——InitBoard //初始化板上的IO

InitBoard( OB_COLD );

//参数【OnBoard.h】

#define OB_COLD 0

#define OB_WARM 1

#define OB_READY 2

//【OnBoard.c】

void InitBoard( byte level )

{

if ( level == OB_COLD )

{

//关中断,最初的时候已经见过了

osal_int_disable( INTS_ALL );

// 关闭所有的LED,调用了LED设置函数

HalLedSet( HAL_LED_ALL, HAL_LED_MODE_OFF );

//【hal_led.h】

// #define HAL_LED_ALL (HAL_LED_1 | HAL_LED_2 | HAL_LED_3 | HAL_LED_4),我这里呢只有

两个LED,所以可以屏蔽掉LED3,LED4,或者将后两个LED也映射到前面两个LED(其实就是宏定义

啦)

// #define HAL_LED_MODE_OFF 0x00

//#define HAL_LED_MODE_ON 0x01

//#define HAL_LED_MODE_BLINK 0x02

//#define HAL_LED_MODE_FLASH 0x04

//#define HAL_LED_MODE_TOGGLE 0x08

//LED设置函数【hal_led.c】

uint8 HalLedSet (uint8 leds, uint8 mode)

{

#if (defined (BLINK_LEDS)) && (HAL_LED == TRUE) //

前提条件是定义了闪烁LED和LED硬件驱动服务使能; // HAL_LED定义在【hal_board_cfg.h】 #ifndef HAL_LED #define HAL_LED TRUE //当设为FALSE时就不使用LED #endif #if (!defined BLINK_LEDS) && (HAL_LED == TRUE) #define BLINK_LEDS #endif

uint8 led;

HalLedControl_t *sts; // LED

控制结构体 typedef struct {  uint8 mode; /* 操作模式 */ uint8 todo; /* 剩余的闪烁周期数 */ uint8 onPct; /* 周期中所占的比例 */ uint16 time; /* 开关周期时间(msec) */ uint32 next; /* 下次改变的时间 */ } HalLedControl_t;

switch (mode)

{

case HAL_LED_MODE_BLINK:

HalLedBlink (leds, 1, HAL_LED_DEFAULT_DUTY_CYCLE,

HAL_LED_DEFAULT_FLASH_TIME); //LED

设置的一些默认参数【hal_led.h】 #define HAL_LED_DEFAULT_MAX_LEDS 4 #define HAL_LED_DEFAULT_DUTY_CYCLE 5 #define HAL_LED_DEFAULT_FLASH_COUNT 50 #define HAL_LED_DEFAULT_FLASH_TIME 1000 //LED闪烁函数【hal_led.c】 void HalLedBlink (uint8 leds, uint8 numBlinks, uint8 percent, uint16 period) { #if (defined (BLINK_LEDS)) && (HAL_LED == TRUE) //同样先判断是否启用了LED并且使用LED闪烁功能 uint8 led; HalLedControl_t *sts; if (leds && percent && period) //判断参数的有效性,percent参数指明一个周期中LED将开启的时间 { if (percent < 100) //如果一个周期中开启时间小于100% { led = HAL_LED_1; leds &= HAL_LED_ALL; //屏蔽掉不想要操作的LED sts = HalLedStatusControl.HalLedControlTable; //HalLedStatusControl是事先定义好的一个结构体 typedef struct { HalLedControl_t

HalLedControlTable[HAL_LED_DEFAULT_MAX_LEDS]; uint8 sleepActive; } HalLedStatus_t; #define HAL_LED_DEFAULT_MAX_LEDS 4 #ifdef BLINK_LEDS static HalLedStatus_t HalLedStatusControl; #endif while (leds) //这个循环主要是对传进来的每一个LED进行独立的设置,比如传进来

的是需要设置LED1和LED2,那么此循环会先设置LED1然后再设置

LED2 { if (leds & led) //

检测当前LED是否是需要设置的 { preBlinkState |= (led & HalLedState); //储存目前的额LED状态 //static uint8 preBlinkState; sts->mode = HAL_LED_MODE_OFF; /*关闭先前的模式 */ sts->time = period; /* Time for one on/off cycle */ sts->onPct = percent; /* % of cycle LED is on */ sts->todo = numBlinks; /* Number of blink cycles */ if (!numBlinks) sts->mode |= HAL_LED_MODE_FLASH; //如果规定的次数未达到则继续闪烁 sts->next = osal_GetSystemClock(); /* Start now */ //读取当前系统时钟【OSAL_Timers.c】 uint32 osal_GetSystemClock( void ) { return ( osal_systemClock ); } //static uint32 osal_systemClock; //这样看来第一次运行的时候osal_systemClock=0,系统中应该启动了定

时器,用来记录系统运行时间。 sts->mode |= HAL_LED_MODE_BLINK; /* Enable blinking */ leds ^= led; //

屏蔽掉已设置好的这个LED } led <<= 1; //检测下一个LED sts++; }//真的是高手才能写出的函数啊!自叹,C语言编程能力还差的远啊 osal_set_event (Hal_TaskID, HAL_LED_BLINK_EVENT); //【hal_drivers.h】 //#define HAL_LED_BLINK_EVENT 0x0002 //extern uint8 Hal_TaskID; //此函数为用户设置任务标志【OSAL.c】 //第一个参数是任务ID,第二个参数是要设置的事件 byte osal_set_event( byte task_id, UINT16 event_flag ) { if ( task_id < tasksCnt ) //【sapi.c】 const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, #if defined( MT_TASK ) MT_ProcessEvent, #endif APS_event_loop, ZDApp_event_loop, SAPI_ProcessEvent }; //【OSAL_Tasks.h】 //事件处理函数原型:typedef unsigned short

(*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event ); //

定义事件处理函数的函数指针类型 //tasksArr中任务事件循环中的顺序必须和osalInitTask中任务的初始化

顺序一致 const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] ); //

得到任务数 //只有当任务ID小于总任务数时才继续执行,也就是说只有当此为有效

任务才执行。 { halIntState_t intState; //

【hal_mcu.h】其实就是一个无符号字符类型 //typedef unsigned char halIntState_t; HAL_ENTER_CRITICAL_SECTION(intState); // 维持全局中断状态 //【hal_mcu.h】 #define HAL_ENTER_CRITICAL_SECTION(x) st( x =

EA; HAL_DISABLE_INTERRUPTS(); ) //

保存当前全局中断状态,然后关闭全局中断 #define HAL_EXIT_CRITICAL_SECTION(x) st( EA = x; ) #define HAL_CRITICAL_STATEMENT(x) st( halIntState_t s;

HAL_ENTER_CRITICAL_SECTION(s); x;

HAL_EXIT_CRITICAL_SECTION(s); ) tasksEvents[task_id] |= event_flag; // Stuff the event bit(s) //

【sapi.c】 //uint16 *tasksEvents; HAL_EXIT_CRITICAL_SECTION(intState); // Release interrupts } else return ( INVALID_TASK ); //如果任务ID超出返回则返回任务无效 //返回值类型定义在【ZComDef.h】 //#define INVALID_TASK 1 return ( ZSUCCESS ); } } Else //如果点亮时间百分比超过100%也就相当于常亮了,就执行LED常亮

的设置 { HalLedSet (leds, HAL_LED_MODE_ON); //

【hal_led.c】 uint8 HalLedSet (uint8 leds, uint8 mode) { #if (defined (BLINK_LEDS)) && (HAL_LED == TRUE) //一样的前提是开始了LED硬件驱动服务 uint8 led; HalLedControl_t *sts; switch (mode) //检测要切换的LED模式 { case HAL_LED_MODE_BLINK: HalLedBlink (leds, 1, HAL_LED_DEFAULT_DUTY_CYCLE,

HAL_LED_DEFAULT_FLASH_TIME); break; //

如果是闪烁模式,则将指定的LED闪烁一次,点亮时间默认为5%,

闪烁周期是1S //

【hal_led.h】 #define HAL_LED_DEFAULT_MAX_LEDS 4 #define HAL_LED_DEFAULT_DUTY_CYCLE 5 #define HAL_LED_DEFAULT_FLASH_COUNT 50 #define HAL_LED_DEFAULT_FLASH_TIME 1000 case HAL_LED_MODE_FLASH: /* Default flash, N times, D% duty cycle */ HalLedBlink (leds, HAL_LED_DEFAULT_FLASH_COUNT,

HAL_LED_DEFAULT_DUTY_CYCLE,

HAL_LED_DEFAULT_FLASH_TIME); break; //

如果是连闪模式,则将指定的LED闪烁50次,点亮时间默认为5%,

闪烁周期是1S case HAL_LED_MODE_ON: case HAL_LED_MODE_OFF: case HAL_LED_MODE_TOGGLE: //

如果是开、关、切换模式,则分别一个个的操作指定的LED led = HAL_LED_1; leds &= HAL_LED_ALL; sts = HalLedStatusControl.HalLedControlTable; while (leds) { if (leds & led) { if (mode != HAL_LED_MODE_TOGGLE) { sts->mode = mode; /* ON or OFF */ } else { sts->mode ^= HAL_LED_MODE_ON; /* Toggle */ //开关的切换,利用了和1相异或为取反的特性。到这里为止还只是设

置了LED的模式,并没有真正的对LED进行开关的操作,也就是说LED并

没有演示出效果。 } HalLedOnOff (led, sts->mode); //

这个函数是对LED进行开关操作的真正的函数了,这个函数执行完后

应该能看到LED状态的变化。列举程序中部分段落(因为其他段落都差不多

呵呵)【hal_led.c】 if (leds & HAL_LED_1) //

检测是否是LED1 { if (mode == HAL_LED_MODE_ON) //检测开关模式 { HAL_TURN_ON_LED1(); //点亮LED1 } else { HAL_TURN_OFF_LED1(); } } ………… if (mode) { HalLedState |= leds; } else { HalLedState &= ~leds; } //此用来记住当前操作的LED的状态,如果是点亮的就把HalLedState

中相应的位置位,否则清零 leds ^= led; } led <<= 1; sts++; } break; default: break; } #elif (HAL_LED == TRUE) LedOnOff(leds, mode); #endif /* BLINK_LEDS && HAL_LED */ return ( HalLedState ); //

返回的是操作了的LED的状态,每个LED在此状态中用1位来表示 } } } Else //如果没有开始LED驱动服务,或者没有开启LED闪烁,就直接关闭

LED { HalLedSet (leds, HAL_LED_MODE_OFF); /* No on time, turn off */ } #elif (HAL_LED == TRUE) //

如果没有定义闪烁,则只对LED进行单次的一次转换状态操作 percent = (leds & HalLedState) ? HAL_LED_MODE_OFF :

HAL_LED_MODE_ON; HalLedOnOff (leds, percent); /* Toggle */ #endif /* BLINK_LEDS && HAL_LED */ }

break;

case HAL_LED_MODE_FLASH:

/* Default flash, N times, D% duty cycle */

HalLedBlink (leds, HAL_LED_DEFAULT_FLASH_COUNT, HAL_LED_DEFAULT_DUTY_CYCLE,

HAL_LED_DEFAULT_FLASH_TIME);

break;

case HAL_LED_MODE_ON:

case HAL_LED_MODE_OFF:

case HAL_LED_MODE_TOGGLE:

led = HAL_LED_1;

leds &= HAL_LED_ALL;

sts = HalLedStatusControl.HalLedControlTable; while (leds)

{

if (leds & led)

{

if (mode != HAL_LED_MODE_TOGGLE)

{

sts->mode = mode; /* ON or OFF */

}

else

{

sts->mode ^= HAL_LED_MODE_ON; /* Toggle */

}

HalLedOnOff (led, sts->mode);

leds ^= led;

}

led <<= 1;

sts++;

}

break;

default:

break;

}

#elif (HAL_LED == TRUE)

LedOnOff(leds, mode);

#endif /* BLINK_LEDS && HAL_LED */

return ( HalLedState );

} //

通过上面程序的解读我发现,到目前为止对LED的操作真正实现的只有开、关、切换,所谓的

闪烁只是调用了一个系统事件设置函数osal_set_event (Hal_TaskID, HAL_LED_BLINK_EVENT);

难道这都是通过系统事件来实现的?因为闪烁涉及到了时间占空比,估计还调用了定时器,那有

可能还用到了中断,嗯,有可能。先接着分析下面的函数

// Check for Brown-Out reset

ChkReset(); //

检测重启原因【OnBoard.c】 void ChkReset( void ) { uint8 led; uint8 rib; // Isolate reset indicator bits rib = SLEEP & LRESET; //得到SLEEP中的RST状态【hal_mcu.h】 //#define LRESET 0x18 if ( rib == RESETPO ) //重启的原因【OnBoard.h】 #define RESETPO 0x00 // 上电重启 #define RESETEX 0x08 //外部重启 #define RESETWD 0x10 //看门狗重启 { // Put code here to handle Power-On reset } else if ( rib == RESETEX ) { // Put code here to handle External reset } else if ( rib == RESETWD ) { // Put code here to handle WatchDog reset } Else //未知的重启原因,闪烁LED指示 { HAL_DISABLE_INTERRUPTS(); led = HAL_LED_4; while ( 1 ) { HalLedSet( led, HAL_LED_MODE_ON ); MicroWait( 62500 ); MicroWait( 62500 ); HalLedSet( led, HAL_LED_MODE_OFF ); MicroWait( 37500 ); MicroWait( 37500 ); if ( !(led >>= 1) ) led = HAL_LED_4; } } }

/* Timer2 for Osal timer*/

//设置OSAL的时钟

OnboardTimerIntEnable = FALSE;

HalTimerConfig (OSAL_TIMER, // 8bit timer2

HAL_TIMER_MODE_CTC, // Clear Timer on Compare

HAL_TIMER_CHANNEL_SINGLE, // Channel 1 - default

HAL_TIMER_CH_MODE_OUTPUT_COMPARE, // Output Compare mode

OnboardTimerIntEnable, // Use interrupt

Onboard_TimerCallBack); // Channel Mode

//【OnBoard.h】

// 使用指定的定时器#define OSAL_TIMER HAL_TIMER_2

//【hal_timer.h】

//#define HAL_TIMER_0 0x00 // 8bit timer //fine HAL_TIMER_1 0x01 // 16bit Mac timer

//fine HAL_TIMER_2 0x02 // 8bit timer

//fine HAL_TIMER_3 0x03 // 16bit timer

//efine HAL_TIMER_MAX 4 // Max number of timer

//定时器的操作模式

//#define HAL_TIMER_MODE_NORMAL 0x01 // Normal Mode

//#define HAL_TIMER_MODE_CTC 0x02 // Clear Timer On Compare

//#define HAL_TIMER_MODE_MASK (HAL_TIMER_MODE_NORMAL | HAL_TIMER_MODE_CTC)

//定时器通道定义

//#define HAL_TIMER_CHANNEL_SINGLE 0x01 // Single Channel - default

//#define HAL_TIMER_CHANNEL_A 0x02 // Channel A

//#define HAL_TIMER_CHANNEL_B 0x04 // Channel B

//#define HAL_TIMER_CHANNEL_C 0x08 // Channel C

//#define HAL_TIMER_CHANNEL_MASK (HAL_TIMER_CHANNEL_SINGLE | \

HAL_TIMER_CHANNEL_A | \

HAL_TIMER_CHANNEL_B | \

HAL_TIMER_CHANNEL_C)

//通道模式

//#define HAL_TIMER_CH_MODE_INPUT_CAPTURE 0x01 // Channel Mode Input-Capture

//#define HAL_TIMER_CH_MODE_OUTPUT_COMPARE 0x02 // Channel Mode Output_Compare

//#define HAL_TIMER_CH_MODE_OVERFLOW 0x04 // Channel Mode Overflow

//#define HAL_TIMER_CH_MODE_MASK (HAL_TIMER_CH_MODE_INPUT_CAPTURE | \

HAL_TIMER_CH_MODE_OUTPUT_COMPARE | \

HAL_TIMER_CH_MODE_OVERFLOW)

//更新定时器的函数【OnBoard.c】

void Onboard_TimerCallBack ( uint8 timerId, uint8 channel, uint8 channelMode)

{

if ((timerId == OSAL_TIMER) && (channelMode == HAL_TIMER_CH_MODE_OUTPUT_COMPARE))

{

osal_update_timers();

//【OSAL_Timers.c】

//每一个时钟滴答跟新时钟结构体

void osal_update_timers( void )

{

osalTimerUpdate( tmr_decr_time ); //

【OSAL_Timers.c】 static void osalTimerUpdate( uint16 updateTime ) { halIntState_t intState; osalTimerRec_t *srchTimer; osalTimerRec_t *prevTimer; osalTimerRec_t *saveTimer; //typedef struct //{ //void *next; //UINT16 timeout; //UINT16 event_flag; //byte task_id; //} osalTimerRec_t; HAL_ENTER_CRITICAL_SECTION( intState ); //保存中断状态 // Update the system time osal_systemClock += updateTime; //就目前的程序流程来看updateTime=0 // Look for open timer slot if ( timerHead != NULL ) //osalTimerRec_t *timerHead; { // Add it to the end of the timer list srchTimer = timerHead; prevTimer = (void *)NULL; // Look for open timer slot while ( srchTimer ) { // Decrease the correct amount of time if (srchTimer->timeout <= updateTime) srchTimer->timeout = 0; else srchTimer->timeout = srchTimer->timeout - updateTime; // When timeout, execute the task if ( srchTimer->timeout == 0 ) { osal_set_event( srchTimer->task_id, srchTimer->event_flag ); // Take out of list if ( prevTimer == NULL ) timerHead = srchTimer->next; else prevTimer->next = srchTimer->next; // Next saveTimer = srchTimer->next; // Free memory osal_mem_free( srchTimer ); srchTimer = saveTimer; } else { // Get next prevTimer = srchTimer; srchTimer = srchTimer->next; } } #ifdef POWER_SAVING osal_retune_timers(); //osal_retune_timers调整CPU睡眠时间到最低的超时值,如果超时值

大于RETUNE_THRESHOLD,则睡眠时间将被设置为

RETUNE_THRESHOLD // osal_timer_activate()

启动或者停止系统时钟,参数为FALSE或者

TRUE #endif } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. } //

所有的定时器都以链表的形式储存着。这段关于系统时钟的程序也没有看

懂,真的看的是不明所以,不晓得到底要干什么,请知道的通知说明一下

啊,谢谢了

}

#ifdef POWER_SAVING

}

}

//配置时钟服务【hal_timer.c】

uint8 HalTimerConfig (uint8 timerId, uint8 opMode, uint8 channel, uint8 channelMode,

bool intEnable, halTimerCBack_t cBack)

{

uint8 hwtimerid;

hwtimerid = halTimerRemap (timerId);

//API硬件定时器ID与硬件定时器ID的转换,为什么要做这个转换?搞不懂【hal_timer.c】

#define HW_TIMER_1 0x00

#define HW_TIMER_3 0x01

#define HW_TIMER_4 0x02

#define HW_TIMER_INVALID 0x03

#define HW_TIMER_MAX 0x03

uint8 halTimerRemap (uint8 timerId)

{

switch (timerId)

{

case HAL_TIMER_0:

return HW_TIMER_3;

case HAL_TIMER_2:

return HW_TIMER_4;

case HAL_TIMER_3:

return HW_TIMER_1;

default:

return HW_TIMER_INVALID; }

}

if ((opMode & HAL_TIMER_MODE_MASK) && (timerId < HAL_TIMER_MAX) &&

(channelMode & HAL_TIMER_CHANNEL_MASK) && (channel & HAL_TIMER_CHANNEL_MASK))

//检测参数的正确性

{

halTimerRecord[hwtimerid].configured = TRUE;

halTimerRecord[hwtimerid].opMode = opMode;

halTimerRecord[hwtimerid].channel = channel;

halTimerRecord[hwtimerid].channelMode = channelMode;

halTimerRecord[hwtimerid].intEnable = intEnable;

halTimerRecord[hwtimerid].callBackFunc = cBack;

//【hal_timer.c】

// static halTimerSettings_t halTimerRecord[HW_TIMER_MAX];

//typedef struct

//{

// bool configured;

// bool intEnable;

// uint8 opMode;

// uint8 channel;

// uint8 channelMode;

// uint8 prescale;

// uint8 prescaleVal;

// uint8 clock;

// halTimerCBack_t callBackFunc;

//} halTimerSettings_t;

//typedef void (*halTimerCBack_t) (uint8 timerId, uint8 channel, uint8 channelMode);定时器的回调函

}

else

{

return HAL_TIMER_PARAMS_ERROR;

}

return HAL_TIMER_OK;

}

}

else // !OB_COLD

{

#ifdef ZTOOL_PORT

MT_IndReset();

#endif

/* Initialize Key stuff */

OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;

HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback); //初始化Key配置,这里是跟安全有关的东西,暂时不讨论

}

}

//可见系统启动参数的作用了冷启动(OB_COLD)中有系统时钟及LED的初始化,而热启动只是重置了

一下KEY而已。关于按键的初始化,稍后再做说明 

ZigBee学习之

学习之学习之

学习之26——HalDriverInit() // 初始化硬件驱动,这个估计跟硬件更加紧密了,只要板子不同就要做相应的修改 HalDriverInit();

 

//【hal_drivers.c】

void HalDriverInit (void)

{

HalTimerInit();//定时器初始化

//【hal_timer.c】

//一些宏定义

#define HAL_TIMER1_16_PRESCALE HAL_TIMER1_16_TC_DIV128

#define HAL_TIMER1_16_PRESCALE_VAL 128

#define HAL_TIMER3_8_PRESCALE HAL_TIMER34_8_TC_DIV128

#define HAL_TIMER3_8_PRESCALE_VAL 128

#define HAL_TIMER4_8_PRESCALE HAL_TIMER34_8_TC_DIV128

#define HAL_TIMER4_8_PRESCALE_VAL 128

/* Clock settings */

#define HAL_TIMER_16MHZ 16

#define HAL_TIMER_32MHZ 32

#define HAL_TIMER1_16_TC_DIV1 0x00 /* No clock pre-scaling */

#define HAL_TIMER1_16_TC_DIV8 0x04 /* Clock pre-scaled by 8 */

#define HAL_TIMER1_16_TC_DIV32 0x08 /* Clock pre-scaled by 32 */

#define HAL_TIMER1_16_TC_DIV128 0x0c /* Clock pre-scaled by 128 */

#define HAL_TIMER1_16_TC_BITS 0x0c /* Bits 3:2 */

//定时器的初始化很简单就是设置了各个寄存器的初始值,然后屏蔽了中断,就不多说了

void HalTimerInit (void)

{

T1CCTL0 = 0; /* Make sure interrupts are disabled */

T1CCTL1 = 0; /* Make sure interrupts are disabled */

T1CCTL2 = 0; /* Make sure interrupts are disabled */

T3CCTL0 = 0; /* Make sure interrupts are disabled */

T3CCTL1 = 0; /* Make sure interrupts are disabled */

T4CCTL0 = 0; /* Make sure interrupts are disabled */

T4CCTL1 = 0; /* Make sure interrupts are disabled */

//禁止定时器的中断 /* Setup prescale & clock for timer0 */

halTimerRecord[HW_TIMER_1].prescale = HAL_TIMER1_16_PRESCALE;

halTimerRecord[HW_TIMER_1].clock = HAL_TIMER_32MHZ;

halTimerRecord[HW_TIMER_1].prescaleVal = HAL_TIMER1_16_PRESCALE_VAL;

/* Setup prescale & clock for timer2 */

halTimerRecord[HW_TIMER_3].prescale = HAL_TIMER3_8_PRESCALE;

halTimerRecord[HW_TIMER_3].clock = HAL_TIMER_32MHZ;

halTimerRecord[HW_TIMER_3].prescaleVal = HAL_TIMER3_8_PRESCALE_VAL;

/* Setup prescale & clock for timer3 */

halTimerRecord[HW_TIMER_4].prescale = HAL_TIMER4_8_PRESCALE;

halTimerRecord[HW_TIMER_4].clock = HAL_TIMER_32MHZ;

halTimerRecord[HW_TIMER_4].prescaleVal = HAL_TIMER4_8_PRESCALE_VAL;

//设置定时器1,3,4的通道数据结构,其实就是在数据结构中设置了定时器相关寄存器的地址。

默认所有的定时器使用通道0

/* Setup Timer1 Channel structure */

halTimerChannel[HW_TIMER_1].TxCCTL = TCHN_T1CCTL;

// #define TCHN_T1CCTL &(X_T1CCTL0)

halTimerChannel[HW_TIMER_1].TxCCL = TCHN_T1CCL;

halTimerChannel[HW_TIMER_1].TxCCH = TCHN_T1CCH;

halTimerChannel[HW_TIMER_1].TxOVF = TCNH_T1OVF;

halTimerChannel[HW_TIMER_1].ovfbit = TCHN_T1OVFBIT;

halTimerChannel[HW_TIMER_1].intbit = TCHN_T1INTBIT;

/* Setup Timer3 Channel structure */

halTimerChannel[HW_TIMER_3].TxCCTL = TCHN_T3CCTL;

halTimerChannel[HW_TIMER_3].TxCCL = TCHN_T3CCL;

halTimerChannel[HW_TIMER_3].TxCCH = TCHN_T3CCH;

halTimerChannel[HW_TIMER_3].TxOVF = TCNH_T3OVF;

halTimerChannel[HW_TIMER_3].ovfbit = TCHN_T3OVFBIT;

halTimerChannel[HW_TIMER_3].intbit = TCHN_T3INTBIT;

/* Setup Timer4 Channel structure */

halTimerChannel[HW_TIMER_4].TxCCTL = TCHN_T4CCTL;

halTimerChannel[HW_TIMER_4].TxCCL = TCHN_T4CCL;

halTimerChannel[HW_TIMER_4].TxCCH = TCHN_T4CCH;

halTimerChannel[HW_TIMER_4].TxOVF = TCNH_T4OVF;

halTimerChannel[HW_TIMER_4].ovfbit = TCHN_T4OVFBIT;

halTimerChannel[HW_TIMER_4].intbit = TCHN_T4INTBIT;

}

#if (defined HAL_ADC) && (HAL_ADC == TRUE)

//如果开启了硬件ADC服务则进行ADC的初始化

HalAdcInit();

//【hal_adc.c】

void HalAdcInit (void)

{

#if (HAL_ADC == TRUE) volatile uint8 tmp;

ADCCON1 = HAL_ADC_STSEL | HAL_ADC_RAND_GEN | 0x03;

// #define HAL_ADC_STSEL HAL_ADC_STSEL_ST

// #define HAL_ADC_STSEL_ST 0x30

//设置ADCCON1.ST =1为启动前提条件

// #define HAL_ADC_RAND_GEN HAL_ADC_RAND_STOP

// #define HAL_ADC_RAND_STOP 0x0c

//停止随机数发生器

//这些配置都可以在【hal_adc.c】中的常数定义部分找到

ADCCON2 = HAL_ADC_REF_VOLT | HAL_ADC_DEC_RATE | HAL_ADC_SCHN;

//AVDD_SOC为参考电压,8位分辨率,AVDD/3为输入

//以上只是ADC的初始状态,使用的时候需要根据情况进行更改

tmp = ADCL; /* 通过读取转换值来清除EOC */

tmp = ADCH;

//因为每次重启后读取的ADC对外部的转换都是地电平,所以这里启动两次对地的ADC转换来消

除这个bug

ADCCON3 = HAL_ADC_REF_VOLT | HAL_ADC_DEC_RATE | HAL_ADC_ECHN;

while ((ADCCON1 & HAL_ADC_EOC) != HAL_ADC_EOC); /* Wait for conversion */

tmp = ADCL; /* read ADCL,ADCH to clear EOC */

tmp = ADCH;

ADCCON3 = HAL_ADC_REF_VOLT | HAL_ADC_DEC_RATE | HAL_ADC_ECHN;

while ((ADCCON1 & HAL_ADC_EOC) != HAL_ADC_EOC); /* Wait for conversion */

tmp = ADCL; /* read ADCL,ADCH to clear EOC */

tmp = ADCH;

#endif

}

#endif

#if (defined HAL_DMA) && (HAL_DMA == TRUE)

HalDmaInit();

//DMA初始化【hal_dma.c】

void HalDmaInit( void )

{

HAL_DMA_SET_ADDR_DESC0( &dmaCh0 );

//【hal_dma.h】

//DMA配置数据结构

typedef struct {

uint8 srcAddrH;

uint8 srcAddrL;

uint8 dstAddrH;

uint8 dstAddrL;

uint8 xferLenV;

uint8 xferLenL;

uint8 ctrlA;

uint8 ctrlB; } halDMADesc_t;

//设置DMA通道0的配置地址

#define HAL_DMA_SET_ADDR_DESC0( a ) \

st( \

DMA0CFGH = (uint8)( (uint16)(a) >> 8 ); \

DMA0CFGL = (uint8)( (uint16)(a) ); \

)

HAL_DMA_SET_ADDR_DESC1234( dmaCh1234 );

}

#endif

/* AES高级加密单元,暂时不说吧 */

#if (defined HAL_AES) && (HAL_AES == TRUE)

HalAesInit();

#endif

/* LCD初始化,这个要大改了,相信每个人的板子上面的LCD设计都有可能不相同的吧 */

//使用LCD的前提是要定义了LCD_HW

#if (defined HAL_LCD) && (HAL_LCD == TRUE)

HalLcdInit();

#endif

/* LED初始化,也跟硬件关系很大 */

#if (defined HAL_LED) && (HAL_LED == TRUE)

HalLedInit();

//【hal_led.c】

void HalLedInit (void)

{

#if (HAL_LED == TRUE)

/* 关闭所有LED */

HalLedSet (HAL_LED_ALL, HAL_LED_MODE_OFF);

/* 这个sleepActive 不晓得是用作什么用的,后面应该会提到,初始化为FALSE */

HalLedStatusControl.sleepActive = FALSE;

#endif /* HAL_LED */

}

#endif

/* UART初始化,这里只是配置了UART的端口以及UART-DMA模式的配置,并没有配置UART数据传输格

式 */

#if (defined HAL_UART) && (HAL_UART == TRUE)

HalUARTInit();

//【hal_uart.c】

void HalUARTInit( void )

{

#if HAL_UART_DMA

halDMADesc_t *ch;

//如果允许UART的DMA则定义一个DMA配置数据结构变量

#endif // 初始设置USART0优先级高于USART1

P2DIR &= ~P2DIR_PRIPO;

// #define P2DIR_PRIPO 0xC0

P2DIR |= HAL_UART_PRIPO;

//#if HAL_UART_0_ENABLE

// #define HAL_UART_PRIPO 0x00

//#else

// #define HAL_UART_PRIPO 0x40

//#endif

//如果定义HAL_UART_0_ENABLE则UART0有最高优先级,否则UART1高优先级。在我的板子

上是使用UART0所以要定义HAL_UART_0_ENABLE了,呵呵

#if HAL_UART_0_ENABLE

//设置UART0 I/O到P0.哈哈刚好符合我板子的情况,太妙了

//#define HAL_UART_0_PERCFG_BIT 0x01

PERCFG &= ~HAL_UART_0_PERCFG_BIT;

/* 配置P0上的Tx and Rx */

// #define HAL_UART_0_P0_RX_TX 0x0c

P0SEL |= HAL_UART_0_P0_RX_TX;

/* 保证ADC不用这几个引脚作为输入,其实重启后的状态就是不用的 */

ADCCFG &= ~HAL_UART_0_P0_RX_TX;

/*用UART Mode */

U0CSR = CSR_MODE;

/* 清空缓存 */

U0UCR = UCR_FLUSH;

#endif

//对于UART1的配置如出一辙,只是换了一下管脚而已,把UART配置到P1啦

#if HAL_UART_1_ENABLE

// Set UART1 I/O location to P1.

PERCFG |= HAL_UART_1_PERCFG_BIT;

/* Enable Tx and Rx on P1 */

P1SEL |= HAL_UART_1_P1_RX_TX;

/* Make sure ADC doesnt use this */

ADCCFG &= ~HAL_UART_1_P1_RX_TX;

/* Mode is UART Mode */

U1CSR = CSR_MODE;

/* Flush it */

U1UCR = UCR_FLUSH;

#endif

//下面是UART的DMA配置,需要先定义HAL_UART_DMA为TRUE

#if HAL_UART_DMA

ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );

// #define HAL_DMA_GET_DESC1234( a ) (dmaCh1234+((a)-1))

// #define HAL_DMA_CH_TX 3

//以上是配置TX用DMA通道2 HAL_DMA_SET_DEST( ch, DMA_UDBUF );

//通道的目的地址设为UxDBUF【hal_uart.c】

//#define DMA_UDBUF HAL_DMA_U0DBUF

// #define HAL_DMA_U0DBUF 0xDFC1

//#define HAL_DMA_U1DBUF 0xDFF9

//在TI的这个驱动中只支持两个USART中的一个使用DMA,不支持同时使用

//配置VLEN域使用LEN字段

HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );

//初始化为一次传输一字节

HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );

//一次触发只传输一个字节,触发源为UART0 TX完成

HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );

HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );

//源地址递增,目的地址不递增

HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );

HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );

//传输完成不发送IRQ请求

HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );

//8位一字节,高优先级模式

HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );

HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );

//一下是配置RX为DMA的源,接收使用DMA模式,大同小异就不多说了.

ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );

// The start address of the source.

HAL_DMA_SET_SOURCE( ch, DMA_UDBUF );

// Using the length field to determine how many bytes to transfer.

HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );

HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );

// The bytes are transferred 1-by-1 on Rx Complete trigger.

HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );

HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_RX );

// The source address is constant - the Rx Data Buffer.

HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );

// The destination address is incremented by 1 word after each transfer.

HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_1 );

// The DMA is to be polled and shall not issue an IRQ upon completion.

HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );

// Xfer all 8 bits of a byte xfer.

HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );

// DMA has highest priority for memory access.

HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );

#endif

}

#endif /* KEY初始化 */

#if (defined HAL_KEY) && (HAL_KEY == TRUE)

HalKeyInit();

//【hal_key.c】在这个文件中定义了SW6,SW5的端口,看来要大改了,我只有3个独立按键,而且

用的是P1口

void HalKeyInit( void )

{

#if (HAL_KEY == TRUE)

halKeySavedKeys = 0;

#if defined (HAL_KEY_SW_6_ENABLE)

// #define HAL_KEY_SW_6_BIT HAL_KEY_BIT1

//#define HAL_KEY_BIT1 0x02

// #define HAL_KEY_SW_5_BIT HAL_KEY_BIT0

//对于CC2430DB,SW6用的是P0_1,SW5用的是P2_0;对于CC2430EB,没有SW6,SW5用的

是P0_5

HAL_KEY_SW_6_SEL &= ~(HAL_KEY_SW_6_BIT);

HAL_KEY_SW_6_DIR &= ~(HAL_KEY_SW_6_BIT);

#endif

#if defined (HAL_KEY_SW_5_ENABLE)

HAL_KEY_SW_5_SEL &= ~(HAL_KEY_SW_5_BIT);

HAL_KEY_SW_5_DIR &= ~(HAL_KEY_SW_5_BIT);

HAL_KEY_SW_5_INP |= HAL_KEY_SW_5_BIT;

#endif

/* 初始化回调函数 */

pHalKeyProcessFunction = NULL;

/* Start with key is not configured */

HalKeyConfigured = FALSE;

#endif /* HAL_KEY */

}

#endif

} ZigBee

学习之

学习之学习之

学习之27——osal_nv_init //初始化NV系统,也就是非易失性储存系统,相当与对FLASH的初始化,不多说了,只要能正常的擦除页就返回TRUE,

直接调用吧 osal_nv_init( NULL );

//【OSAL_Nv.c】

//检测扩展地址,如果无效(全F)则进行一个简单的配置,协调器配置成全10,否则配额成全20

zmain_ext_addr();

//注意其中line283左右的代码,用到了LED和KEY

………………

// Flash LED1 until user hits SW5

led = HAL_LED_MODE_OFF; while ( HAL_KEY_SW_5 != HalKeyRead() )

{

MicroWait( 62500 );

HalLedSet( HAL_LED_1, led^=HAL_LED_MODE_ON ); // Toggle the LED

MicroWait( 62500 );

}

HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF );

……………… ZigBee

学习之

学习之学习之

学习之28——zgInit() //初始化Z-Stack全局变量,用NV中的值初始化物件,如果NV中没有值则用默认值,并将值写入NV中,启动选项

ZCD_NV_STARTUP_OPTION指示了配置状态【ZGlobals.c】 zgInit(); ZigBee

学习之

学习之学习之

学习之29——ZMacInit() // 初始化MAC ZMacInit();

//【zmac.c】

ROOT uint8 ZMacInit( void )

{

uint8 stat;

MAC_Init();

MAC_InitDevice();

#if defined ( RTR_NWK )

//如果定义的是路由网络

MAC_InitCoord();

#endif

stat = ZMacReset( TRUE );

//设置MAC PIB

osal_int_disable( INTS_ALL );

return ( stat );

} ZigBee

学习之

学习之学习之

学习之30——afInit() #ifndef NONWK

//如果禁止了NWK,APS,ZDO函数功能,则需要单独初始化AF层

// Since the AF isn't a task, call it's initialization routine

afInit();

//【AF.c】  void afInit( void )

{

//开始时没有设置ENDPOINT,对于AF的初始化就是初始化ENDPOINT

epList = NULL;

// epList_t *epList;

typedef enum

{

eEP_AllowMatch = 1,

eEP_NotUsed

} eEP_Flags;

typedef struct

{

endPointDesc_t *epDesc;

eEP_Flags flags;

pDescCB pfnDescCB; // Don't use if this function pointer is NULL.

void *nextDesc;

} epList_t;

//endpoint设备描述,每一个在表中的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;

//简单描述数据结构

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;

typedef enum

{

noLatencyReqs,

fastBeacons,

slowBeacons

} afNetworkLatencyReq_t; }

#endif ZigBee

学习之

学习之学习之

学习之31——osal_init_system //初始化操作系统,主要是堆栈,时钟,电源模式,任务号分配 osal_init_system();

//通过创建定义在任务表【OSAL_Tasks.h】中的任务来初始化任务系统【OSAL.c】

byte osal_init_system( void )

{

//初始化内存分配系统【OSAL_Memory.c】

osal_mem_init();

//初始化消息队列,其实就是一个指针

osal_qHead = NULL;

// osal_msg_q_t osal_qHead;

// typedef void * osal_msg_q_t;

#if defined( OSAL_TOTAL_MEM )

//如果定义了OSAL_TOTAL_MEM就会跟踪系统的堆栈使用情况并通过LCD显示出来

osal_msg_cnt = 0;

#endif

//初始化系统定时器【OSAL_Timers.c】

osalTimerInit();

void osalTimerInit( void )

{

// Initialize the rollover modulo

tmr_count = TICK_TIME;

//【OnBoard.h】

// #define TICK_TIME 1000 //每次时钟滴答的时间(毫秒)

tmr_decr_time = TIMER_DECR_TIME;

// #define TIMER_DECR_TIME 1

osal_timer_activate( false );

//初始化系统定时器,实际上是停止了时钟定时器【OSAL_Timers.c】

void osal_timer_activate( byte turn_on )

{

osal_timer_hw_setup( turn_on );

//设置时钟硬件【OSAL_Timers.c】

void osal_timer_hw_setup( byte turn_on )

{

if (turn_on)

{

HalTimerStart (OSAL_TIMER, tmr_count );

//启动时钟

}

else {

HalTimerStop (OSAL_TIMER);

}

}

timerActive = turn_on;

}

timerActive = false;

osal_systemClock = 0;

}

//初始化电源管理系统

osal_pwrmgr_init();

//配置器件使用的电源模式【OSAL_PwrMgr.c】

void osal_pwrmgr_init( void )

{

pwrmgr_attribute.pwrmgr_device = PWRMGR_ALWAYS_ON;

// 默认不打开节能模式

//【OSAL_PwrMgr.h】

// #define PWRMGR_ALWAYS_ON 0 //不使用节能模式

//#define PWRMGR_BATTERY 1 //使用电池供电,允许进入休眠模式

// pwrmgr_attribute_t pwrmgr_attribute;

typedef struct

{

uint16 pwrmgr_task_state;

uint16 pwrmgr_next_timeout;

uint16 accumulated_sleep_time;

uint8 pwrmgr_device;

} pwrmgr_attribute_t;

pwrmgr_attribute.pwrmgr_task_state = 0; // Cleared. All set to conserve

}

//初始化系统任务,为每个任务调用初始化函数

osalInitTasks();

//【sapi.c】

void osalInitTasks( void )

{

uint8 taskID = 0;

tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

// uint16 *tasksEvents;

// const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );

//任务队列中其实是一个个的事件处理函数,这里分配了任务要用的内存空间。然后为每个任务

分配一个不同的任务ID,ID递增,按照初始化的顺序排列在tasksArr队列中

macTaskInit( taskID++ );

nwk_init( taskID++ );

Hal_Init( taskID++ ); #if defined( MT_TASK )

MT_TaskInit( taskID++ );

#endif

APS_Init( taskID++ );

ZDApp_Init( taskID++ );

SAPI_Init( taskID );

}

// Setup efficient search for the first free block of heap.

//跳过第一个块

osal_mem_kick();

return ( ZSUCCESS );

} ZigBee

学习之

学习之学习之

学习之32—— zmain_dev_info() //显示器件的信息,需要LCD的支持,主要使显示了器件的IEEE地址 zmain_dev_info();

static ZSEG void zmain_dev_info ( void )

{

#ifdef LCD_SUPPORTED

uint8 i;

uint8 ch;

uint8 *xad;

unsigned char lcd_buf[18];

//显示扩展地址

xad = (uint8*)&aExtendedAddress + Z_EXTADDR_LEN - 1;

for ( i = 0; i < Z_EXTADDR_LEN*2; xad-- ) {

ch = (*xad >> 4) & 0x0F;

lcd_buf[i++] = ch + (( ch < 10 ) ? '0' : '7');

ch = *xad & 0x0F;

lcd_buf[i++] = ch + (( ch < 10 ) ? '0' : '7');

}

lcd_buf[Z_EXTADDR_LEN*2] = '\0';

HalLcdWriteString( "IEEE Address:", HAL_LCD_LINE_1 );

HalLcdWriteString( (char*)lcd_buf, HAL_LCD_LINE_2 );

#endif // LCD

} ZigBee

学习之

学习之学习之

学习之33——osal_start_system() osal_start_system(); //运行系统【OSAL.c】 //此函数是任务系统的主循环函数,他将轮询所有任务事件然后调用相关的任务处理函数,没有任务时将进

入休眠状态。 void osal_start_system( void )

{

#if !defined ( ZBIT )

//虽然搞不清楚这个ZBIT什么东西,不过应该是没有定义的,如果定义了的话所有函数只会顺序跑下来,

跑完这个函数整个系统就结束了,没有意义,这里看到这个无限循环应该就是整个系统的运行方式了

for(;;)

#endif

{

uint8 idx = 0;

Hal_ProcessPoll(); // This replaces MT_SerialPoll() and osal_check_timer().

//OSAL调用此函数来推送UART,TIMER。。。【hal_drivers.c】

void Hal_ProcessPoll ()

{

//选取定时器

HalTimerTick();

//检查定时器的计数溢出【hal_timer.c】

void HalTimerTick (void)

{

if (!halTimerRecord[HW_TIMER_1].intEnable)

{

halProcessTimer1 ();

//处理TIMER1事件

void halProcessTimer1 (void)

{

if (halTimerRecord[halTimerRemap(HAL_TIMER_3)].channelMode ==

HAL_TIMER_CH_MODE_OUTPUT_COMPARE)

//处理通道模式为输出比较时的情况

{

if (T1CTL & T1CTL_CH0IF)

//检查通道0中断标志看是否达到设定的中断要求

{

T1CTL &= ~(T1CTL_CH0IF);

//清除中断标志

halTimerSendCallBack (HAL_TIMER_3, HAL_TIMER_CHANNEL_A,

HAL_TIMER_CH_MODE_OUTPUT_COMPARE);

//给回调函数发送回应

void halTimerSendCallBack (uint8 timerId, uint8 channel, uint8

channelMode)

{

uint8 hwtimerid;

hwtimerid = halTimerRemap (timerId);

if (halTimerRecord[hwtimerid].callBackFunc)

(halTimerRecord[hwtimerid].callBackFunc) (timerId, channel,

channelMode); }

}

if (T1CTL & T1CTL_CH1IF)

{

T1CTL &= ~(T1CTL_CH1IF);

halTimerSendCallBack (HAL_TIMER_3, HAL_TIMER_CHANNEL_B,

HAL_TIMER_CH_MODE_OUTPUT_COMPARE);

}

if (T1CTL & T1CTL_CH2IF)

{

T1CTL &= ~(T1CTL_CH2IF);

halTimerSendCallBack (HAL_TIMER_3, HAL_TIMER_CHANNEL_C,

HAL_TIMER_CH_MODE_OUTPUT_COMPARE);

}

}

else if

(halTimerRecord[halTimerRemap(HAL_TIMER_3)].channelMode ==

HAL_TIMER_CH_MODE_OVERFLOW)

{

if (T1CTL & T1CTL_OVFIF)

{

T1CTL &= ~(T1CTL_OVFIF);

halTimerSendCallBack (HAL_TIMER_3,

HAL_TIMER_CHANNEL_SINGLE,

HAL_TIMER_CH_MODE_OVERFLOW);

}

}

}

}

if (!halTimerRecord[HW_TIMER_3].intEnable)

{

halProcessTimer3 ();

}

if (!halTimerRecord[HW_TIMER_4].intEnable)

{

halProcessTimer4 ();

}

}

/* UART的选取*/

#if (defined HAL_UART) && (HAL_UART == TRUE)

HalUARTPoll();

//【hal_uart.c】

typedef struct

{ uint8 *rxBuf;

uint8 rxHead;

uint8 rxTail;

uint8 rxMax;

uint8 rxCnt;

uint8 rxTick;

uint8 rxHigh;

uint8 *txBuf;

#if HAL_UART_BIG_TX_BUF

uint16 txHead;

uint16 txTail;

uint16 txMax;

uint16 txCnt;

#else

uint8 txHead;

uint8 txTail;

uint8 txMax;

uint8 txCnt;

#endif

uint8 txTick;

uint8 flag;

halUARTCBack_t rxCB;

} uartCfg_t;//UART数据结构,这个数据结构在文档中没有提到,估计是串口缓存区的配置串口

需要掌握的是HalUARTOpen函数和halUARTCfg_t数据结构

#endif

}

do {

if (tasksEvents[idx]) //轮询检查任务

{

break;

}

} while (++idx < tasksCnt);

if (idx < tasksCnt)

{

uint16 events;

halIntState_t intState;

HAL_ENTER_CRITICAL_SECTION(intState);

events = tasksEvents[idx];

tasksEvents[idx] = 0; // Clear the Events for this task.

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

}

}

} // main()

//到此main函数我们就分析完了 ZigBee

学习之

学习之学习之

学习之34—按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析1 按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析 因为我的开发板没有DB的joystick,只有三个可用的独立按键,所以必须对Z-Stack中的按

键部分的代码做比较多的修改。修改过程中我们可以看到Zstack对按键的处理,可以更深入地

理解协议栈的实现。我首先把代码中关于SW5和SW6的部分全部注释掉了,然后自己声明了一

些宏定义。我们主要来看一下函数部分的实现和执行流程。 //

按键初始化部分,设置端口状态,初始化标志变量 void HalKeyInit( void ) { #if (HAL_KEY == TRUE) /* Initialize previous key to 0 */ halKeySavedKeys = 0; //因为我在前面已经注释掉了HAL_KEY_SW_6_ENABLE和HAL_KEY_SW_5_ENABLE,所以

下面部分代码不执行 #if defined (HAL_KEY_SW_6_ENABLE) HAL_KEY_SW_6_SEL &= ~(HAL_KEY_SW_6_BIT); /* Set pin function to GPIO */ HAL_KEY_SW_6_DIR &= ~(HAL_KEY_SW_6_BIT); /* Set pin direction to Input */ #endif #if defined (HAL_KEY_SW_5_ENABLE) HAL_KEY_SW_5_SEL &= ~(HAL_KEY_SW_5_BIT); /* Set pin function to GPIO */ HAL_KEY_SW_5_DIR &= ~(HAL_KEY_SW_5_BIT); /* Set pin direction to Input */ HAL_KEY_SW_5_INP |= HAL_KEY_SW_5_BIT; /* Set pin input mode to tri-state */ #endif //

这里是我加的,主要是设置按键链接的IO为通用IO,输入状态 HAL_KEY_SW_SEL &= ~(HAL_KEY_SW_1_BIT | HAL_KEY_SW_2_BIT

|HAL_KEY_SW_3_BIT); /*

设置管脚功能为GPIO*/ HAL_KEY_SW_DIR &= ~(HAL_KEY_SW_1_BIT | HAL_KEY_SW_2_BIT

|HAL_KEY_SW_3_BIT); /*

设置管脚为输入*/ //---------------------------------------  /* Initialize callback function */ pHalKeyProcessFunction = NULL; /* Start with key is not configured */ HalKeyConfigured = FALSE; #endif /* HAL_KEY */ } //按键配置,根据是否采用中断方式响应按键来进行不同的配置。这里把不相干的部分省略了 void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback) { #if (HAL_KEY == TRUE) /* Enable/Disable Interrupt or */ Hal_KeyIntEnable = interruptEnable; /* Register the callback fucntion */ pHalKeyProcessFunction = cback; //上面将传进来的参数送给局部变量,这样可以保护参数,嗯,学习! /* Determine if interrupt is enable or not */ if (Hal_KeyIntEnable) { //打开中断允许,清除相关的中断标志 HAL_KEY_SW_PXIF = 0; /*清除P1全局中断*/ HAL_KEY_SW_PXIFG = ~(HAL_KEY_SW_1_BIT | HAL_KEY_SW_2_BIT

|HAL_KEY_SW_3_BIT); /*

清除按键1,2,3端口的中断标志*/ HAL_KEY_SW_IEN |= (HAL_KEY_SW_1_BIT | HAL_KEY_SW_2_BIT

|HAL_KEY_SW_3_BIT); /*

允许各按键的中断*/ HAL_KEY_SW_GLOBAL_IEN |= HAL_KEY_SW_IENBIT; /*允许P1中断*/ /* Do this only after the hal_key is configured - to work with sleep stuff */ if (HalKeyConfigured == TRUE) {//第一次配置按键是不会进入到这里执行的,必须配置过一次以后将标志HalKeyConfigured置

位才行,但是只要执行按键初始化操作就可以复位HalKeyConfigured标志。 osal_stop_timerEx( Hal_TaskID, HAL_KEY_EVENT); /*

如果采用中断方式则不用定时器来发送

事件,所以关闭以前为这个事件开启的系统时钟*/ } } else /*

这里是非中断方式的执行*/ { HAL_KEY_SW_GLOBAL_IEN &= ~HAL_KEY_SW_IENBIT; /*不允许P1中断*/ HAL_KEY_SW_IEN &= ~(HAL_KEY_SW_1_BIT | HAL_KEY_SW_2_BIT

|HAL_KEY_SW_3_BIT); /*

不允许各按键的中断*/ osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); /*此函数根据传进来的参数启创建一个指定任务的定时器并启动,当定时器计时到时就会发

出指定的事件给指定的任务。此函数仅仅是为指定事件和指定任务创建了一个时钟,并使时钟运

行,那时钟超时的处理函数,事件的发送是在哪里呢? */ //

一路跟踪定位:

osal_timer_activate()——osal_timer_hw_setup()——HalTimerStart()——HalTimerInterruptEnable()

【不要误会为是一个开启中断的标志,这个函数的操作过程是如果定时器配置中的中断允许为开

启的话,就开启定时器的中断,如果为不允许的话就不开启中断,所以其实这是一个定时器中断

配置函数。至此已没有能继续跟踪的函数。那么猜想应该是开启了总中断,在定时器计时到时进

入中断服务程序进行相关事件和消息的发送,应为系统时钟使用的是一个8位的定时器。 #define OSAL_TIMER HAL_TIMER_2 #define HAL_TIMER_2 0x02 // 8bit timer 我们关键来看看这个定时器的配置情况】 //

在函数void InitBoard()【OnBoard.c】中可以发现对系统定时器的配置: ZigBee学习之

学习之学习之

学习之35-按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析按键部分及系统调用时钟的分析

按键部分及系统调用时钟的分析2 /* Timer2 最为系统任务的时钟系统*/ OnboardTimerIntEnable = FALSE; HalTimerConfig (OSAL_TIMER, // 8bit timer2 HAL_TIMER_MODE_CTC, // Clear Timer on Compare HAL_TIMER_CHANNEL_SINGLE, // Channel 1 - default HAL_TIMER_CH_MODE_OUTPUT_COMPARE, // Output Compare mode OnboardTimerIntEnable, // Use interrupt Onboard_TimerCallBack); // Channel Mode //函数执行后得到系统任务时钟(HAL_TIMER_2)的配置结构: halTimerRecord[hwtimerid].configured = TRUE; halTimerRecord[hwtimerid].opMode = HAL_TIMER_MODE_CTC; halTimerRecord[hwtimerid].channel = HAL_TIMER_CHANNEL_SINGLE; halTimerRecord[hwtimerid].channelMode = HAL_TIMER_CH_MODE_OUTPUT_COMPARE; halTimerRecord[hwtimerid].intEnable = FALSE; halTimerRecord[hwtimerid].callBackFunc = Onboard_TimerCallBack; //Onboard_TimerCallBack()为处理系统时钟的回调函数,这个函数什么时候会被调用呢?是

个问题 //

一些相关宏的定义如下: //#define HAL_TIMER_MODE_CTC 0x02 // Clear Timer On Compare //#define HAL_TIMER_CHANNEL_SINGLE 0x01 // Single Channel - default //#define HAL_TIMER_CH_MODE_OUTPUT_COMPARE 0x02 // Channel Mode

Output_Compare //

现在系统任务时钟所需要使用的定时器已经配置好了,但是如何启动的呢?启动定时器必

须要对TxCTL寄存器启动位置位才能启动啊!我们回到ZMain.c中的main()中,在

InitBoard( OB_COLD );下面接着执行的是HalDriverInit()函数,在此函数中最开始就是初始化了

定时器HalTimerInit();进去一看发现任然没有启动定时器!仅仅只是对定时器的寄存器进行了一

个别名配置而已,增加了如下的配置: halTimerRecord[HW_TIMER_4].prescale = HAL_TIMER4_8_PRESCALE; halTimerRecord[HW_TIMER_4].clock = HAL_TIMER_32MHZ;  halTimerRecord[HW_TIMER_4].prescaleVal = HAL_TIMER4_8_PRESCALE_VAL; /* Setup Timer4 Channel structure */ halTimerChannel[HW_TIMER_4].TxCCTL = TCHN_T4CCTL; halTimerChannel[HW_TIMER_4].TxCCL = TCHN_T4CCL; halTimerChannel[HW_TIMER_4].TxCCH = TCHN_T4CCH; halTimerChannel[HW_TIMER_4].TxOVF = TCNH_T4OVF; halTimerChannel[HW_TIMER_4].ovfbit = TCHN_T4OVFBIT; halTimerChannel[HW_TIMER_4].intbit = TCHN_T4INTBIT; 那么是不是初始在main()中的osal_init_system();【OSAL.c】函数中,好,那我们来看一下,

是不是这里启动了系统时钟定时器。在这个函数中又调用了一个函数嫌疑最大:osalTimerInit();

【OSAL_Timers.c】 void osalTimerInit( void ) { //

初始化了两个变量 //#define TICK_TIME 1000 //#define TIMER_DECR_TIME 1 tmr_count = TICK_TIME; tmr_decr_time = TIMER_DECR_TIME; //这里是停止了系统时钟定时器OSAL_TIMER osal_timer_activate( false ); timerActive = false; osal_systemClock = 0; } //看来在这个函数中也没有启动系统定时器,反而调用了停止系统时钟定时器的函数,莫非

是黎明前的黑暗,呵呵!接着在main()函数中找嫌疑犯!再往下是打开了总中断

osal_int_enable( INTS_ALL );剩下的可能在就是osal_start_system()中启动系统时钟了,我们来找

一下。 //

在此函数中调用了Hal_ProcessPoll ()【hal_drivers.c】 void Hal_ProcessPoll () { /* Timer Poll */ HalTimerTick(); //这个函数又是个跟时钟相关的函数,会不会在这里面启动系统时钟呢?来

看一下 /* UART Poll */ #if (defined HAL_UART) && (HAL_UART == TRUE) HalUARTPoll(); #endif } //

【hal_timer.c】 void HalTimerTick (void) { //前面调用的InitBoard()中已经设置halTimerRecord[hwtimerid].intEnable = FALSE; //再根据HAL_TIMER_2 --> HW Timer 4,所以会执行以下函数: ………… if (!halTimerRecord[HW_TIMER_4].intEnable) { halProcessTimer4 (); } } //继续跟踪。这个函数在定时器发生中断的时候就会执行,因为在定时器4中断中有调用此

函数: HAL_ISR_FUNCTION( halTimer4Isr, T4_VECTOR ) {halProcessTimer4 ();} void halProcessTimer4 (void) { if (halTimerRecord[halTimerRemap(HAL_TIMER_2)].channelMode == HAL_TIMER_CH_MODE_OUTPUT_COMPARE) { if (TIMIF & TIMIF_T4CH0IF) {//

定时器4通道0产生中断 TIMIF &= ~(TIMIF_T4CH0IF); //清除中断标志 halTimerSendCallBack (HAL_TIMER_2, HAL_TIMER_CHANNEL_A,

HAL_TIMER_CH_MODE_OUTPUT_COMPARE); //

调用前面初始化的回调函数,对系统时钟+1 } if (TIMIF & TIMIF_T4CH1IF) { TIMIF &= ~(TIMIF_T4CH1IF); halTimerSendCallBack (HAL_TIMER_2, HAL_TIMER_CHANNEL_B,

HAL_TIMER_CH_MODE_OUTPUT_COMPARE); } } ………… } //

貌似还没有看到启动系统时钟的定时器啊!呵呵别急,我们现在回过来看按键程序,我们

接着分析这个函数: byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value ) { halIntState_t intState; osalTimerRec_t *newTimer; HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts. //

为此任务和事件填充一个新定时器数据结构,这个结构中将包含计数时间,要传递的

时间,接收事件的任务 newTimer = osalAddTimer( taskID, event_id, timeout_value ); if ( newTimer ) { #ifdef POWER_SAVING // Update timer registers osal_retune_timers(); (void)timerActive; #endif if ( timerActive == FALSE ) {//这里要注意了,回想一下我们对系统时钟启动的分析,只发现停止了系统时钟,初

始timerActive == FALSE,现在终于启动定时器了。而且处于开中断的状态下,这样的

话当计时器溢出的时候当然会产生中断就去执行中断函数了。所以我们得出系统时钟

也不是每时每刻都在运行的! osal_timer_activate( TRUE ); } } HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts. return ( (newTimer != NULL) ? ZSUCCESS : NO_TIMER_AVAIL ); } } //

我们再来看一下中断服务函数,我的是用P1口: HAL_ISR_FUNCTION( halKeyPort1Isr, P1INT_VECTOR ) {//刚开始是对芯片版本的判断,这个部重要了,可以去掉判断直接清除P1IF标志位 if( CHVER <= REV_D ) { P1IF = 0; } //这个函数是清除端口每一位的子中断,然后继续进行先前的任务,即发送按键事件

HAL_KEY_EVENT给指定的任务 halProcessKeyInterrupt(); if( CHVER >= REV_E ) {//

大于版本E的清除中断后进入睡眠模式 P1IF = 0; CLEAR_SLEEP_MODE(); } } //注意在ZStack1.4.3-1.2.1中对于任务的管理是通过任务表来进行的,每个任务由初始化函数和

事件处理函数构成,任务的初始化通过osalInitTasks()进行,然后OSAL利用任务表来调用任务

函数,注意任务表中的顺序必须和任务初始化的顺序一致 //

任务处理函数表,在后面可以添加自己的任务处理函数【sapi.c】 const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, #if defined( MT_TASK ) MT_ProcessEvent, #endif APS_event_loop, ZDApp_event_loop, SAPI_ProcessEvent }; 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++ ); SAPI_Init( taskID ); } //有一条主线:osal_set_event()将事件存入tasksEvents数组中【或者:通过HalTimerConfig()配置

定时器的回调函数——对指定任务和事件调用osal_start_timerEx()等指定超时时间到达后通过调

用回调函数来调用osal_set_event()】——系统主循环中检查到tasksEvents 数组中有事件——调

用相应的接收事件的函数(tasksArr[idx])( idx, events ) ——Hal_ProcessEvent()检查是否是按键事

件—— HalKeyPoll()得到按键值——调用按键回调函数OnBoard_KeyCallback(),回调函数在

InitBoard()中通过调用HalKeyConfig()来配置——OnBoard_SendKeys()构造消息包,向应用发送

按键信息,同时发送了KEY_CHANGE事件标志,【之前必须通过RegisterForKeys()注册接收按

键事件的任务ID,此函数执行才有意义】——osal_msg_send()对对应任务发送消息——发送成

功返回OnBoard_KeyCallback()进行相关按键的处理。这以上就是整个任务系统的执行流程。对

于系统定时器的理解我认为系统时钟所用定时器并不是每时每刻都到运行的,而且它的作用是指

定一个安全合适的系统时间间隔期。 ZigBee

学习之

学习之学习之

学习之36——simpleapp分析

分析分析

分析-续

续续

续 根据【sapi.c】中任务队列中有两个任务是接收按键事件的,如果有按键发生就会分别调用这个

两个事件处理函数。 const pTaskEventHandlerFn tasksArr[] = { ………… Hal_ProcessEvent, ………… SAPI_ProcessEvent }; Hal_ProcessEvent

调用HalKeyPoll读取按键值到keys变量中,然后调用回调函数

OnBoard_KeyCallback ,在回调函数中调用OnBoard_SendKeys 给应用发送键值,在此函数中会

构造消息数据包,并且将硬件的按键事件打包成KEY_CHANGE 系统消息来发送,osal_msg_send

发送消息,将消息放到系统消息队列中,然后调用osal_set_event为指定的任务设置系统事件

SYS_EVENT_MSG,所以在系统事件队列tasksEvents中为此任务增加了一个SYS_EVENT_MSG

系统事件。任务循环就可以检测到事件队列非空,就可以调用相应的任务处理函数了。这也是整个Zstack协议栈的执行流程。 SAPI_ProcessEvent是这个简单例子的事件处理任务,约line760

处,检测按键改变事件并调用按键处理函数 case KEY_CHANGE: zb_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys ); break; 如果是第一次运行应用,并且按下

HAL_KEY_SW_1则将此器件配置成协调器并且使用自动启

动: ……………… logicalType = ZG_DEVICETYPE_COORDINATOR; zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType); …………………… startOptions = ZCD_STARTOPT_AUTO_START; zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); zb_SystemReset();//

使用看门狗强制复位系统 如果应用不是初始化阶段就允许绑定,并且闪烁LED1,这个判断通过标志myAppState来指定。 zb_AllowBind( 0xFF ); HalLedSet( HAL_LED_1, HAL_LED_MODE_ON ); void zb_AllowBind ( uint8 timeout ) { osal_stop_timerEx(sapi_TaskID, ZB_ALLOW_BIND_TIMER); if ( timeout == 0 ) {//超时时间设为0好像是停止了绑定允许,不响应ZDO匹配描述请求 afSetMatch(sapi_epDesc.simpleDesc->EndPoint, FALSE); } else { afSetMatch(sapi_epDesc.simpleDesc->EndPoint, TRUE); //允许响应ZDO匹配描述请求 if ( timeout != 0xFF ) { if ( timeout > 64 ) { timeout = 64; }//最大的超时时间是64秒 osal_start_timerEx(sapi_TaskID, ZB_ALLOW_BIND_TIMER, timeout*1000); //为此任务启动系统时钟,相当于启动任务了,等待64S,如果一直没有绑定请求的话

会发送ZB_ALLOW_BIND_TIMER事件,指示绑定失败 } } return; } ZigBee

学习之

学习之学习之

学习之37——osalInitTasks()分析

分析分析

分析 此函数完成了任务ID的分配,以及所有任务的初始化,如果我们要加入自己的应用必须在

这里加入自己的任务初始化函数,以使系统可以自动为任务分配一个ID号。如果是想要自

己创建一个应用,那么必须首先实现两个东西,一是任务回调函数队列,另一个是系统任务

初始化及ID分配。任务队列是一个pTaskEventHandlerFn结构的数组,只要把每个任务相关

的回调函数按初始化队列中的顺序填到数组中就可以了,他们的实现一般是写在自己的应用

源码里面。介绍一个例子(SimpleApp):

const pTaskEventHandlerFn tasksArr[] = {

macEventLoop,

nwk_event_loop,

Hal_ProcessEvent,

#if defined( MT_TASK )

MT_ProcessEvent,

#endif

APS_event_loop,

ZDApp_event_loop,

SAPI_ProcessEvent

};

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++ );

SAPI_Init( taskID );

}

注意这里两个实体里面的顺序要一一对应!

macTaskInit( taskID++ ),nwk_init( taskID++ ),APS_Init( taskID++ )定义在mac_api.h,nwk.h,

APS.h中,但没有找到其实现,可能被打包在链接库文件里面了。

void Hal_Init( uint8 task_id )

{

/*将系统分配的任务的ID传给变量*/

Hal_TaskID = task_id;

}

//开始器件的启动【ZDApp.c】

void ZDApp_Init( byte task_id )

{

uint8 capabilities; //保存系统分配的任务ID

ZDAppTaskID = task_id;

//初始化ZDO全局的短地址,并配置成无效的一个地址

ZDAppNwkAddr.addrMode = Addr16Bit;

ZDAppNwkAddr.addr.shortAddr = INVALID_NODE_ADDR;

// ZDAppNwkAddr为一个地址数据结构:addrMode只有几种:

AddrNotPresent = 0,

AddrGroup = 1,

Addr16Bit = 2,

Addr64Bit = 3,

AddrBroadcast = 15

typedef struct

{

union

{

uint16 shortAddr;

ZLongAddr_t extAddr;

} addr;

byte addrMode;

} zAddrType_t;

(void)NLME_GetExtAddr(); //这是个ZstackAPI,返回指向此器件64位地址的指针

//检查"Hold Auto Start",如果启动的时候按着SW1,此函数就会设置设备的状态为:

DEV_HOLD

ZDAppCheckForHoldKey();

//初始化ZDO物件,并且设置设备

ZDO_Init();

//【ZDObject.c】

void ZDO_Init( void )

{

#if defined ( REFLECTOR )

//如果器件定义的是“反射器”则需要定义个路由的设备结构,REFLECTOR是一个编译选项,

如果定义了这个编译选项则使用“源绑定”,源绑定的定义有这样一句话In the Zigbee 2006

release,the binding mechanism is implemented in all devices and is called source binding(在

Zigbee2006中,绑定机制在所有的设备中实现,这就叫做源绑定),如果使用源绑定则绑表

是存放在源设备中的,这样就不用为找绑定入口而先向协调器提交绑定请求,但是方面又增

大了源节点的开销,因为要为保存绑定表而开辟一段静态内存。默认是不使用源绑定。

ZDO_EDBind = NULL;

#endif

//设置创建设备的类型

ZDODeviceSetup();

//根据设置的编译选项来调用不同的网络层管理函数

// ZDO_COORDINATOR:设备作为协调器

// SOFT_START:如果没有协调器则设备以协调器启动,否则以路由器启动

//这里面的几个函数都做成了静态库了看不到源码!看来TI的所谓开放也不是完全开放 static void ZDODeviceSetup( void )

{

#if defined( ZDO_COORDINATOR )

NLME_CoordinatorInit();

#endif

#if defined ( REFLECTOR )

#if defined ( ZDO_COORDINATOR )

APS_ReflectorInit( APS_REFLECTOR_PUBLIC );

#else

APS_ReflectorInit( APS_REFLECTOR_PRIVATE );

#endif

#endif

#if !defined( ZDO_COORDINATOR ) || defined( SOFT_START )

NLME_DeviceJoiningInit();

#endif

}

}

// 向AF层注册endpoint(终端)

afRegister( (endPointDesc_t *)&ZDApp_epDesc );

endPointDesc_t ZDApp_epDesc =

{

ZDO_EP,

//0,ZDO为终端0

&ZDAppTaskID,

//指向应用任务号

(SimpleDescriptionFormat_t *)NULL,

// 终端的简单描述,No Simple description for ZDO

(afNetworkLatencyReq_t)0 // No Network Latency req

};

#if defined( ZDO_USERDESC_RESPONSE )

ZDApp_InitUserDesc();

#endif // ZDO_USERDESC_RESPONSE

//从NIB中读取属性

NLME_GetRequest(nwkCapabilityInfo, 0, &capabilities);

//设置设备能处理的广播消息

NLME_SetBroadcastFilter( capabilities );

//根据设备状态启动设备,如果不是DEV_HOLD则启动ZDO设备,否则闪烁LED1

if ( devState != DEV_HOLD )

{

ZDOInitDevice( 0 );

//启动网络中的设备,在这个函数中会调用ZDApp_NetworkInit()来启动网络,并发送

ZDO_NETWORK_INIT事件,当ZDO事件循环ZDApp_event_loop()接收到这个事件后会执

行ZDO_StartDevice()来启动设备

} else

{

// Blink LED to indicate HOLD_START

HalLedBlink ( HAL_LED_4, 0, 50, 500 );

}

ZDApp_RegisterCBs();

//注册ZDO消息,只有注册了的消息才能以ZDO_CB_MSG消息的形式发送给指定的任务

} /* ZDO_Init() */

事件处理函数SAPI_ProcessEvent(),在任何地方只要调用osal_set_event()对这个处理函数对

应的任务ID设置了事件的话就会启动这个事件处理函数。

系统消息事件SYS_EVENT_MSG包含几个子类的消息(子类可以在文档【Z-Stack Sample

Applications (F8W-2006-0023).pdf】中找到),SYS_EVENT_MS由函数osal_msg_send()

【OSAL.C】发送,osal_msg_send()首先将传过来的任务ID加入到消息包中,然后再把这个

消息包通过osal_msg_enqueue加入到消息队列中,(osal_qHead是消息队列的头指针),然

后我们再来查找调用osal_msg_send()的函数,这样我们就可以知道系统消息

SYS_EVENT_MSG是在什么情况下如何发出的了。afDataConfirm()【AF.c】会调用

osal_msg_send(),并且构造的消息中包含事件AF_DATA_CONFIRM_CMD(此事件说明由

AF_DataRequest()初始化的数据已成功发送,若AF_DataRequest()的选项中要求有回应,则

此事件表示目标已收到数据)

void afDataConfirm( uint8 endPoint, uint8 transID, ZStatus_t status )

{

endPointDesc_t *epDesc;

afDataConfirm_t *msgPtr;

//每个终端都需要一个描述,如果没有则直接返回

epDesc = afFindEndPointDesc( endPoint );

if ( epDesc == NULL )

return;

………………

// Determine the incoming command type

msgPtr = (afDataConfirm_t *)osal_msg_allocate( sizeof(afDataConfirm_t) );

if ( msgPtr )

{

// Build the Data Confirm message

msgPtr->hdr.event = AF_DATA_CONFIRM_CMD;

//设置消息的事件为AF_DATA_CONFIRM_CMD

msgPtr->hdr.status = status;

msgPtr->endpoint = endPoint;

msgPtr->transID = transID;

//给指定任务发送消息,并且设置事件数组中SYS_EVENT_MS事件准备好

osal_msg_send( *(epDesc->task_id), (byte *)msgPtr );

}

} 换个思路看看SAPI的事件处理函数当接收到数据的时候干了些什么,然后找出接收数据的

函数!从文档中我们得知当采集器接收到数据后就会发送到串口,好那么就找出发送串口数

据的处理函数:SAPI_ReceiveDataIndication(),当收到系统消息中的事件为

AF_INCOMING_MSG_CMD事调用此函数,afBuildMSGIncoming()会构造基于此事件的消

息,而afIncomingData将会调用此函数, ZigBee

学习之

学习之学习之

学习之38-初步理解

初步理解初步理解

初步理解Home Automation Profile Zigbee将应用划分为不同的域,每个应用域都有自己的Profile,ZigBee Profile为这个应用域提

供标准的接口和设备定义,使得不同生产商生产的针对同一应用领域的ZigBee设备之间能互通。

下面是文档【075366r01ZB_AFG-ZigBee_Cluster_Library_Public_download_version.pdf】中的一些

重点。 为了避免与

WiFi的冲突,推荐HA使用通道11,14,15,19,20,24,25 启动属性集

启动属性集启动属性集

启动属性集(

((

(SAS)

))

) 为了确保互操作性,所有ZigBee HA设备都应该实现兼容的启动属性集(SAS) 启动参数

启动参数启动参数

启动参数:

::

: 短地址:0xFFFF E PANiD:0x0000000000000000 PAN ID:0xFFFF 通道掩码:使用通道26 协议栈版本:0x02(2006及其更高版本) 堆栈剖面:1(2006)或2(2007) 启动控制;3:通过关联加入网络,0:设备认为自己是ExtendedPANId属性所指网络的一部分,

不执行任何加入和重加入操作 信任中心地址:

0x0000 加入参数

加入参数加入参数

加入参数:

::

: 扫描尝试:3次,如果是新加入的节点会则会扫描所有的通道3次,然后找到允许加入的网络。

如果是在使用中的节点则扫描三次查找原来的PAN加入。 扫描间隔:

1秒 重加入间隔:60秒或更短,决定设备发现自己离线后多久将进行重加入 最大重加入间隔:15分钟 终端设备参数

终端设备参数终端设备参数

终端设备参数:

::

: 间接查询速率:控制终端设备向父设备进行查询的速率,由堆栈剖面设置,建议设为60秒 绑定参数

绑定参数绑定参数

绑定参数:

::

: 终端设备绑定超时:60秒 设备描述

设备描述设备描述

设备描述:

::

: 设备由其所处的最终应用领域组织在一起,和此规范相兼容的产品至少要实现规范中众多设备描

述中的一个,而且应该包含应用中实现的所有相关设备的描述,比如一个产品实现了调光和灯光

传感应用,那么就应该支持可调灯和灯传感设备描述。 ZCL HA Profile

利用了ZigBee Cluster Library中定义的簇集(clusters)。ZCL为簇集报告属性值的改

变提供了一种机制,并且提供了配置报告参数的命令。默认设置为最大的报告间隔0x0000,最

小的报告间隔需要≥0x0001,如果设置为非0值需要≥0x003C。创建一个新设备描述是在规范中

增加新簇集的首选方案。 一般设备

一般设备一般设备

一般设备 On/Off Switch 此设备可以给设备发送开、关、切换命令来控制设备的开关和状态转换,此设备只在其他此类更

具体的设备(如On/Off Light Switch)不存在时使用。 Level Control Switch 此设备可以给设备发送开、关、切换命令来控制设备的开关和状态转换,并且能控制这些设备的等级特性,此设备只在其他此类更具体的设备(如On/Off Light Switch)不存在时使用。 On/Off Output 此设备可以被用来做开、关状态的切换。此设备只有在更具体的设备(如

Basic Light)存在时才

能使用。 Level Controllable Output 此设备可以被用来做开、关切换和输出等级调节。此设备只有在更具体的设备(如

Dimmer Switch)

存在时才能使用。 Scene Selectoral 此设备可以被用设置和选择其他设备上的场景。 Configuration Tool 此设备可以被用配置其他设备。此设备用来对新安装的设备进行配置并在其后进行优化。 Remote Control 此设备可以被用来控制和监控其他设备。 Mains Power Outlet 此设备可以被用来做开、关状态的切换。此设备用来控制主电源输出。 灯光设备

灯光设备灯光设备

灯光设备 On/Off Light On/Off Light设备即是具有开关功能的灯 Dimmable Output 此设备是一个具有开关和亮度可调功能的灯。 ……………… ZigBee学习之

学习之学习之

学习之39-Home Automation Profile2 TI Home Automation Profile样例

Sample Light Application

此例用开关簇集命令来开关LED4,或者用ZCL写入命令将IdentifyTime属性设为非0值来

将设备置于认证模式(如闪烁LED4)。在文档中列出了这个示例程序所支持的属性。如:?

The On/Off cluster attributes:

o zclSampleLight_OnOff现在在考虑一点,就是这些属性是怎么支持的,这些属性又是怎么

确定的,可能在程序中应该会有所体现,等后面分析程序时再看一下。要理解这个首先需要

有个概念,就是多个属性组成一个簇集(簇集是属性和命令的集合!),而簇集又是组成应用

剖面的必备条件,在规范中对不同的应用剖面其客户端和服务器端必须或可选实现的簇集都

有规定,在簇集库规范中对每个簇集中必须或可选实现的属性又有相应的规范。可参考文档:

【075366r01ZB_AFG-ZigBee_Cluster_Library_Public_download_version.pdf】和

【075367r01ZB_AFG-Home_Automation_Profile_for_Public_Download.pdf】做初步理解。

按键动作:SW2:通过ZDApp_SendEndDeviceBindReq发送绑定。

Sample Switch Application APS_event_loop,

ZDApp_event_loop,

#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )

ZDNwkMgr_event_loop,

#endif

zcl_event_loop,

//ZCL事件循环

zclSampleLight_event_loop

//这个是ZCL应用事件处理循环是我们自己针对自己的应用构建的

};

//此函数中事件处理循环功能函数的顺序必须和系统初始化任务列表中的顺序相一致,也就

是一个任务初始化函数就对应这个一个事件处理循环

至此OSAL_SimpleLight.c模块就完成了自己的任务:对系统进行初始化设定,下面我们深

入到初始化函数和事件处理循环去看一下。在此例中zcl_event_loop仅接收一个事件

AF_INCOMING_MSG_CMD,处理也仅仅是找到对应的终端和根据是否要发送应答来发送

或者不发送应答。

【zcl_samplelight.c】应用初始化函数:

void zclSampleLight_Init( byte task_id )

{

zclSampleLight_TaskID = task_id;

//分配一个任务ID

// Set destination address to indirect

//zclSampleLight_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;

//zclSampleLight_DstAddr.endPoint = 0;

//zclSampleLight_DstAddr.addr.shortAddr = 0;

// This app is part of the Home Automation Profile

//注册HA剖面的简单描述和簇ID转换表

zclHA_Init( &zclSampleLight_SimpleDesc );

【zcl_ha.c】

void zclHA_Init( SimpleDescriptionFormat_t *simpleDesc )

{

endPointDesc_t *epDesc;

//如果没有簇ID转换表则需要构建一个转换表其中保存着真是簇ID和逻辑簇ID之间的对

// Set up the Real Cluster ID to Logical Cluster ID conversion

if ( !zclHA_RegisteredClusterList )

{

zcl_registerClusterConvertTable( ZCL_HA_PROFILE_ID,

(sizeof( ha_ClusterConvertTable ) / sizeof( zclConvertClusterRec_t )) ,

(GENERIC zclConvertClusterRec_t *)ha_ClusterConvertTable );

zclHA_RegisteredClusterList = TRUE;

}

//注册应用的终端描述,需要为终端描述分配内存

// Register the application's endpoint descriptor // - This memory is allocated and never freed.

epDesc = osal_mem_alloc( sizeof ( endPointDesc_t ) );

if ( epDesc )

{

// Fill out the endpoint description.

epDesc->endPoint = simpleDesc->EndPoint;

epDesc->task_id = &zcl_TaskID; // all messages get sent to ZCL first

epDesc->simpleDesc = simpleDesc;

epDesc->latencyReq = noLatencyReqs;

//所有的终端都要注册到af层上。按照这样的话,如果在一个节点上要实现多个终端的话,

是不是就需要调用几次zclHA_Init()来注册所有终端呢?还是说可以在函数外面再通过多次

调用afRegister()来注册所有的终端?不过可以肯定这里注册的是设备简单描述里面的终端!

这个简单描述是网络用来设别设备用的,如果仅要说明节点上的一个终端可以只定义终端描

述就可以了!那么这个简单描述到底和终端描述在一个设备上的区别在哪里呢?一个节点设

备肯定是需要一个简单描述,而一个终端也必须要有一个终端描述,那如果一个节点设备只

有一个终端的话是不是简单描述中的的终端号和终端描述中的终端号要一致的呢?【看一下

传递进来的简单描述在文件zcl_samplelight_data.c中:

SimpleDescriptionFormat_t zclSampleLight_SimpleDesc =

{

SAMPLELIGHT_ENDPOINT, //设置在节点号10上,即此宏就是10

ZCL_HA_PROFILE_ID, //此设备上支持的剖面:0x0104为联盟规定的HA剖面ID;

ZCL_HA_DEVICEID_DIMMABLE_LIGHT, //可调亮度灯的设备ID:0x0101,规范中规定

SAMPLELIGHT_DEVICE_VERSION, //应用设备上使用的版本,可自设,此例中设为了0

SAMPLELIGHT_FLAGS, //应用标志,此例中设为了0,此位为简单描述中的保留位

ZCLSAMPLELIGHT_MAX_INCLUSTERS, //输入簇集数目,此例设为了5,在samplelight

应用中使用了5个输入簇集:

ZCL_HA_CLUSTER_ID_GEN_BASIC,

ZCL_HA_CLUSTER_ID_GEN_SCENES,

ZCL_HA_CLUSTER_ID_GEN_GROUPS,

ZCL_HA_CLUSTER_ID_GEN_ON_OFF,

ZCL_HA_CLUSTER_ID_GEN_LEVEL_CONTROL

(cId_t *)zclSampleLight_InClusterList, //输入簇集列表,为一个包含所有输入簇的数组

ZCLSAMPLELIGHT_MAX_OUTCLUSTERS, //输出簇集数目

(cId_t *)zclSampleLight_OutClusterList //输出簇集列表,为一个包含所有输出簇的数组

};以上所有联盟或者是规范中规定的ID都能在文档

【075367r01ZB_AFG-Home_Automation_Profile_for_Public_Download.pdf】中找到】

// Register the endpoint description with the AF

afRegister( epDesc );

}

}

//注册命令回调函数(将要注册的命令回调函数和终端关联起来然后插入到链表中),第二

个参数是一个一般簇命令函调函数的列表数据结构,我们只需要填充我们要用到的命令就可

以了。其结构定义在【zcl_general.h】:

typedef struct {

zclGCB_BasicReset_t pfnBasicReset; // Basic Cluster Reset command

zclGCB_Identify_t pfnIdentify; // Identify command

zclGCB_IdentifyQueryRsp_t pfnIdentifyQueryRsp; // Identify Query Response command

zclGCB_OnOff_t pfnOnOff; // On/Off cluster commands

zclGCB_LevelControlMoveToLevel_t pfnLevelControlMoveToLevel; // Level Control Move to

Level command

zclGCB_LevelControlMove_t pfnLevelControlMove; // Level Control Move command

zclGCB_LevelControlStep_t pfnLevelControlStep; // Level Control Step command

zclGCB_LevelControlStop_t pfnLevelControlStop; // Level Control Stop command

zclGCB_GroupRsp_t pfnGroupRsp; // Group Response commands

zclGCB_SceneStoreReq_t pfnSceneStoreReq; // Scene Store Request command

zclGCB_SceneRecallReq_t pfnSceneRecallReq; // Scene Recall Request command

zclGCB_SceneRsp_t pfnSceneRsp; // Scene Response command

zclGCB_Alarm_t pfnAlarm; // Alarm (Response) commands

zclGCB_Location_t pfnLocation; // RSSI Location command

zclGCB_LocationRsp_t pfnLocationRsp; // RSSI Location Response command

} zclGeneral_AppCallbacks_t;

在此例中主要实现开关的命令回调函数,其命令回调函数表定义在【zcl_samplelight.c】中:

static zclGeneral_AppCallbacks_t zclSampleLight_CmdCallbacks =

{

zclSampleLight_BasicResetCB, // Basic Cluster Reset command

zclSampleLight_IdentifyCB, // Identify command

zclSampleLight_IdentifyQueryRspCB, // Identify Query Response command

zclSampleLight_OnOffCB, // On/Off cluster command

NULL, // Level Control Move to Level command

NULL, // Level Control Move command

NULL, // Level Control Step command

NULL, // Group Response commands

NULL, // Scene Store Request command

NULL, // Scene Recall Request command

NULL, // Scene Response command

NULL, // Alarm (Response) command

NULL, // RSSI Location commands

NULL, // RSSI Location Response commands

};可见仅仅填充了想要实现的部分,其他均填充为NULL

// Register the ZCL General Cluster Library callback functions

zclGeneral_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT,

&zclSampleLight_CmdCallbacks );

//为终端注册属性,注意属性列表中属性ID必须升序排列,否则发现响应命令将不能正确

获得属性信息。

//zclSampleLight_Attrs定义在【zcl_samplelight_data.c】中

// Register the application's attribute list

zcl_registerAttrList( SAMPLELIGHT_ENDPOINT, SAMPLELIGHT_MAX_ATTRIBUTES, zclSampleLight_Attrs );

//为任务注册按键事件就不用解释了吧,前面好几个地方都有提到过来

// Register for all key events - This app will handle all key events

RegisterForKeys( zclSampleLight_TaskID );

//注册一个测试终端,此终端ID为20,和前面设备简单描述中的终端ID不同了!

// Register for a test endpoint

afRegister( &sampleLight_TestEp );

}

下面我们进入zclSampleLight_event_loop看看这个应用的事件处理循环是怎么做的。

uint16 zclSampleLight_event_loop( uint8 task_id, uint16 events )

{

afIncomingMSGPacket_t *MSGpkt;

if ( events & SYS_EVENT_MSG )

{

while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( zclSampleLight_TaskID )) )

{//得到系统消息,从应用任务中接收消息

switch ( MSGpkt->hdr.event )

{

case KEY_CHANGE:

//处理按键事件,按下按键2时就发送绑定请求

zclSampleLight_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t

*)MSGpkt)->keys );

break;

default:

break;

}

// Release the memory

osal_msg_deallocate( (uint8 *)MSGpkt );

}

// return unprocessed events

return (events ^ SYS_EVENT_MSG);

}

if ( events & SAMPLELIGHT_IDENTIFY_TIMEOUT_EVT )

{

if ( zclSampleLight_IdentifyTime > 0 )

zclSampleLight_IdentifyTime--;

zclSampleLight_ProcessIdentifyTimeChange();

return ( events ^ SAMPLELIGHT_IDENTIFY_TIMEOUT_EVT );

}

// Discard unknown events

return 0;

} //现在问题来了,如果对发发送了命令,那么命令是如果传递过来,如何能够产生效果呢?

通篇查看【zcl_samplelight.c】发现只有在函数zclSampleLight_OnOffCB()中出现了对LED

的操作代码,其行为也符合这个函数的定义:命令回调函数,当接收到一个命令时就会调用

此函数,此函数位于命令回调表zclSampleLight_CmdCallbacks中,那么我们只要找到怎么

样进入这个命令回调表然后定位到这个回调函数就可以了,前面的分析可得命令回调表是通

过zclGeneral_RegisterCmdCallbacks()函数注册给终端的,在这个函数中只存在一个与外部发

生联系的变量zclGenCBs,此变量为静态变量,定义在【zcl_general.c】中。再回到OSAL,

当收到其他设备发过来的数据时会产生AF_INCOMING_MSG_CMD消息,zcl_event_loop

调用zclProcessMessageMSG()来处理消息 ZigBee

学习之

学习之学习之

学习之40-Home Automation Profile3 static void zclProcessMessageMSG( afIncomingMSGPacket_t *pkt )

{

…………

//首先检查命令域的数据长度,如果没有数据则直接退出

if ( pkt->cmd.DataLength == 0 )

return; // Error, ignore the message

…………

//然后检查消息的目的终端是否存在于节点上,若不是发送给此节点的则退出

epDesc = afFindEndPointDesc( pkt->endPoint );

if ( epDesc == NULL )

return; // Error, ignore the message

//将簇ID转换到逻辑簇ID,如果为0xFFFF则退出

logicalClusterID = zclConvertClusterID( pkt->clusterId, epDesc->simpleDesc->AppProfId,

TRUE );

if ( logicalClusterID == ZCL_INVALID_CLUSTER_ID )

return; // Error, ignore the message

//检查设备是否可操作,即检查DEVICE_ENABLED属性

if ( zcl_DeviceOperational( pkt->endPoint, pkt->clusterId,

inMsg.hdr.fc.type, inMsg.hdr.commandID ) == FALSE )

{

return; // Error, ignore the message

}

//一下是处理和解析收到的命令

if ( zcl_ProfileCmd( inMsg.hdr.fc.type ) )

{//收到的是针对剖面的命令比如读写属性,报告属性,身份认证等

if ( inMsg.hdr.fc.manuSpecific )

{

// We don't support any manufacturer specific command

//不支持生产商的特定命令

status = ZCL_STATUS_UNSUP_MANU_GENERAL_COMMAND;

}

else if ( ( inMsg.hdr.commandID <= ZCL_CMD_MAX ) && ( zclCmdTable[inMsg.hdr.commandID].pfnParseInProfile != NULL ) )

{//接收到的命令ID小于最大命令ID,并且命令表中此命令定义了解析函数,则解析并处理

此命令

zclParseCmd_t parseCmd;

parseCmd.endpoint = pkt->endPoint;

parseCmd.dataLen = inMsg.pDataLen;

parseCmd.pData = inMsg.pData;

// Parse the command, remember that the return value is a pointer to allocated memory

inMsg.attrCmd = zclParseCmd( inMsg.hdr.commandID, &parseCmd );

if ( (inMsg.attrCmd != NULL) && (zclCmdTable[inMsg.hdr.commandID].pfnProcessInProfile !=

NULL) )

{

// Process the command

if ( zclProcessCmd( inMsg.hdr.commandID, &inMsg ) == FALSE )

{//处理此命令不成功

// Couldn't find attribute in the table.

}

………………

}

else

{//处理此簇ID的特殊命令

// Nope, must be specific to the cluster ID

//为实际的簇ID查找合适的插头

// Find the appropriate plugin

pInPlugin = zclFindPlugin( pkt->clusterId, epDesc->simpleDesc->AppProfId );

//在这个查找插头的函数中关联了一个静态变量plugins,这个变量中指明了插头的范围以及

处理输入消息的函数,其数据结构定义为:【zcl.c】

typedef struct zclLibPlugin

{

struct zclLibPlugin *next;

uint16 startLogCluster; // starting logical cluster ID

uint16 endLogCluster; // ending logical cluster ID

zclInHdlr_t pfnIncomingHdlr; // function to handle incoming message

} zclLibPlugin_t;

//在zclSampleLight_Init()中我们通过调用zclGeneral_RegisterCmdCallbacks()来注册了插头:

//…………

// zcl_registerPlugin( ZCL_GEN_LOGICAL_CLUSTER_ID_BASIC,

// ZCL_GEN_LOGICAL_CLUSTER_ID_LOCATION,

// zclGeneral_HdlIncoming );

//…………

ZStatus_t zcl_registerPlugin( uint16 startLogCluster,

uint16 endLogCluster, zclInHdlr_t pfnIncomingHdlr )

{

………… // Find spot in list

//在这个函数中我们对静态变量plugins进行了填充

if ( plugins == NULL )

{

plugins = pNewItem;

}

else

{

// Look for end of list

pLoop = plugins;

while ( pLoop->next != NULL )

pLoop = pLoop->next;

// Put new item at end of list

pLoop->next = pNewItem;

}

return ( ZSuccess );

}

//对应的回调函数zclGeneral_HdlIncoming()首先检查接收的消息是否是簇的特定命令,然后

调用zclGeneral_HdlInSpecificCommands()来处理不同的命令:

static ZStatus_t zclGeneral_HdlInSpecificCommands( zclIncoming_t *pInMsg, uint16

logicalClusterID )

{

ZStatus_t stat;

switch ( logicalClusterID )

{//逻辑簇ID存在是进行判断的基础

………………

#ifdef ZCL_ON_OFF

//调用处理开关簇的处理函数

case ZCL_GEN_LOGICAL_CLUSTER_ID_ON_OFF:

stat = zclGeneral_ProcessInOnOff( pInMsg );

static ZStatus_t zclGeneral_ProcessInOnOff( zclIncoming_t *pInMsg )

{

zclGeneral_AppCallbacks_t *pCBs;

if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) )

{//只处理服务器方向,因为对于开关簇来说,灯是作为服务端,被其他设备控制的

if ( pInMsg->hdr.commandID > COMMAND_TOGGLE )

return ( ZFailure ); // Error ignore the command

pCBs = zclGeneral_FindCallbacks( pInMsg->msg->endPoint );

//通过终端号来查找相应的命令回调函数,在这个查找命令回调函数的函数中又调用了一个

静态变量zclGenCBs,这个变量保存着终端与命令回调函数的对应,其数据结构如下:

typedef struct zclGenCBRec

{

struct zclGenCBRec *next;

uint8 endpoint; // Used to link it into the endpoint descriptor zclGeneral_AppCallbacks_t *CBs; // Pointer to Callback function

} zclGenCBRec_t;

//这个变量在zcl应用任务初始化注册回调函数时也同时注册了:

ZStatus_t zclGeneral_RegisterCmdCallbacks( uint8 endpoint, zclGeneral_AppCallbacks_t

*callbacks )

{

………………

// Fill in the new profile list

pNewItem = osal_mem_alloc( sizeof( zclGenCBRec_t ) );

if ( pNewItem == NULL )

return (ZMemError);

pNewItem->next = (zclGenCBRec_t *)NULL;

pNewItem->endpoint = endpoint;

pNewItem->CBs = callbacks;

// Find spot in list

if ( zclGenCBs == NULL )

{

zclGenCBs = pNewItem;

}

else

{

// Look for end of list

pLoop = zclGenCBs;

while ( pLoop->next != NULL )

pLoop = pLoop->next;

// Put new item at end of list

pLoop->next = pNewItem;

}

return ( ZSuccess );

}

//这些注册的命令回调函数都是属于一个终端的,一个终端中有多个命令回调函数,那么如

何区分到底应该调用哪个回调函数呢?接着往下看:

if ( pCBs && pCBs->pfnOnOff )

pCBs->pfnOnOff( pInMsg->hdr.commandID );

//上面两句进行判断和调用,如果开关簇的命令回调函数存在则进行调用。所以在对回调函

数数据结构zclGeneral_AppCallbacks_t进行填充时需要把用到的簇的回调函数填充,没有用

到的填为NULL,而且要安全按照定义中簇的顺序来填充。

}

// no Client command

return ( ZSuccess );

}

break;

#endif // ZCL_ON_OFF

……………… default:

stat = ZFailure;

break;

}

return ( stat );

}

if ( pInPlugin && pInPlugin->pfnIncomingHdlr )

{

zclInHdlrMsg_t inHdlrMsg;

inHdlrMsg.msg = &inMsg;

inHdlrMsg.logicalClusterID = logicalClusterID;

status = pInPlugin->pfnIncomingHdlr( &inHdlrMsg );

if ( status == ZCL_STATUS_CMD_HAS_RSP )

return; // We're done

}

if ( status != ZSuccess )

{

// Unsupported message

if ( inMsg.hdr.fc.manuSpecific )

status = ZCL_STATUS_UNSUP_MANU_CLUSTER_COMMAND;

else

status = ZCL_STATUS_UNSUP_CLUSTER_COMMAND;

}

}

……………………

}

//总结一下:应用层收到数据——ZCL事件处理循环zcl_event_loop收到系统事件消息

AF_INCOMING_MSG_CMD——调用消息处理函数zclProcessMessageMSG()——根据命令

中帧控制域指示的命令类型分别进入剖面命令或簇命令处理,首先在其数据结构中查找是否

有相接收到的命令想匹配的命令项『剖面命令通过zclCmdTable查找相应的回调函数,调用

回调函数处理;簇命令通过zclInHdlrMsg_t查找相应的插头』——在zclCmdTable或者plugins

中找到对应的项——检查相应的项中是否有回调函数——调用相应项中的回调函数对设备

进行操作——如果需要发送应答则发送应答

ZigBee学习之

学习之学习之

学习之41-SimpleSwitch 再来看控制端

再来看控制端再来看控制端

再来看控制端SimpleSwitch 【OSAL_SampleSw.c】构造系统初始化及其事件循环数据结构。任务初始化队列osalInitTasks

中把最后的任务改为开关的任务初始化函数:zclSampleSw_Init( taskID );在事件循环tasksArr

中加入开关节点的事件循环函数:zclSampleSw_event_loop。zclSampleSw_Init()实现在

【zcl_samplesw.c】中,同样是HA剖面初始化,注册回调函数,应用属性列表,按键等。 void zclSampleSw_Init( byte task_id ) { zclSampleSw_TaskID = task_id; //设置目的地址 // Set destination address to indirect zclSampleSw_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent; zclSampleSw_DstAddr.endPoint = 0; zclSampleSw_DstAddr.addr.shortAddr = 0; // This app is part of the Home Automation Profile zclHA_Init( &zclSampleSw_SimpleDesc ); // Register the ZCL General Cluster Library callback functions zclGeneral_RegisterCmdCallbacks( SAMPLESW_ENDPOINT, &zclSampleSw_CmdCallbacks ); // Register the application's attribute list zcl_registerAttrList( SAMPLESW_ENDPOINT, SAMPLESW_MAX_ATTRIBUTES,

zclSampleSw_Attrs ); // Register for all key events - This app will handle all key events RegisterForKeys( zclSampleSw_TaskID ); // Register for a test endpoint afRegister( &sampleSw_TestEp ); //

注册两个消息。通过ZDO_RegisterForZDOMsg()注册的消息都能够被应用接收,调用此函数请

求空中消息,消息的副本将会以系统消息的形式发送到任务,然后任务再解析消息。当注册了一

个特定的消息,然后接收到消息(OTA)后,消息将作为ZDO_CB_MSG被发送到应用/任务,

消息体(zdoIncomingMsg_t)将包含OTA消息。 ZDO_RegisterForZDOMsg( zclSampleSw_TaskID, End_Device_Bind_rsp ); ZDO_RegisterForZDOMsg( zclSampleSw_TaskID, Match_Desc_rsp ); } 事件处理循环: uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ) { afIncomingMSGPacket_t *MSGpkt; if ( events & SYS_EVENT_MSG ) { while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( zclSampleSw_TaskID )) ) { switch ( MSGpkt->hdr.event ) { case ZDO_CB_MSG: //

处理在初始化中注册的那两个消息 zclSampleSw_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt ); break; case KEY_CHANGE: //处理按键事件 zclSampleSw_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break; default: break; } //消息处理完后要记得释放掉所有空间 // Release the memory osal_msg_deallocate( (uint8 *)MSGpkt ); } ……………… } //按键处理函数是一个关键,在这个函数里面将会出现命令的发送: static void zclSampleSw_HandleKeys( byte shift, byte keys ) { zAddrType_t dstAddr; if ( keys & HAL_KEY_SW_1 ) { // Using this as the "Light Switch" //发送切换命令,调用zcl_SendCommand()来发送特定的命令,目的地址在初始化的时候赋了值,

给予的是一个不定的地址,所以依靠绑定表来查找。这些发送命令的函数定义在【zcl_general.h】 zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &zclSampleSw_DstAddr, false, 0 ); } if ( keys & HAL_KEY_SW_2 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); //

关闭LED4并发送绑定请求,这里的绑定请求参数中的输入输出簇只有和要绑定的设备相互补

时才能绑定成功。开关设备上没有输入簇,输出簇为:ZCL_HA_CLUSTER_ID_GEN_ON_OFF,

那么就只能和输入簇列表中为ZCL_HA_CLUSTER_ID_GEN_ON_OFF的终端发生绑定。在灯设

备上输入簇列表为: static cId_t bindingInClusters[ZCLSAMPLELIGHT_BINDINGLIST] = { ZCL_HA_CLUSTER_ID_GEN_ON_OFF, ZCL_HA_CLUSTER_ID_GEN_LEVEL_CONTROL };

其中存在ZCL_HA_CLUSTER_ID_GEN_ON_OFF,那么这两个设备只要在规定时间内向协调

器发送ZDP_EndDeviceBindReq()就能绑定了。这样理解应该没错吧 呵呵 // Initiate an End Device Bind Request, this bind request will // only use a cluster list that is important to binding. dstAddr.addrMode = afAddr16Bit; dstAddr.addr.shortAddr = 0; // Coordinator makes the match ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(), SAMPLESW_ENDPOINT, ZCL_HA_PROFILE_ID, 0, NULL, // No incoming clusters to bind ZCLSAMPLESW_BINDINGLIST, bindingOutClusters, TRUE ); } if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); // Initiate a Match Description Request (Service Discovery) dstAddr.addrMode = AddrBroadcast; dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR; ZDP_MatchDescReq( &dstAddr, NWK_BROADCAST_SHORTADDR, ZCL_HA_PROFILE_ID, ZCLSAMPLESW_BINDINGLIST, bindingOutClusters, 0, NULL, // No incoming clusters to bind FALSE ); } } ZigBee学习之

学习之学习之

学习之42——协议栈中的串口操作

协议栈中的串口操作协议栈中的串口操作

协议栈中的串口操作

 如果要使用协议栈中提供的串口,则需要定义

HAL_UART和HAL_UART TRUE

【hal_board_cfg.h】。首先初始化串口,在主函数中调用HalDriverInit()时,在函数中初始化

串口,主要是配置管脚和DMA通道。然后在osal_start_system()开始系统后,会调用

Hal_ProcessPoll()来读取时间和串口,

void Hal_ProcessPoll ()

{

HalTimerTick();

#if (defined HAL_UART) && (HAL_UART == TRUE)

HalUARTPoll();

#endif

}

//来看下串口poll函数,我们只看UART0的,因为我的开发板使用这个串口

void HalUARTPoll( void )

{

#if ( HAL_UART_0_ENABLE | HAL_UART_1_ENABLE )

static uint8 tickShdw;

uartCfg_t *cfg;

uint8 tick;

#if HAL_UART_0_ENABLE

//当发生串口接收中断时cfg0就会改变,如果串口没有数据输入cfg0为空,当接收到数据

时cfg0将在串口中断服务程序中被改变

if ( cfg0 )

{

cfg = cfg0; }

#endif

// Use the LSB of the sleep timer (ST0 must be read first anyway).

//系统上电后,睡眠定时器就会自动启动做自增计数ST0即睡眠定时器启动到现在计算值的

最低8位

tick = ST0 - tickShdw;

tickShdw = ST0;

//要注意接下来的是个循环

do

{

if ( cfg->txTick > tick )

{

cfg->txTick -= tick;

}

else

{

cfg->txTick = 0;

}

if ( cfg->rxTick > tick )

{

cfg->rxTick -= tick;

}

else

{

cfg->rxTick = 0;

}

//是使用DMA方式还是使用中断方式

#if HAL_UART_ISR

#if HAL_UART_DMA

if ( cfg->flag & UART_CFG_DMA )

{

pollDMA( cfg );

}

else

#endif

{

pollISR( cfg );

//中断方式

static void pollISR( uartCfg_t *cfg )

{

//如果串口没有接收到数据,也就是说没有发生过串口接收中断,那么cfg应为是为空的,

则cnt=0

//如果发生了串口中断,则cnt计算出串口缓存中还有多少数据没有读出,这个缓存并不是

硬件寄存器的缓存,而是程序中开辟一段空间 uint8 cnt = UART_RX_AVAIL( cfg );

if ( !(cfg->flag & UART_CFG_RXF) )

{

//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡

眠定时器来控制),而且需要把已经读出的数据数目减去!

// If anything received, reset the Rx idle timer.

if ( cfg->rxCnt != cnt )

{

cfg->rxTick = HAL_UART_RX_IDLE;

cfg->rxCnt = cnt;

}

/* It is necessary to stop Rx flow in advance of a full Rx buffer because

* bytes can keep coming while sending H/W fifo flushes.

*/

//当接收缓存超过安全界限的时候停止RX流

if ( cfg->rxCnt >= (cfg->rxMax - SAFE_RX_MIN) )

{

RX_STOP_FLOW( cfg );

}

//关于安全界限,在程序中有下面一段:

/* Need to leave enough of the Rx buffer free to handle the incoming bytes

* after asserting flow control, but before the transmitter has obeyed it.

* At the max expected baud rate of 115.2k, 16 bytes will only take ~1.3 msecs,

* but at the min expected baud rate of 38.4k, they could take ~4.2 msecs.

* SAFE_RX_MIN and DMA_RX_DLY must both be consistent according to

* the min & max expected baud rate.

*/

//如果声明了流控制,为保证数据的正确接收需要在RX缓存区中预留出足够的空间。CC2430

可以使用的最大串口波特率为115.2k。这个安全界限的数字跟使用的波特率还有串口tick

有关。

#if !defined( SAFE_RX_MIN )

#define SAFE_RX_MIN 48 // bytes - max expected per poll @ 115.2k

// 16 bytes @ 38.4 kBaud -> 4.16 msecs -> 138 32-kHz ticks.

#define DMA_RX_DLY 140

// 2 bytes @ 38.4 kBaud -> 0.52 msecs -> 17 32-kHz ticks.

#define DMA_TX_DLY 20

#endif

//超时计数利用的是睡眠定时器,而睡眠定时器是以32Khz时钟计数的,所以1微妙约为33

个计数值(如果使用外部32.768KHz晶振)

// The timeout tick is at 32-kHz, so multiply msecs by 33.

#define RX_MSECS_TO_TICKS 33

//超时时间设为传输一个字节的时间,之所以是6而不是8我觉得是排除掉串口协议中的停

止位和起始位的结果

// The timeout only supports 1 byte. #if !defined( HAL_UART_RX_IDLE )

#define HAL_UART_RX_IDLE (6 * RX_MSECS_TO_TICKS)

#endif

}

}

}

#elif HAL_UART_DMA

pollDMA( cfg );

#endif

/* The following logic makes continuous callbacks on any eligible flag

* until the condition corresponding to the flag is rectified.

* So even if new data is not received, continuous callbacks are made.

*/

if ( cfg->rxHead != cfg->rxTail )

{

//如果接收缓存中有数据,当接收数据时rxHead会增计数,当读取数据时rxTail会增计数,

两个标志的初始值都为0,所以这两个标志的差值就指示了缓存中有多少的数据

uint8 evt;

if ( cfg->rxHead >= (cfg->rxMax - SAFE_RX_MIN) )

{

//已保存的数据已经超过了安全界限,发送接收满事件

evt = HAL_UART_RX_FULL;

}

else if ( cfg->rxHigh && (cfg->rxHead >= cfg->rxHigh) )

{

evt = HAL_UART_RX_ABOUT_FULL;

}

else if ( cfg->rxTick == 0 )

{

//超时事件

evt = HAL_UART_RX_TIMEOUT;

}

else

{

evt = 0;

}

//如果发生事件,并且配置了回调函数则调用回调函数

if ( evt && cfg->rxCB )

{

cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );

//(cfg->flag & UART_CFG_U1F)!=0)判读是那个串口,如果是串口1则为1,否则为0

}

}

//下面的没看懂,下面的判断好像是没有用一样,不管是哪个条件都break了! if ( cfg == cfg0 )

{

break;

}

else

break;

} while ( TRUE );

#else

return;

#endif

}

//在来看一下串口中断,就是将U0DBUF的数据存放到cfg0->rxBuf里面,并且更改rxHead

标志

HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )

{

cfg0->rxBuf[cfg0->rxHead] = U0DBUF;

if ( cfg0->rxHead == cfg0->rxMax )

{

cfg0->rxHead = 0;

}

else

{

cfg0->rxHead++;

}

}

//cfg和回调函数在HalUARTOpen (SERIAL_APP_PORT, &uartConfig)中配置


  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值