《ZigBee实战演练》学习笔记
学习者:陈美
版本记录
u 2015/10/17起草
初步了解ZigBee是什么和开发环境的快速建立以及基础实验的第一个实验:点亮第一个LED。
u 2015/10/25修订
点亮第二个LED、点亮第三个LED、同时点亮第一个LED,第二个LED,点亮第三个LED、循环点亮三个LED。
u 2015/10/28修订
通用I/O、各类寄存器的相关知识
u 2015/11/5修订
完成按键实验以及拓展实验
u 2015/11/8修订
熟悉中断的相关知识完成外部中断相关实验
u 2015/11/9修订
利用定时器使LED周期性闪烁。
u 2015/11/13修订
串口通讯相关实验
u 2015/11/14修订
AD控制(自带温度计)相关实验、睡眠唤醒相关实验
u 2015/11/15修订
看门狗相关实验、LCD12864液晶显示相关实验
u 2015/11/16修订
Zigbee协议栈简介、无线点灯相关实验
u 2015/11/18修订
信号传输质量检测相关实验
u 2015/11/23修订
协议栈工作原理介绍、协议栈中的串口实验
u 2015/11/24修订
协议栈中的按键实验、一 小时实现无线数据传输、串口透 传,打造无线串口模块
u 2015/11/27修订
网络通讯实验(单播、组播、广播)、Zigbee 协议栈网络 管理
学习目标
一、完成九个基础实验。
二、完成八个组网实验。
学习内容
一、初步了解ZigBee是什么和开发环境的快速建立以及基础
实验的第一个实验:点亮第一个LED。
1、ZigBee简介
(1)它是一种低功耗个域网协议,根据这个协议规定的即使一种短距离,低功耗的无线组网通信技术。
(2)ZigBee的结点类型:协调器(只有一个)、路由器、终端。
2、ZigBee开发平台
平台:IAR+Z_stack 2007 PRO
芯片:TI公司的CC2530
3、开发环境的快速建立
(1)相关软件和驱动安装
S1:安装IAR8.10方法
解压:网蜂科技(WeBee) ZigBee开发套件配套资源 (适用于2014年9月22日之后购买的用户).rar-->双击解压后生成的文件夹-->双击“开发软件和驱动”-->双击“IAR-EW8051-8101(带注册机)”-->双击“IAR kegen PartA810.exe”(因为这个软件的正版是要收钱的买那个密钥,而中国一般用的都是盗版的,一些牛人把它给破解了,用注册机能自动生成密钥。所以我们得先打开这个可执行程序来获取这个密钥)-->双击“EW8051-EV-Web-8101.exe”,然后直接下一步,下一步,下一步。。。。。中间会提示输入license和license key。还有路径的选择(我安装的路径是F:\,。安装完成后可以在F:\common\bin路径下找到IarIdePm.exe)。
S2:TI协议栈Zstack_CC2530_2.5.1a安装方法
(目前八个基础实验还用不到这个软件,等八个实验做完以后可能会用到)双击解压后生成的文件夹-->双击“开发软件和驱动”-->双击“ZStack-CC2530-2.5.1a.exe”下一步....
S3:仿真器SRF04EB驱动安装方法
(该步骤是对于首次安装该驱动的计算机)插上仿真器,状态栏会检测到正在安装设备驱动程序软件-->打开设备管理器(在控制面板中),会看到的其他设备栏下有出现带感叹号的SmartRF04EB,选中右击选择“更新程序驱动软件”-->选择“浏览计算机以查找驱动程序软件”-->单击“浏览”选择该仿真器驱动所在位置-->下一步,待成功安装后关闭。
S4:USB转串口驱动的安装
(我们观察到CC2530开发板,它上面有一个连接仿真器的接口还有一个接口类似于、就是串口,它也要和电脑的USB接口通过USB线连接可用于串口发送实验。我们在做串口实验的时候,我们其实是需要串口线的,它是专用的线,但是由于一些缺陷我们用USB线代替,要实现这个功能就必须安装驱动。驱动(软件)是硬件工作。ZigBee 所有开发板上集成 PL2303 的 USB 转串口芯片,我们通过安装相应的驱动可通过 USB 直接开发调试。安装时候建议 USB 线不连接 zigbee 开发板! )
双击解压后生成的文件夹-->双击“开发软件和驱动”-->双击“串口调试助手和驱动”-->双击“PL2030驱动”-->双击“PL2303_driver(默认安装这个).exe”-->下一步,完成。安装好后,通过方口 USB 线连接 zigbee 开发板,我们右键打开我的电脑--属性—硬件—设备管理器,在端口栏下查看到 USB-to-Serial Com,说明驱动安装成功。
(2)IAR工程文件的快速建立
S1:新建项目
S2:新建文件
S3:编写代码
S4:配置
S5:编译、下载与仿真、运行
附:使用TI SmartRF Flash Programmer下载程序
它是另外一种程序烧写方法。虽然简单,迅速。但是它好像不能用于调试程序。目前我们做的实验,代码都是复制粘贴的,因此用不到调试,但是后期随着程序的复杂,需自己慢慢调试,因此,该种程序烧写方法不建议使用。
相关知识:
无线模块:高性能专业数据传输模块。
MCU:微控单元,单片微型计算机或者单片机。把中央处理器的频率与规格做适当缩减,并将内存,计数器等都整合在单一芯片上,形成的芯片级计算机。
芯片:可以把程序烧写进去。
PCB模块:印制电路板,又称印刷电路板,是电子元器件电气连接的提供。
电气连接:指产品内部将不同导体连接起来的所有方式。
ZigBee:单片机+无线模块。
节点:CC2530开发板也叫节点。因为它也可以不连接计算机,由电池供电。
二、点亮第二个LED、点亮第三个LED、同时点亮第一个LED,第
二个LED,点亮第三个LED、循环点亮三个LED。
1、深入理解“点亮第一个LED”代码
源代码:
#include <ioCC2530.h>
#define LED1 P1_0 //定义P10口为LED1控制端
void IO_Init(void)
{
P1DIR |= 0x01; //P1_0定义为输出
P1INP |= 0X01;
LED1 = 1; //点亮LED1
}
void main(void)
{
IO_Init(); //调用初始化程序
while(1);//让程序进入无限循环,目的是为了让程序一 直保持在我们需要运行的情况下
}
首先,我们的这个实验目的就是要点亮第一个LED,那么我们是通过代码(软件)让硬件工作,而软件是不能直接让硬件工作的,是通过间接方式上。作为这中间桥梁的其实是寄存器,涉及到的寄存器有功能寄存器、方向寄存器和配置寄存器。应当注意的是方向寄存器,它是决定让那个灯亮的依据。代码是高级语言,无法直接控制硬件工作的,的找一个听得懂该语言的东西,这个东西就是寄存器,代码通过操作寄存器简接使硬件工作。
在以上代码中,我们可以看到P1DIR |= 0x01; 该句代码中P1DIR的功能就是在选择哪个灯亮。此处它的值是0x01,它是一个十六进制的数。转化为二进制后是八位,则功能寄存器是八位寄存器。0x01=0000 0001。再查看传感器地板,控制LED1的端口是P1_0,因此在程序的最开始要自定义#define LED1 P1_0,在main函数中要设置LED1=1,表示点亮LED1。而八位二进制的最低位是1,其余是0。
2、点亮第二个LED2和第三个LED3
通过对点亮第一个LED1的代码理解后,再结合传感器底板的观察,可知控制LED2的端口是P1_1,而控制LED3的端口是P1_4,因此如果想点亮LED2,则在以上代码中仅将自定义改成#define LED2 P1_1,IO_Init(void)中将P1DIR的值改为0x10,main(void)中将LED1 = 1改为LED2=1,其余不变。点亮LED3同样一次对应修改。
3、同时点亮LED1、LED2、LED3
#include <ioCC2530.h>
#define LED1 P1_0 //定义P10口为LED1控制端
#define LED2 P1_1 //定义P11口为LED2控制端
#define LED3 P1_4 //定义P14口为LED3控制端
void IO_Init(void)
{
P1DIR |= 0x13; //P1_0定义为输出
P1INP |= 0X01;
LED1 = 1; //点亮LED1
LED2 = 1; //点亮LED2
LED3 = 1; //点亮LED3
}
void main(void)
{
IO_Init(); //调用初始化程序
while(1);
}
PS:以上代码中加粗部分为修改部分。
4、循环点亮三个LED
我们要循环点亮三个LED,那么我们得控制好循环的次数。而且为了实验现象明显我们得想到延迟,那么必定要写一个延迟函数。然后在main函数中分别调用这些函数来实现循环亮灭。
#include <ioCC2530.h>
#define LED1 P1_0 //定义P10口为LED1控制端
#define LED2 P1_1
#define LED3 P1_4
void Delay(unsigned char n) ;
//延时函数
void Delay(unsigned char n)
{
unsigned char i;
unsigned int j;
for(i = 0; i < n; i++)
for(j = 1; j < 3000; j++)
{
asm("NOP");
asm("NOP");
asm("NOP");
}
}
void IO_Init(void)
{
P1DIR |= 0x13; //P1_0定义为输出
P1INP |= 0X01;
}
void main(void)
{
IO_Init(); //调用初始化程序
unsigned char i;
int n=100;//控制循环的次数
LED1=0;
LED2=0;
LED3=0;
for(i=0;i<n;i++)
{
LED1 = 0; //LED1灯灭
Delay(500);//延时
LED1 = 1; //点亮LED1
Delay(500);//延时
LED1 = 0; //LED1灯灭
LED2 = 0; //LED2灯灭
Delay(500);//延时
LED2 = 1;//点亮LED2
Delay(500);//延时
LED2 = 0; //LED2灯灭
LED3 = 0; //LED3灯灭
Delay(500);//延时
LED3 = 1;//点亮LED2
Delay(500);//延时
LED3 = 0; //LED3灯灭
}
}
三、通用I/O、各类寄存器的相关知识
1、通用I/O
CC2530有21个输入/输出引脚,可以配置为通用数字I/O或外设I/O为不同信号服务。当I/O口用作通用I/O时引脚可以组成3个8位端口,端口0,端口1和端口2,分别用P0、P1和P2来表示。
端口0即P0口:有8位端口,分别是P0_0~P0_7;
如:控制key
端口1即P1口:有8位端口,分别是P1_0~P1_7;
如:控制LED
端口2即P2口:有5位端口,分别是P2_0~P2_4;
根据寄存器设置的不同,可以将端口设置为输入/输 出状态,通用I/O端口常用的寄存器有功能寄存器PxSEL(其中x端口的标号0~2),方向寄存器PxDIR(其中x端口的标号0~2),配置寄存器PxINP(其中x端口的标号0~2)。
(引脚、端口、接口、口)
2、寄存器
功能寄存器PxSEL、方向寄存器PxDIR、设置寄存器PxINP
的设置都是针对某一个端口,端口的几个引脚共同设置。
* PxSEL (0:普通 IO口 1:第二功能)
* PxDIR (0:输入 1:输出)
* PxINP (0:上拉/下拉1:三态)
上拉:就是要接到电源正极有时候可以直接接到电源正极,有时候需要通过电阻接到电源正极;
下拉:就是接地;
三态:就是导通,截止,高组,高阻就是电阻变得特别大。
3、功能寄存器PxSEL
功能寄存器用来设置端口的引脚为通用I/O或外设I/O信号。PxSEL (0:普通IO 口1:第二功能)。复位之后,所有的数字I/O引脚都被设置为通用输入引脚。 如果要将某一引脚设置为通用I/O或者是外设I/O只需要将相应的“位”设置为0或1即可。比如要将P0_1设置为通用I/O,此时需要将P0SEL的“第一位”置0;将P0_2设置为外设I/O,此时需要将P0SEL的“第二位”置1。
例:
/*P0_1设置为通用I/O引脚*/
P0SEL &=~0x02
/*P0_2设置为外设I/O引脚*/
P0SEL|=0x04
4、方向寄存器PxDIR
当端口用作通用I/O时,可以用方向寄存器来配置端口的信号方向。PxDIR (0:输入1:输出)。复位之后,所有数字I/O引脚都被设置为输入引脚。 如果要将端口某一引脚设置为通用I/O的输入或输入功能时,只需要将PXDIR寄存器相应“位”设置为0或1即可,比如要将P0_1设置为输入I/O,此时需要将P0DIR的“第一位”置0;将P0_2设置为输出I/O,此时需要将P0SEL的“第二位”置1。
例:
/*P0_1设置为输入I/O引脚*/
P0DIR&=~0x02;
/*P0_1设置为输入I/O引脚*/
P0DIR1=0x04;
5、配置寄存器PxINP
当端口用作通用I/O输入时,引脚可以设置为上拉、下拉和三态操作模式。复位之后,所有端口均被设置为带有上拉的输入。要取消输入的上拉和下拉功能,需要将PxINP中的对应“位”设置为1。如果要将某一引脚设置为上拉/下拉或者是三态,此时需要将相应的位设置为0或1即可,比如要将P0_5设置为上拉/下拉,此时需要将P0INP的“第五位”置0;如果将P0_3设置为三态功能时,此时需要将P0INP的“第三位”置1。
例:
/*p0_5设置为上拉\下拉功能*/
P0INP&=~0x20;
/*p0_3设置为三态功能*/
P0INP|=0x08;
不是规则的规则:对某一“位”设置就先将那一“位”设值为1,剩余其他设值为0。设置为功能“0”是用“&=~”,设置为功能“1”是用“|=”。而功能寄存器和配置寄存器通常只需设置第一位,而方向寄存器需配置多位(如根据根据灯的个数按键的个数)。
四、完成按键实验以及拓展实验
1、通过按下按键S1来控制LED1的亮灭
1)现象:LED1不亮的时候,当你按下S1并松手后,LED1
就亮了。
LED1不亮的时候,当你按下S1不松手,LED1
仍不会亮。
LED1亮的时候,当你按下S1并松手后,LED1
就灭了。
LED1亮的时候,当你按下S1不松手,LED1仍
不会灭。
2)代码:
#include <ioCC2530.h>
#define uint unsigned int
#define uchar unsigned char
//定义控制LED灯的端口
#define LED1 P1_0 //LED1为P1.0口控制
#define KEY1 P0_0 //KEY1为P0.0口控制
//函数声明
void Delayms(uint); //延时函数
void InitLed(void); //初始化LED1
void KeyInit(); //按键初始化
uchar KeyScan(); //按键扫描程序
延时函数
void Delayms(uint xms) //i=xms 即延时i毫秒
{
uint i,j;
for(i=xms;i>0;i--)
for(j=587;j>0;j--);
}
LED初始化函数
void InitLed(void)
{
P1DIR |= 0x01; //P1_0定义为输出
P1INP |= 0X01;
LED1 = 0; //LED1灯一开始是熄灭状态
}
按键初始化函数
void InitKey()
{
P0SEL &= ~0X01; //设置P00为普通IO口
P0DIR &= ~0X01; //按键在P00口,设置为输入模式
P0INP &= ~0x01; //打开P00上拉电阻,不影响
}
按键检测函数
uchar KeyScan(void)
{
if(KEY1==0)
{
Delayms(10);
if(KEY1==0)
{
while(!KEY1); //松手检测
return 1; //有按键按下
}
}
return 0; //无按键按下
}
主函数
void main(void)
{
InitLed( ); //调用初始化函数
InitKey( );
while(1)
{
if(KeyScan()) //按键改变LED状态
LED1=~LED1;
}
}
3)对部分代码的解释
A)uchar KeyScan(); //按键扫描程序
uchar KeyScan(void)
{
if(KEY1==0)
{
Delayms(10);
if(KEY1==0)
{
while(!KEY1); //松手检测
return 1; //有按键按下
}
}
return 0; //无按键按下
}
上述的按键扫描程序中,函数体内体用了两个if语句即if嵌套语句,中间用了delayms函数。这样做的目的是为防止“鬼影”,专业术语:去抖动。举一个例子:有一扇门,我们现在要检测是否有人痛过这扇门。在某一个时刻,我们开始检测,有一个人正要准备这扇门,注意只是正准备痛过而还没有痛过也就是这一时刻检测员没有检测到,于是记录下没有人通过的结果。但是在这时刻的下一秒这个人就通过了这扇门。因此,该检测员记录的结果不对。如果检测员多等一会儿,就可以检测到结果。接下来,Key==1表示手在按键上(这里默认为有力的作用),Key==0表示手离开了按键。而while(!Key);也是为了多次确保手已离开了按键。这两步共同决定了是否有按键按下。这个按键扫描程序也很好地解释了一开始的四种现象。
2、通过按下按键S1来控制LED1的亮灭,通过按下按键S2
来控制LED2的亮灭。
1)相关的代码:
#include <ioCC2530.h>
#define uint unsigned int
#define uchar unsigned char
//定义控制LED灯的端口
#define LED1 P1_0 //LED1为P1.0口控制
#define KEY1 P0_0 //KEY1为P0.0口控制
#define LED2 P1_1 //LED2为P1.0口控制
#define KEY2 P0_1 //KEY2为P0.0口控制
//函数声明
void Delayms(uint); //延时函数
void InitLed(void); //初始化LED1
void KeyInit(); //按键初始化
uchar KeyScan1(); //按键扫描程序
uchar KeyScan2(); //按键扫描程序
/****************************
延时函数
*****************************/
void Delayms(uint xms) //i=xms 即延时i毫秒
{
uint i,j;
for(i=xms;i>0;i--)
for(j=587;j>0;j--);
}
/****************************
LED初始化函数
*****************************/
void InitLed(void)
{
P1DIR |= 0x03; //P1_0、P1_1定义为输出
P1INP |= 0X01;
LED1 = 0; //LED1灯熄灭
LED2 = 0; //LED2灯熄灭
}
/****************************
按键初始化函数
*****************************/
void InitKey()
{
P0SEL &= ~0X01; //设置