zigbee开发.docx
《zigbee开发.docx》由会员分享,可在线阅读,更多相关《zigbee开发.docx(37页珍藏版)》请在冰豆网上搜索。
zigbee开发
Zigbee学习过程
1前言
学习zigbee之前需要熟悉掌握C语言的高级编程,而不是简单的普通的C语言编程。
做zigbee开发,需要相当熟悉掌握c语言,并且有c语言大程序的制作经验,不然非得吃不少苦头不可。
我本身并没有大程序的开发经验,导致我费了很大力气,一方面去补习c语言,一方面慢慢适应zigbee的开发,结果导致周博士主掌zigbee程序开发,我只负责开发C4驱动……。
不服气也得看自己实力。
至于C51是否够用,答案是肯定的也是否定的。
在刚开始接触zigbee时,C51是完全够用的,因为TI最初的开发板C2530就是基于C51的,所以够用;但是如果涉及到深层次的zigbee开发,就要上升到ARM的级别,因此C51就不够使了。
2轮询机制
Osal.c文件里有个osal-run-system函数,这个函数就是负责轮询的,通过轮询查找到要处理的函数和事件,继而传到上层去处理。
上层处理函数是通过events=(tasksArr[idx])(idx,events)这个程序调用的,tasksArr[idx]存储的是事件处理函数,如下图。
以上图中的Sample_ProcessEvents函数为例,从参数中就可以发现这个参数和events=(tasksArr[idx])(idx,events)的参数恰恰一一对应。
3登记任务
有了轮询函数,还是不够的,咱们还需要一个登记任务的机制,不然有事件发生,但是没有登记任务,那么该事件依然不会被处理……。
在OSAL_SampleAPP.c文件里有下面这个函数。
这个函数就是用来登记任务的。
这个任务在前面的轮询机制提到过。
轮询其实就是查任务,看是否有任务要求执行。
一旦有任务执行且没有其它执行的任务,则找到任务相应函数进行相应的处理。
3.1任务事件
任务事件要用到一个函数,osal_set_event,如下面截图所述,这个函数主要是为某个任务设置一个事件,比如说串口事件,或者按键事件。
以按键事件为例:
osal_set_event(Hal_TaskID,HAL_KEY_EVENT)。
需要注意,osal_set_event只有一个系统事件,其余的事件都是用户自定义。
3.2串口任务登记
有时候我们需要用到串口打印数据,方便进行对比或者了解系统运行情况。
任务登记在Sample_APP.C文件的初始化函数里,如下图。
如果Sample_APP相关的任务被系统发现后,那么SampleApp_ProcessEvent函数里会知道是Sample_APP中哪一个任务的ID,不然Sample_APP那么多任务ID,谁也不知道到底是哪个任务的。
4Zigbee数据处理
Zigbee设备接受到zigbee数据后,必然要对这些数据进行处理,处理完后发到哪,暂且比较迷糊。
从SampleApp.C文件里的SampleApp_ProcessEvent函数里可以看到一些端倪。
AF_INCOMING_MSG_CMD表示收到zigbee数据,该zigbee数据是在SampleApp里定义过的。
/*********************************************************************
*@fnSampleApp_MessageMSGCB
*@briefDatamessageprocessorcallback.Thisfunctionprocesses
*anyincomingdata-probablyfromotherdevices.So,based
*onclusterID,performtheintendedaction.
*@paramnone
*@returnnone
voidSampleApp_MessageMSGCB(afIncomingMSGPacket_t*pkt)
{
uint16flashTime;
switch(pkt->clusterId)
{
caseSAMPLEAPP_PERIODIC_CLUSTERID:
break;
caseSAMPLEAPP_FLASH_CLUSTERID:
flashTime=BUILD_UINT16(pkt->cmd.Data[1],pkt->cmd.Data[2]);
HalLedBlink(HAL_LED_4,4,50,(flashTime/4));
break;
}
}
意味着要找到ClusterID才能进行下一步操作?
?
?
5事件
每一个task共有16个事件,用一个UINT16变量表示,每一个二进制位表示一个事件,其中0x8000为系统事件,剩余15个为用户可定义事件,如定时器。
系统事件可定义256个消息。
6HALDriverAPI
这部分介绍了HAL驱动的应用程序编程接口。
API为每个程序提供了接口以方便访问GPIO、UART、ADC、定时器。
7C语言相关
7.1Typedef
typedef作为类型定义关键字,用于在原有数据类型(包括基本类型、构造类型和指针等)的基础上,由用户自定义新的类型名称。
在编程中使用typedef的好处,除了为变量取一个简单易记且意义明确的新名称之外,还可以简化一些比较复杂的类型声明。
比如:
typedefintINT32;
将INT32定义为与int具有相同意义的名字,这样类型INT32就可用于类型声明和类型转换了,它和类型int完全相同。
比如:
INT32 a; //定义整型变量a
(INT32) b;//将其它的类型b转换为整型
既然已经有了int这个名称,为什么还要再取一个名称呢?
主要是为了提高程序的可移植性。
比如,某种微处理器的int为16位,long为32位。
如果要将该程序移植到另一种体系结构的微处理器,假设编译器的int为32位,long为64位,而只有short才是16位的,因此必须将程序中的int全部替换为short,long全部替换为int,如此这样修改势必工作量巨大且容易出错。
如果将它取一个新的名称,然后在程序中全部用新取的名称,那么要移植的工作仅仅只是修改定义这些新名称即可。
也就是说,只需要将以前的:
typedefintINT16;
typedeflongINT32;
替换成:
typedefshortINT16;
typedefintINT32;
由此可见,typedef声明并没有创建一个新类型,而是为某个已经存在的类型增加一个新的名字而已。
用这种方式声明的变量与通过声明方式声明的变量具有完全相同的属性。
至于typedef如何简化复杂的类型声明,将在后续的章节中详细阐述。
综上所述,如果在变量定义的前面加上typedef,即可定义该变量的类型。
比如:
intsize;
这里定义了一个整型变量size,当加上typedef后:
typedefintsize;
那么,size就成为了上面的size变量的类型,即int类型。
既然size是一个类型,当然可以用它来定义另外一个变量。
即:
sizea;
类似于变量的类型定义,也可以用typedef声明新的类型,比如:
char *ptr_to_char;//声明ptr_to_char为一个指向字符的指针
typedefcharptr_to_char;//声明ptr_to_char为指向char的指针类型
ptr_to_char pch;//声明pch是一个指向字符的指针
对于初学者来说,也许会产生一个这样的疑问,为什么不使用#define创建新的类型名?
比如:
#defineptr_to_charchar*
ptr_to_charpch1,pch2;
由于有了“#defineptr_to_charchar*”,因此“ptr_to_charpch1,pch2”可以展开为
char*pch1,pch2;
所以pch2为char型变量。
如果用typedef来定义的话,其代码如下:
typedefchar* ptr_to_char;
ptr_to_char pch1,pch2;
则“ptr_to_charpch1,pch2”等价于
char *pch1;
char *pch2;
因此,pch1、pch2都是指针。
虽然#define语句看起来象typedef,但实际上却有本质上的差别。
对于#define来说,仅在编译前对源代码进行了字符串替换处理;而对于typedef来说,它建立了一个新的数据类型别名。
由此可见,只是将pch1定义为指针变量,却并没有实现程序员的意图,而是将pch2定义成了char型变量。
在指针函数中,有这样一类函数,它们也返回指针,但是这个指针不是指向int、char之类的基本类型,而是指向函数。
对于初学者,别说写出这样的函数声明,就是看到这样的写法也是一头雾水。
比如,下面的语句:
int(*ff(int))(int*,int);
我们用上面介绍的方法分析一下,ff首先与后面的“()”结合,即:
int(*(ff(int)))(int*,int);//用括号将ff(int)再括起来
也就意味着,ff是一个函数。
接着与前面的“*”结合,说明ff函数的返回值是一个指针。
然后再与后面的“()”结合,也就是说,该指针指向的是一个函数。
这种写法确实让人非常难懂,以至于一些初学者产生误解,认为写出别人看不懂的代码才能显示自己水平高。
而事实上恰好相反,能否写出通俗易懂的代码是衡量程序员是否优秀的标准。
一般来说,用typedef关键字会使该声明更简单易懂。
在前面我们已经见过:
int(*PF)(int*,int);
也就是说,PF是一个函数指针“变量”。
当使用typedef声明后,则PF就成为了一个函数指针“类型”,即:
typedefint(*PF)(int*,int);
这样就定义了返回值的类型。
然后,再用PF作为返回值来声明函数:
PFff(int);
返回函数指针会用在什么地方呢?
且听下文分解。
7.2函数指针
以typedefvoid(*halKeyCBack_t)(uint8keys,uint8state)为例,halKeyCBack_t是一个变量的名字,利用halKeyCBack_t可以声明变量,如halKeyCBack_tpHalKeyProcessFunction。
在C语言中,这种类型叫做函数指针,即指针指向的是一个函数。
具体形式为:
int(*pfun)(int,int)。
分析,通过括号强行将pfun首先与“*“结合,也就意味着,pfun是一个指针,接着与后面的“()”结合,说明该指针指向的是一个函数,然后再与前面的int结合,也就是说,该函数的返回值是int。
由此可见,pfun是一个指向返回值为int的函数的指针。
7.3指针函数
指针函数的声明:
类型名*函数名(函数参数表列);以int*pfun(int,int);为例。
由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数。
即:
int*(pfun(int,int));
接着再和前面的“*”结合,说明这个函数的返回值是一个指针。
由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。
7.4函数指针作为函数的返回值
在上面提到的指针函数里面,有这样一类函数,它们也返回指针型数据(地址),但是这个指针不是指向int、char之类的基本类型,而是指向函数。
对于初学者,别说写出这样的函数声明,就是看到这样的写法也是一头雾水。
比如,下面的语句:
int(*ff(int))(int*,int);
我们用上面介绍的方法分析一下,ff首先与后面的“()”结合,即:
int(*(ff(int)))(int*,int);//用括号将ff(int)再括起来
也就意味着,ff是一个函数。
接着与前面的“*”结合,说明ff函数的返回值是一个指针。
然后再与后面的“()”结合,也就是说,该指针指向的是一个函数。
这种写法确实让人非常难懂,以至于一些初学者产生误解,认为写出别人看不懂的代码才能显示自己水平高。
而事实上恰好相反,能否写出通俗易懂的代码是衡量程序员是否优秀的标准。
一般来说,用typedef关键字会使该声明更简单易懂。
在前面我们已经见过:
int(*PF)(int*,int);
也就是说,PF是一个函数指针“变量”。
当使用typedef声明后,则PF就成为了一个函数指针“类型”,即:
typedefint(*PF)(int*,int);
这样就定义了返回值的类型。
然后,再用PF作为返回值来声明函数:
PFff(int);
下面将以程序清单1为例,说明用函数指针作为函数的返回值的用法。
当程序接收用户输入时,如果用户输入d,则求数组的最大值,如果输入x,则求数组的最小值,如果输入p,则求数组的平均值。
程序清单1求最值与平均值示例
1#include
2#include
3doubleGetMin(double*dbData,intiSize)//求最小值
4{
5doubledbMin;
6inti;
7
8assert(iSize>0);
9dbMin=dbData[0];
10for(i=1;i11if(dbMin>dbData[i]){
12dbMin=dbData[i];
13}
14}
15returndbMin;
16}
17
18doubleGetMax(double*dbData,intiSize)//求最大值
19{
20doubledbMax;
21inti;
22
23assert(iSize>0);
24dbMax=dbData[0];
25for(i=1;i26if(dbMax27dbMax=dbData[i];
28}
29}
30returndbMax;
31}
32
33doubleGetAverage(double*dbData,intiSize)//求平均值
34{
35doubledbSum=0;
36inti;
37
38assert(iSize>0);
39for(i=0;i40{
41dbSum+=dbData[i];
42}
43returndbSum/iSize;
44}
45
46doubleUnKnown(double*dbData,intiSize)//未知算法
47{
48return0;
49}
50
51typedefdouble(*PF)(double*dbData,intiSize);//定义函数指针类型
52PFGetOperation(charc)//根据字符得到操作类型,返回函数指针
53{
54switch(c)
55{
56case'd':
57returnGetMax;
58case'x':
59returnGetMin;
60case'p':
61returnGetAverage;
62default:
63returnUnKnown;
64}
65}
66
67intmain(void)
68{
69doubledbData[]={3.1415926,1.4142,-0.5,999,-313,365};
70intiSize=sizeof(dbData)/sizeof(dbData[0]);
71charc;
72
73printf("PleaseinputtheOperation:
\n");
74c=getchar();
printf("resultis%lf\n",GetOperation(c)(dbData,iSize));//通过函数指针调用函数
}
上述程序中前面4个函数分别实现求最大值、最小值、平均值和未知算法,然后实现了GetOperation函数。
这个函数根据字符的返回值实现上面4个函数。
它是以函数指针的形式返回的,从后面的main函数的GetOperation(c)(dbData,iSize)可以看出,通过这个指针可以调用函数。
7.5结构体指针
指向一个结构体类型数据的指针叫做结构体指针,如下图所示,structnode*next就是指向一个node结构体的指针,node只是一个结构体的标识。
结构体指针也可以强制转换,那么就由指向A类型的结构体变为了指向B类型的结构体。
7.6->运算符
这个运算符是用来获取结构体成员的值,它是用于指向结构体、C++中的class等含有子数据的指针用来取子数据(该运算符常见于C语言中的链表部分)。
换种说法,如果我们在C语言中定义了一个结构体,然后申明一个指针指向这个结构体,那么我们要用指针取出结构体中的数据,就要用到“->”,通常的做法是pkt->cmd.Data[0]。
->是*运算符和.运算符的结合,相当于(*pkt).cmd.Data[0]被pkt->cmd.Data[0]替换了。
即为定位pkt指向的结构,先对pkt间接寻址,然后再选择结构的成员cmd。
7.7运算符优先级
优先级
运算符
运算形式
名称或含义
运算对象的个数
结合方向
1
()
(e)
圆括号
左->右
[]
a[e]
数组下标
->
p->x
指针指向成员
.
x.y
结构体、共用体成员
2
!
!
e
逻辑非
(单目运算符)1
左<-右
~
~e
按位取反
++
++x或x++
自增
--
--x或x--
自减
-+
-e
正负号
(类型)
(类型)e
强制类型转换
*
*p
指针运算,由地址求内容
&
&x
求变量地址
sizeof
sizeof(t)
求某类型变量长度(byte)
3
*
e1*e2
乘、除和求余
2
左->右
/
%
4
+
e1+e2
加和减
2
左->右
-
5
<<
e1<左移和右移
左->右
>>
6
<
e1关系运算
2
左->右
<=
>
>=
7
==
e1==e2
等于和不等于比较
2
左->右
!
=
8
&
e1&e2
按位与
2
左->右
9
^
e1^e2
按位异或
2
左->右
10
|
e1|e2
按位或
2
左->右
11
&&
e1&&e2
逻辑与(并且)
2
左->右
12
||
e1||e2
逻辑或(或者)
2
左->右
13
?
:
e1?
e2:
e3
条件运算
3
左<-右
14
=
x=e
赋值运算
2
左<-右
+=
x+=e
复合赋值运算
-=
*=
/=
%=
>>=
<<=
&=
^=
|=
15
e1,e2
顺序求值运算
左->右
注:
左->右为自左向右。
第一、像()[]->.之类的理所当然是最优先的,其实它们压根也不算什么运算符了第二、除了上面的四种运算符之外,接下来就是单目运算符,也就是!
~++---(type)*&sizeof了。
记住它们的顺序可是自右向左啊!
其实结合实例是很容易理解的,比如i++等。
第三、跟着就是双目运算符了,也是C语言运算符优先级中最容易让人混淆的地方了。
其实也没有那么可怕,你认真观察就会发现。
在双目运算符中,算术运算符优先级最高,然后是移位运算符,接着是关系运算符,再着是逻辑运算符。
不过这边需要说的是,在关系运算符中,<<=>>=比==!
=的优先级来得高。
此外,在逻辑运算符中,与运算又比或运算优先级来得高,异或则处于二者之间。
同样的,你可以类比出&&与||的优先级关系.第四、在双目操作符之后,就是三目操作符了,没有什么好说的了。
第五、然后是赋值操作符,你也许会把赋值操作符与三目运算符的优先级搞混。
没有关系,我想你一定写过这样的语句(如果没有,请你记住!
):
max=(a>b)?
a:
b;,从这个语句里,你就不难记住赋值运算符为什么比三目运算符的优先级低了!
第六、逗号运算符是分割各个子语句的(感觉这么说有点不准确,不过我想大家会明白我的意思的),自然优先级最低了,我想这个应该不是很容易混淆的。
总结一下,按运算符优先级从高到低:
单目运算符->双目运算符->三目运算符->赋值运算符->逗号运算符特别在双目运算符中,按运算符优先级从高到低:
算术运算符->移位运算符->关系运算符(其中==和!
=优先级又较低)->逻辑运算符(按位与-〉按位异或-〉按位或-〉逻辑与-〉逻辑或)!
运算符的结合性指同一优先级的运算符在表达式中操作的组织方向,即:
当一个运算对象两侧运算符的优先级别相同时,运算对象与运算符的结合顺序,C语言规定了各种运算符的结合方向(结合性)。
大多数运算符结合方向是“自左至右”,即:
先左后右,例如a-b+c,b两侧有-和+两种运算符的优先级相同,按先左后右结合方向,b先与减号结合,执行a-b的运算,再执行加c的运算。
除了自左至右的结合性外,C语言有三类运算符参与运算的结合方向是从右至左。
即:
单目运算符,条件运算符,以及赋值运算符。
关于结合性的概念在其他高级语言中是没有的,这是C语言的特点之一。
++a或a++和--a或a--分别称为前置加或后置加运算和前置减或后置减运算,都是单目运算符。
值得注意的是,前置、后置运算只能用于变量,不能用于常量和表达式,且结合方向是从右至左。
如当i=6时,求-i++的值和i的值。
由于“-”(负号)“++”为同一个优先级,故应理解为-(i++),又因是后置加,所以先有-i++的值为-6,然后i增值1为7,即i=7。
例1main()
{
inta=3,b=5,c;
c=a*b+++b;
printf(“c=%d”,c);
}
要得出c的值,首先要搞清+++的含义。
++运算符的结合方向是自右向左的,如果将表达式理解为:
c=a*b+(++b);实际上C编译器将表达式处理为:
c=(a*b++)+b,因为