《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
#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
#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
#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 { 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 #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 #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; //设置P00为普通IO口 P0DIR &= ~0X03; //按键在P00、P01口,设置为输入模式 P0INP &= ~0x01; //打开P00上拉电阻,不影响 } /**************************** 按键检测函数 *****************************/ uchar KeyScan1(void) { if(KEY1==0) {Delayms(10); if(KEY1==0) { while(!KEY1); //松手检测 return 1; //有按键按下 } } return 0; //无按键按下 } uchar KeyScan2(void) { if(KEY2==0) {Delayms(10); if(KEY2==0) { while(!KEY2); //松手检测 return 1; //有按键按下 } } return 0; //无按键按下 } /*************************** 主函数 ***************************/ void main(void) { InitLed(); //调用初始化函数 InitKey(); while(1) { if(KeyScan1()) //按键改变LED1状态 LED1=~LED1; if(KeyScan2()) //按键改变LED2状态 LED2=~LED2; } /* while(1) { if(KeyScan2()) //按键改变LED状态 LED2=~LED2; } */ } 2)注意:一个函数中只能有一个无限死循环。 五、熟悉中断的相关知识完成外部中断相关实验 1、什么是外部中断 中断其实就是机器收到信号后,放下正在处理的任务,来处理你设定好的中断函数。CC2530按键外部中断,通过按键产生中断,进入中断函数,实现基本的中断控制。这种中断方式是基本的I/O中断。 2、通用I/O中断 CC2530的CPU有18个中断源,每个中断源都由一系列的SFR寄存器进行控制。每个中断都可以分别使能和控制 (中断使能寄存器IEN0、IEN1、IEN2)(0:中断禁止 1: 中断使能)。通用I/O引脚在设置为输入后,可以用于产 生中断。并且通用I/O中断还可以设置其触发方式。通用 I/O中断在P0、P1、P2三个端口都可以产生,在设置其中 断时需要将其要发生中断引脚的使能位置1。 中断使能:让中断可以被触发,可以进入中断服务程序。 中断禁止:即使中断信号来了也不会触发中断,也就不 会进入中断服务程序。 3、中断优先级 为使系统能及时响应并处理发生的所有中断,系统根据引起中断事件的重要性和紧迫程度,硬件将中断源分为若干个级别,称作中断优先级。本次试验中,仅仅涉及到了通过按键产生中断信号,不需考虑优先级。 4、中断涉及到的寄存器 (1)中断使能寄存器 1)IEN1(八位寄存器) 中断使能寄存器IEN1控制P0端口、定时器1~4、 和DMA中断的使能和禁止,如果使某一位中断使能, 只需要将IEN1中对应的“位”设置为1即可;如果 将中断禁止,只需要将其设置为0即可。比如,IEN1 使能寄存器的第五位P0IE用来设置端口P0使能。 例:IEN1中断设置 /*设置P0端口中断使能*/ IEN1|=0x20; 2)IEN2(八位寄存器) 中断使能寄存器IEN2控制看门狗定时器、P1端口、串口发送、P2端口、RF中断的使能和禁止,如果使 某一位中断使能,只需要将IEN2中对应的“位”设置为1即可;如果将中断禁止,只需要将其设置为0即可。比如,IEN2使能寄存器的第四位P1IE用来 设置端口P1使能;第一位P2IE用来设置端口P2使能。 例:IEN2使能设置 /*设置P1和P2端口中断使能*/ IEN2|=0x12; 3)PxIEN IEN1和IEN2寄存器设置端口中断时,是将P0、P1 和P2所有端口的引脚全部设置为中断使能。比如设 置P0端口中断使能,实质上是设置了P0_0~P0_7所 有的输入引脚中断使能,如果要单独设置某一引脚 中断使能,除了设置IENx(x的取值为0和1)还 需要设置PxIEN寄存器(x的取值为0、1、2)。PxIEN 中断使能寄存器可以单独配置端口的某一引脚中断 使能禁止。 4)P0IEN(八位寄存器) 中断使能寄存器P0IEN控制P0端口P0_0~P0_7引脚 的中断禁止和使能,如果要使某一特定引脚中断使 能或禁止,只需要在P0IEN中将相应的“位”设置 为0或1即可。 例:设置P0_5引脚中断使能 /*设置P0_5中断使能*/ P0IEN|=0x20; /*设置P0端口中断使能*/ IEN|=0x20; 5)IEN0(八位寄存器) 通用I/O中断在设置完引脚之后,需要开启CC2530 总中断,总中断EA位于中断使能寄存器IEN0的第 七位,此位决定CC2530所有中断的使能和禁止。通 用I\O在设置完成引脚中断之后,需要将EA的总中 断打开。 例:EA设置 /*打开总中断*/ EA=1; (2)中断触发方式寄存器 通用I\O在作为中断使用时,可以配置其中断触发方 式由寄存器PICTL来设置,其触发方式分为上升沿触 发方式和下降沿触发方式。中断触发方式寄存器可以 控制P0端口、P1端口和P2端口的触发方式,比如中 断触发方式寄存器的第零位用来设置端口0的 P0.0~ P0.7输入模式下的中断配置,该位为所有端口0的输 入P0.0~P0.7选择中断请求条件。0:输入的上升沿引 起中断;1:输入的下降沿引起中断。 例:PICTL中断设置 /*设置P0_5下降沿触发中断*/ PICTL|=0x01 (3)中断标志寄存器 1)PxIFG I/O中断发生后,中断标志寄存器的相应位会自动 值1。在中断处理函数中判断是否有中断发生只需 要判断寄存器PxIFG(其中x的取值为0、1、2)的 值是否大于0,或者是PxIFG的某一位是否大于0 即可。 2)P0IFG(八位寄存器) 如果在P0端口有中断发生,但不需要判断具体是 哪一引脚发生中断是,在判断中断标志时只需要判 断P0IFG是否大于0即可。 例:中断标志判断 /*判断端口P0是否发生中断*/ if(P0IFG>0) { } 如果需要判断是否某一引脚发生中断,则需要判断 PxIFG寄存器中相应的“位”是否置1. 例:中断标志判断 /*判断P0_5是否发生中断*/ If(P0IFG&0x20>0) { } 6、实验现象 依次按下按键S1控制LED1的亮和灭 5、相关代码 #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0//定义LED1为P1.0口控制 #define KEY1 P0_0 //中断口 //函数声明 void Delayms(uint);//延时函数 void InitLed(void);//初始化P1口 void KeyInit(); //按键初始化 uchar KeyValue=0; //延时函数 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灯熄灭 } //KEY初始化程序--外部中断方式 void InitKey() { P0IEN |= 0X01; //P00 设置为中断方式 IEN1 |= 0X20; //允许P0口中断; PICTL |= 0x01; //下降沿触发 P0IFG = 0x00; //初始化中断标志位 EA = 1; //开CC2530总中断 } //中断处理函数 #pragma vector = P0INT_VECTOR //格式:#pragma vector = 中断向量,紧接着是中断处理程序 __interrupt void P0_ISR(void) { Delayms(100); //去除抖动,让上次的 //中断处理完成。 LED1=~LED1; //改变LED1状态 P0IFG = 0; //清中断标志 P0IF = 0; //清中断标志 } //主函数 void main(void) { InitLed();//调用初始化函数 InitKey(); while(1); } 6、对代码的解释 上述代码,主函数中只有三行语句,感觉好像没有用到中 断函数,但其实是用到了的。所谓外部中断就是当接收到 中断信号,就去处理中断即执行中断函数。一开始,我认 程序运行到while(1)这个地方,在没有接收按键中断信 号之前,它会一直停在那里。然后这时候按下按键S1后, 表明中断信号已产生。然后程序就会停下当前的任务即一 直进行while循环转去执行中断处理程序。然后执行完后 回来继续while循环。还有对于该实验中的中断处理函数, 需要注意的一点就是中断处理函数有很多种写法,有些地 方可能会在一开始进行一个if语句的判断,来检测是否有 中断产生,这里没有这样写。但是它一样能完成这样的一 个中断处理。所以代码不需要去纠结,只要会用即可。还 有这里的中断函数中有: P0IFG = 0; //清中断标志 P0IF = 0; //清中断标志 可能会有这样的一个想法:这两句是不是有些重复。其实 不然,P0IF是清P0端口中断标志,而P0IFG是清P0端口 具体某一位的中断标志。而且这两句少了其中任一句都不 行(实验现象:当你按下按键后,灯会一直闪烁一直闪 烁)。如果将这两句的位置调换一下也会影响实验现象(实 验现象:一段时间,按下按键一次后,灯会亮一下然后保 持灭的状态;一段时间,按下按键一次后,灯会灭一下然 后保持亮的状态。) 六、利用定时器使LED周期性闪烁 1、通过定时器T1查询方式控制LED1周期性闪烁 (1)定时器 定时器就类似于计时器,可以顺计时,可以倒计时。 我们可以事先给他设置一个值,确定它的工作时间t。在某一时刻让它开始工作,然后工作的时间达到t后, 就产生一个行为或者更准确的说应该是一个中断请求, 然后去处理这个中断,比如我们的LED灯就会亮。这么一来,它的功能就相当于一个延时函数,只不过延时函数是用程序来控制时间,而定时器是通过具体的硬件来实现对时间的控制。我认为定时器控制的时间应该更准确一些。 CC2530有5个定时器,一个十六位(定时器1)、两个8位定时器(定时器3和定时器4)、一个用于休眠的定时器(睡眠定时器)和一个MAC定时器。本次试验只涉及到定时器1。 (2)通过定时器T1查询方式控制LED1周期性闪烁VS 通过器时器T3中断方式控制LED1周期性闪烁 如果使用的中断的话定时时间一到马上执行中断函数;如果使用查询的话定时时间到了之后必须等到程序执行到判断语句的时候才能执行。所以实时性差。 (3)定时器T1的功能 定时器T1是一个独立的16位定时器,支持定时和计数功能,有输入捕获、输出比较和PWN的功能。定时器1有5个独立的输出捕获和输入比较通道。每个 通道使用通道使用一个I/O引脚。定时器1的主要功能 如下: 1)5个独立的捕获、比较通道。 输入捕获的工作原理: 当你设置的捕获开始的时候,cpu会将计数寄存器的值复制到捕获比较寄存器中并开始计数,当再次捕捉到电平变化时,这是计数寄存器中的值减去刚才复制的值就是这段电平的持续时间,你可以设置上升沿捕获、下降沿捕获、或者上升沿下降沿都捕获。 输出比较的工作原理: 这里有两个单元:一个计数器单元和一个比较单元,比较单元就是个双缓冲寄存器,比较单元的值是可以根据不同的模式设置的,与此同时,计数器在不停的计数,并不停的与比较寄存器中的值进行比较,当计数器的值与比较寄存器的值相等的时候一个比较匹配就发生了,根据自己的设置,就会产生不同的波形了。 2)上升沿、下降沿或任何边沿的输入捕获。 3)设置、清除或切换输出比较。 4)3种运行模式:自由运行、模计数模式和正/倒计 数操作模式。 5)可被1、8、32或128整除的时钟分频器。 6)在捕获、比较和最终计数上生成中断请求。 7)具有DMA触发功能。 DMA触发可以通过设置DMA的触发源,来判断DMA通道会接收哪一个事件的触发。比如这里的定时器 触发。 (4)定时器T1的运行模式 定时器T1运行模式有多种,不同的运行模式有不 同的功能。本次试验只涉及到自由运行模式。自由运行模式下,计数器从0x0000开始,每个活动时钟边沿增加1。当计数器达到0xFFFF会产生自动溢出,然后计数器自动载入0x0000,继续递增计数,当达到最终数值0xFFFF产生溢出。当产生溢出之后,相应的寄存器会自动产生溢出标志。 Ps:定时器1具有一个十六位的计数器,计数器具备运行模式。 (5)所涉及到的寄存器 1)T1CTL(控制寄存器) 定时器T1的控制寄存器T1CTL是一个八位寄存器。它的主要功能时选择定时器T1的工作模式和分 频器频率划分。 它的第0位和第1位是用来设置定时器T1的模式。定时器操作模式通过下列方式设置: 00:暂停运行。 01:自由运行,从0x0000到0xFFF反复计数。 10:模计数,从0x0000到TICCO反复计数。 11:正计数/倒计数,从0x0000到T1CC0反复计数且从T1CC0倒计数到0x0000。 它的第2位和第3位用来设置分频器划分值。产生主动的时钟边缘用来更新计数器,如下: 00:标记频率/1 01:标记频率/8 10:标记频率/32 11:标记频率/128 它的第4位到第7位保留。 2)T1STAT(状态寄存器) 定时器1的状态寄存器T1STAT是一个八位寄存器。只负责定时器1中断标志,包括定时器溢出中断标志和定时器1通道0~4的中断标志。 它的第0位到第4位为通道0~通道4的中断标志,第5位为溢出标志位,当计数器达到计数终值自动置1。 D0:定时器1通道0中断标志位 D1:定时器1通道1中断标志位 D2:定时器1通道2中断标志位 D3:定时器1通道3中断标志位 D4:定时器1通道4中断标志位 D5:定时器溢出中断标志位 D7:未用 D8:未用 3)IRCON(中断标志位寄存器) 当定时器1中断发生时设为1并且当CPU向量指向中断服务例程时清除。 0:无中断请求 1:有中断请求 (6)实验源代码 #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0//定义LED1为P10口控制 //函数声明 //void Delayms(uint xms);//延时函数 void InitLed(void);//初始化P1口 void InitT1(); //初始化定时器T1 //延时函数 /*void Delayms(uint xms) //i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } */ //初始化程序 void InitLed(void) { P1DIR |= 0x01; //P1_0定义为输出 P1INP |= 0X01; LED1 = 0; //LED1灯初始化熄灭 } //定时器初始化 void InitT1() //系统不配置工作时钟时默认是2分//频,即16MHz { T1CTL = 0x0d; //128分频,自动重装 //0X0000-0XFFFF T1STAT= 0x21; //通道0, 中断有效 } //主函数 void main(void) { uchar count; InitLed();//调用初始化函数 InitT1(); while(1) { if(IRCON>0) { IRCON=0; if(++count>=1) //约1s周期性闪烁 { count=0; LED1 = !LED1; //LED1闪烁 } } } } (7)对代码的解释 首先是一些命令行,然后就是一函数的声明。这里有三个函数,分别是延时函数、灯初始化函数和定时器初始化函数。前面两个函数都不是重点,关键是定时器初始化函数。我们只是将将灯作为我们实验的一个现象展示,让我们对定时器有一个深入的了解,以后它将作为一个模块应用到其他的场景中,比如通过定时器来控制某一个物体的开关。这里的定时器初始化函数中只有两行代码。T1CTL = 0x0d; 它表示我们这个定时器的工作模式是自由运行模式,工作频率为128分频,至于它的值是怎么来的呢?我们前面讲过,T1CTL控制寄存器它是一个八位寄存器,它的第0位和第1位是控制定时器的工作模式的,当为01表示的是自由运行模式,我们本次试验涉及的就是自由运行模式,所以低两位为01,而它的第3位和第4位是控制定时器分频的,当为11时表示的是128分频,所以T1CTL的低四位是1101,高四位保留即为0000,所以转化为十六进制为0x0d。T1STAT=0x21;它表示的是通道0中断有效。试验证明其实这一个设置要不要都不影响。紧接着就是主函数,其实我们应该是从主函数开始讲解。首先定义一个无符号的字符类型变量count,它作为整数使用的话范围是从0到255,默认值是0。该变量在计算总时间的一个公式中会用到。然后就是初始化函数,完了后进入到一个无限循环,while循环中首先用if语句判断一下是否有中断请求,有的话表明寄存器IRCON的值为1,此时将它置为0。 然后执行下一个if语句,判断count的值,count的值应 该是记录计时器达到0xFFFF的次数。然后count究竟能处 在一个什么样的范围内,就取决于你想让他间隔多长时间 闪烁一次。这里是count的值大于等于1if条件成立,执 行下一步将count置零,紧接着改变灯的转态。第二个if 语句也可以用一个延时函数来代替。 Ps:系统在不配置工作频率时默认为 2 分频,即32M/2=16M,所以定时器每次溢出时T=1/(16M/128)*65536≈0.5s, 所以总时间Ta=T*count=0.5*1=0.5S切换 1 次状态。所以看起来是 1S 闪烁 1 次。 2、利用定时器T3中断方式控制LED1状态周期性改变 (1)定时器T3的介绍 1)定时器3的运行模式 定时器3是一个八位寄存器,它有两个独立独立的比较/捕获通道,每个通道上使用一个I/O引脚。它还具有一个8位的计数器,提供定时和计数功能。计数器有4种运行模式:自由运行模式、倒计数模式、模计数模式和正/倒计数模式。本次试验用到的是自由运行模式,定时器的计数器从0x00开始,在每个时钟活动的边沿递增,当计数器达到0xFF,计数器将重装载入0x00。如果设置了中断,当达到最终计数值0xFF时,将产生一个中断请求。 2)定时器通道 定时器3有两个通道,通道0和通道1,每个通道的模式是由控制和状态寄存器来控制的,设置模式包括捕获模式和输出比较模式。当通道配置为输入捕获模式时,通道相关的I/O引脚配置为一个输入。定时器启动之后,输入引脚上的一个上升沿、下降沿或任何边沿都会触发一个捕获,即捕获8位计数器内容到相关的通道捕获寄存器中,因此定时器能够捕获一个外部事件发生的时间。它的每一次捕获可以作为一个触发中断事件。当通道配置为输出比较模式,通道相关的I/O引脚必须设置为输出。定时器启动之后,将对比计数器的内容和通道比较寄存器的内容。 3)定时器中断 定时器3有一个中断向量,当触发中断事件发生时,将产生一个中断请求,有下列几种触发中断事件: 计数器达到最终计数值; 比较事件; 捕获事件。 Ps:所有涉及到中断的实验应当都是首先有一个触发中断事件,然后由中断向量产生一个中断请求,紧接着转去处理中断。 (2)相关寄存器的配置 1)控制寄存器T3CTL(八位寄存器) 定时器3控制寄存器T3CTL主要负责定时器的分频器的划分、定时器的停止/运行、定时器的中断设置、定时器的计数器清除、定时器的功能模式选择。 从高位到地位: 7~5:负责分频器划分值。 000:标记频率/1 001:标记频率/2 010:标记频率/4 011:标记频率/8 100:标记频率/16 101:标记频率/32 110:标记频率/64 111:标记频率/128 4:负责启动定时器。 3:负责溢出中断屏蔽。 0:中断禁止 1:中断使能 2:负责清除计数器。 0~1:负责选择定时器的模式。 00:自由运行模式 01:倒计数模式 10:模计数模式 11:正计数/倒计数模式 (2)相关代码 #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0 //定义LED1为P10口控制 //函数声明 void Delayms(uint xms); //延时函数 void InitLed(void); //初始化P1口 void InitT3(); //初始化定时器T3 uint count;//用于定时器计数 //延时函数 void Delayms(uint xms) //i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x01; //P1_0义为输出 P1INP |= 0X01; LED1 = 0; //LED1灯熄灭 } //定时器初始化 void InitT3() { T3CTL |= 0x08 ; //开溢出中断(针对计数器) /* 当计数器达到0xFF,计数器将重新载入0x00。 */ T3IE = 1; //T3中断(针对定时器) //或者可以写成IEN1|=0x08; EA = 1; //开总中断(针对CC2530芯片) T3CTL |=0XE0; //128分频,128/16000000*N=0.5S, //N=65200 /* 每隔0.5秒一次开,一次关,这样看起来就像以1S周期性闪烁 */ T3CTL &= ~0X03; //自动重装 00->0xff // 65200/256=254(次) /* 每次溢出递增次数为256(0x00~0xFF),所有总共需要溢出的次数= 65200/256 = 254. */ /* 当系统不配置工作频率时,即为16MHz工作频率下,定时器每递增一次需要时间为1/(16000000/128) = 8us。这是在不设置自由运行模式的情况下。 */ T3CTL |=0X10; //启动定时器 } //主函数 void main(void) { InitLed(); //调用初始化函数 InitT3(); while(1); } #pragma vector = T3_VECTOR //定时器T3 __interrupt void T3_ISR(void) { IRCON = 0x00; //清中断标志, 也可由硬件自动完成 if(++count>254) //254次中断后LED取反, //闪烁一轮(约为0.5 秒时间) { count = 0; //计数清零 LED1=~LED1; } } 七、串口通讯相关实验 1、串口 串口即串行通信接口,CC2530有两个串行通信接口:USART0 和USART1。两个USART具有相同的功能,可以通过设置相应 的寄存器来决定选用哪一个串口。它们能够分别运行于异步 USART 模式或者同步 SPI 模式。 USART0 对应的外部设备 IO 引脚关系为: P0_2 ------ RX P0_3 ------ TX USART1 对应的外部设备 IO 引脚关系为: P0_5 ------ RX P0_4 ------ TX TX:发送数据 RX:接收数据 2、串口发送:CC2530向PC发送内容 (1)CC2530 配置串口的一般步骤 在本次实验中,我们用到的是UART0 。 1 、 配置 IO, 使用外部设备功能。 此处配置 P0_2 和 P0_3 用作串口 USART0 2 、 配置相应串口的控制和状态寄存器。此处配置 USART0 的工作寄存器 3 、 配置串口工作的波特率。此处配置为波特率为 115200 (2)与串口配置相关的寄存器 1)PERCFG 它是外设I/O寄存器之一,称作:外设控制寄存器。它用来控制外设功能的备用位置。(备用位置:串口在I/O端口中有对应的位置。比如USART0当使用UART模式时,备用位置1根据映射表就知道,使用的是P0_2 P0_3端口来实现接收和发送数据的,其中P0_2对应TX,其中P0_3对应RX。)并且它是一个八位的寄存器,它的第0位是负责USART0 I/O控制。0:表示备用位置1 1:表示备用位置2。 例:串口0备用位置设置。 /*设置串口0位备用位置1*/ PERCFG &=~ 0x01; 2)P0SEL 它是一个八位功能寄存器。用来设置P0端口的每个引脚为通用I/O或者外设I/O。当某个引脚作为串口时就是外设功能。 3)P2DIR 它是一个八位方向寄存器,设置端口2。它除了设置端口P2_0~P2_4输出/输入方向之外,第6位和第7位还可以用来决定串口的优先级别。当串口0、串口1和定时器1共同使用CC2530的某些引脚时就需要设置其优先级别。 例:串口0优先级别设置 /*设置串口0为第一优先级别*/ P2DIR &=~0XC0;//0000 0000 4)U0CSR 八位寄存器。串口0的控制和状态寄存器。可以用来选择串口模式。它的第7位主要负责串口模式选择。 7: 0:选择SPI模式 1:选择UART模式 例:U0CSR寄存器配置 /*UART方式*/ U0CSR|=0x80; 5)U0GCR、U0BAUD 都是八位寄存器。U0GCR是串口0的通用控制寄存器。U0BAUD是串口0的波特率控制寄存器。它们通常是搭配使用来设置串口的波特率。波特率的计算公式: 通过寄存器U0GCR的4~0位的设置来决定BAUD_E 值,通过寄存器U0BAUD的7~0位的设置来决定 BAUD_M的值。 6)UTX0IF USART0发送完成中断标志。 (3)相关代码 #include #include #define uint unsigned int #define uchar unsigned char //定义LED的端口 #define LED1 P1_0 #define LED2 P1_1 //函数声明 void Delay_ms(uint); void initUART(void); void UartSend_String(char *Data,int len); char Txdata[14];//存放"HELLO WEBEE "共14个字符 //延时函数 void Delay_ms(uint n) { uint i,j; for(i=0;i { for(j=0;j<1774;j++); } } void IO_Init() { P1DIR = 0x03; //P1_0,P1_1 IO方向输出 P1INP |= 0X03; //打开下拉 LED1 = 0; LED2 = 0; } //串口初始化函数 void InitUART(void) { PERCFG = 0x00; //位置1 P0口 /* 外设控制寄存器 第0位负责。0:位置1 1:位置2 */ P0SEL |= 0x0c; //P0_2,P0_3用作串口(外部设//备功能) P2DIR &= ~0XC0; //P0优先作为UART0 /* 当串口0、串口1共同使用某些引脚 第6、7位负责 */ U0CSR |= 0x80; //设置为UART方式 /* 控制状态寄存器 第7位决定串口模式 0:同步SPI 1:异步UART */ U0GCR |= 11; U0BAUD |= 216; //波特率设为115200 /* 分别决定计算波特率公式中的两个未知数, 从而算出串口波特率 */ UTX0IF = 0; //UART0 TX中断标志初始值为0 } //串口发送字符串函数 void UartSend_String(char *Data,int len) { int j; for(j=0;j { /*U0BUF串口0数据接收和发送缓存寄存器。 主要功能是存放串口接收和发送的数据。 发送时,将数据写到这个寄存器中*/ U0DBUF = *Data++; while(UTX0IF == 0);//发送未结束等待 UTX0IF = 0; } } //主函数 void main(void) { /*系统的时钟配置*/ CLKCONCMD &= ~0x40; //设置系统时钟源为32MHZ//晶振 while(CLKCONSTA & 0x40); //等待晶振稳定为//32MHz CLKCONCMD &= ~0x47; //设置系统主时钟频率为//32MHZ IO_Init(); InitUART(); //将发送内容copy到Txdata; strcpy(Txdata,"HELLO WEBEE "); while(1) { //串口发送数据 UartSend_String(Txdata,sizeof("HELLOWEBEE ")); Delay_ms(500); //延时 LED1=!LED1; //标志发送状态 } } Ps:1、USB转串口驱动的安装: F:\WXCGSYS\second\网蜂科技(WeBee) ZigBee开发套件配套资源 (适用于2014年9月22日之后购买的用户)\开发软件和驱动\串口调试助手和驱动\PL2030驱动,双击PL2303驱动合集.rar,找到PL-2303 for webee.rar,双击PL-2303 for webee.exe即可完成安装。 2、打开串口调试助手的路径: F:\WXCGSYS\second\网蜂科技(WeBee) ZigBee开发套件配套资源 (适用于2014年9月22日之后购买的用户)\开发软件和驱动\串口调试助手和驱动,双击串口调试助手V2.1.exe。 2、串口接收和发送:串口接收到PC发送的数据,又将该数据 发送给PC (1)相关代码 #include #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0 //定义LED1为P10口控制 #define LED2 P1_1 //函数声明 void Delayms(uint xms); //延时函数 void InitLed(void); //初始化P1口 void InitUart(); //初始化串口 void Uart_Send_String(char *Data,int len); char Rxdata[50]; /* RXTXflag:接收发送标志 1:接收 2:发送 */ uchar RXTXflag = 1; char temp;//暂时存储数据的一个容器 uchar datanumber = 0;//数组下标 //延时函数 /* i=xms 即延时i毫秒 (16M晶振时候大约数,32M需要修改, 系统不修改默认使用内部16M) */ void Delayms(uint xms) { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x03; //P1_0、P1_1定义为输出 LED1 = 0; //LED灯熄灭 LED2 = 0; } //串口初始化函数 void InitUart() { /*对系统时钟的配置*/ CLKCONCMD &= ~0x40; // 设置系统时钟源为 //32MHZ晶振 while(CLKCONSTA & 0x40); // 等待晶振稳定 CLKCONCMD &= ~0x47; // 设置系统主时钟频率为 //32MHZ PERCFG = 0x00; //位置1 P0口 P0SEL = 0x3c; //P0_2,P0_3,P0_4,P0_5 //用作串口,第二功能 P2DIR &= ~0XC0; //P0 优先作为UART0 ,//优先级 U0CSR |= 0x80; //UART 方式 U0GCR |= 11; //U0GCR与U0BAUD配合 U0BAUD |= 216; // 波特率设为115200 UTX0IF = 0; //UART0 TX 中断标志初始//置位1(收发时候) /* U0CSR的第6位控制USART的接收使能 0:禁止接收器 1:使能接受器 */ U0CSR |= 0X40; //允许接收 /* 中断使能寄存器IEN0 第7位:负责开总中断相当于EA=1 第2位:负责USART0 RX中断使能和禁止 1:中 断使能 */ IEN0 |= 0x84; // 开总中断,接收中断 } //串口发送字符串函数 void Uart_Send_String(char *Data,int len) { { int j; for(j=0;j { U0DBUF = *Data++; while(UTX0IF == 0); //发送未结束等待 //发送结束自动为1 UTX0IF = 0; } } } /* 串口接收一个字符: 一旦有数据从串口传至CC2530, 则进入中断,将接收到的数据赋值给变量temp. */ #pragma vector = URX0_VECTOR __interrupt void UART0_ISR(void) { URX0IF = 0; // 清中断标志 temp = U0DBUF; } //主函数 void main(void) { InitLed();//调用初始化函数 InitUart(); while(1) { if(RXTXflag == 1) //接收状态 { LED1=1; //接收状态指示 if( temp != 0) { //'#'被定义为结束字符,最多能接收50 //个字符 if((temp!='#')&&(datanumber<50)) Rxdata[datanumber++] = temp; else { RXTXflag = 3; //进入发送状态 LED1=0; //关指示灯 } temp = 0; } } if(RXTXflag == 3) //发送状态 { LED2= 1; //开发送指示 U0CSR &= ~0x40; //禁止接收 //发送已记录的字符串。 Uart_Send_String(Rxdata,datanumber); U0CSR |= 0x40; //允许接收 RXTXflag = 1; // 恢复到接收状态 datanumber = 0; //指针归0 LED2 = 0; //关发送指示 } } } (2)对本次试验的一些理解 一开始程序从main函数开始执行,一直执行到程序的最后接受区都没有任何内容显示。直到我们在串口调试助手中点击“手动发送”(这个行为其实就表明有数据从串口传至CC2530),这实际上是一个触发中断事件,则接下来会进入中断,执行中断函数。这一个中断函数的最终结果就是将temp有了不为零的值。此时程序从while(1)开始执行,就会出现现象。 3、UART0- 控制 LED:依次发送L1# L2# 指令分别控制 LED1、LED2亮灭,波特率:115200bps (1)相关代码 #include #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0//定义LED1为P10口控制 #define LED2 P1_1 //函数声明 void Delayms(uint xms);//延时函数 void InitLed(void);//初始化P1口 void InitUart(); //初始化串口 void Uart_Send_String(char *Data,int len); char Rxdata[3]; uchar RXTXflag = 1; char temp; uchar datanumber = 0; //延时函数 /*i=xms 即延时i毫秒 (16M晶振时候大约数,32M需要修改, 系统不修改默认使用内部16M) */ void Delayms(uint xms) / { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x03; //P1_0、P1_1定义为输出 P1INP |= 0X03; //打开下拉 LED1 = 0; //LED1、2灯熄灭 LED2 = 0; } // 串口初始化函数 void InitUart() { /*系统时钟配置*/ CLKCONCMD &= ~0x40; // 设置系统时钟源为 //32MHZ晶振 while(CLKCONSTA & 0x40); // 等待晶振稳定 CLKCONCMD &= ~0x47; // 设置系统主时钟频率为 //32MHZ PERCFG = 0x00; //位置1 P0口 P0SEL = 0x3c; //P0_2,P0_3,P0_4,P0_5用作串口, //第二功能 P2DIR &= ~0XC0; //P0 优先作为UART0 ,优先级 U0CSR |= 0x80; //UART 方式 U0GCR |= 11; //U0GCR与U0BAUD配合 U0BAUD |= 216; // 波特率设为115200 UTX0IF = 1; //UART0 TX 中断标志初始置位1(收发时候) U0CSR |= 0X40; //允许接收 IEN0 |= 0x84; // 开总中断,接收中断 } //串口发送字符串函数 void Uart_Send_String(char *Data,int len) { int j; for(j=0;j { U0DBUF = *Data++; while(UTX0IF == 0); UTX0IF = 0; } } /*串口接收一个字符: 一旦有数据从串口传至CC2530, 则进入中断,将接收到的数据赋值给变量temp. */ #pragma vector = URX0_VECTOR __interrupt void UART0_ISR(void) { URX0IF = 0; // 清中断标志 temp = U0DBUF; } //主函数 void main(void) { InitLed();//调用初始化函数 InitUart(); while(1) { if(RXTXflag == 1) //接收状态 { if( temp != 0) { /* '#'被定义为结束字符, 最多能接收50个字符 */ if((temp!='#')&&(datanumber<3)) Rxdata[datanumber++] = temp; else { RXTXflag = 3; //进入发送状态 } temp = 0; } } if(RXTXflag == 3) //检测接收到的数据 { if(Rxdata[0]=='L') /* 很重要,ASICC码转成数字, 判断L后面第一个数 */ switch(Rxdata[1]-48) { case 1: { LED1=~LED1; //低高平点亮 break; } case 2: { LED2=~LED2; break; } } RXTXflag = 1; datanumber = 0; //指针归 0 } } } (2)对本次实验的一些理解 本次试验较上次串口实验,把CC2530向PC发数据改成了根据接收的数据来使LED亮起来。 八、AD 控制(自带温度计) 1、前言 ADC(模拟数字转化器)。它将模拟信号转化为数字信号。传感器的必要组成元件是敏感元件和转化元件。敏感元件所感受到的应当是模拟信号,把这种感受到的温度变化转化成数字显示在屏幕上,这个数字即为数字信号,这就需要用到转化元件,而ADC正是充当这样一个角色。我们现在所用到的这个CC2530开发板中它集成了一个温度传感器在里面,以后还可能有湿度传感器,光传感器等其他传感器。 2、试验目的 通过内部AD控制把温度信息通过串口发送给PC机。手摸着芯片,温度明显变大。 3、本次试验大致需要做的工作 (1)完成ADC的相关配置 (2)完成温度传感器的相关配置 (3)主函数调用 4、所需要配置的寄存器 (1)TR0(测试寄存器) 功能:将温度传感器启用。 (2)ATEST 功能:将温度传感器与 ADC 连接起来。 (3)ADCCON3(控制寄存器) 功能:选择用于额外转换的参考电压, 设置用于额外转化的抽取率,(决定转化需要的 时间和分辨率) 设置转化源。 (4)ADCCON1(控制寄存器) 功能:设置AD启动的方式,启动一个新的转化序列, 启动AD转化, 标志转化结束。 (5)ADCL(数据地位寄存器) 功能:用来存放模/数转化结果。 (6)ADCH(数据高位寄存器) 功能:用来存放模/数转化结果。 5、试验现象 运行后,在串口调试助手的接收区会显示出实时温度。 6、相关代码 #include #include "InitUART_Timer.h" //注意在option里设置路径 #include "stdio.h" // 温度传感器初始化函数 void initTempSensor(void) { DISABLE_ALL_INTERRUPTS(); //关闭所有中断 InitClock(); //设置系统主时钟为 32M TR0=0X01; //将温度传感器与ADC连接起来 ATEST=0X01; //开启温度传感器 } //读取温度传感器AD值函数 float getTemperature(void){ uint value; ADCCON3 = 0x3E; //选择1.25V为参考电压; 7~6:00 //14位分辨率(显示分辨率) //5~4:11,512抽取率 //单通道AD转换源为温度传感器, 3~0:1110 ADCCON1 |= 0x30; //选择ADC的启动模式为手动 5~4: ADCCON1 |= 0x40; //启动AD转化, 6:开始转化 /*ADCCON1的第7位用来设置转化结束。0:转换没有完成 1:转化完成*/ while(!(ADCCON1 & 0x80)); //等待 AD 转换完成 value = ADCL >> 4; //ADCL寄存器低 4 位无效 value |= (((UINT16)ADCH) << 4);//连接ADCH和ADCL,//并赋值给value //UINT16:无符号的短整型 /* 根据 AD 值,计算出实际的温度, 芯片手册有错,温度系数应该是4.5 /℃ 进行温度校正,这里减去5℃(不同芯片根据具体情况校正) */ return (value-1367.5)/4.5-5; } //主函数 void main(void) { char i; //循环变量 char TempValue[6];//存放温度字符串 float AvgTemp; InitUART0(); //初始化串口 (头文件定义) initTempSensor(); //初始化 ADC while(1) { AvgTemp = 0; for(i = 0 ; i < 64 ; i++) { AvgTemp += getTemperature(); AvgTemp=AvgTemp/2; //每次累加后除 2, //取得平均值 } /****温度转换成ascii码发送****/ TempValue[0] = (unsigned char)(AvgTemp)/10 +48; //十位 TempValue[1] = (unsigned char)(AvgTemp)%10 +48; //个位 TempValue[2]='.'; //小数点 TempValue[3]=(unsignedchar)(AvgTemp*10)%10+ 48; //十分位 TempValue[4]=(unsignedchar)(AvgTemp*100)%10+ 48; //百分位 TempValue[5]='\0'; //字符串结束符 UartTX_Send_String( TempValue,6);//头文件中 Delayms(2000); //使用32M晶振,故这里2000约//等于1S /* 公式中的48涉及到C语言中整型和字符型的转化: 将整型转化为字符型,加上0的ACSII码48,存 到字符型变量中。 将字符型转化为整型,减去0的ASCII码48,存 到整型变量中。 */ } } Ps: #include “InitUART_Timer.h” //注意在 option 里设置路径 九、睡眠唤醒 1、前言 Zigbee的特点就是远距离低功耗的无线传输设备,节点模块闲时可以进入睡眠模式,在需要传输数据时候进行唤醒,能进一步节省电量。 2、睡眠唤醒--中断方式唤醒 (1)实验目的 LED2闪烁5次后进入睡眠状态,通过按下按键S1产生外部中断进行唤醒,重新进入工作模式。 (2)实验内容 1)供电模式 CC2530的电源管理有5种供电模式(或者说是系统的工作模式),不同的供电模式选择的系统时钟源不同。它们是:主动模式、空闲模式、PM1、PM2、PM3。 PM3:此模式是最低功耗模式,稳压器的数字内核关闭,高频振荡器和低频振荡器都不运行,在此模式下,复位和外部I/O端口中断是该模式下仅有的运行功能。 2)电源管理寄存器 供电模式的选择是由相应的寄存器来控制的,电源管理寄存器有供电模式控制寄存器PCON、睡眠模式控制寄存器SLEEPCMD和睡眠模式控制状态寄存器SLEEPSTA。 ① PCON 功能:用来进行供电模式控制,设置系统进入睡眠或唤醒。第0位控制,其他位保留。 ② SLEEPCMD 功能:设置供电模式。 00:主动/空闲模式(PM0) 01:PM1 10:PM2 11:PM3 3)代码的基本思路 首先从main()开始执行,进入while,从第一次循环到第十次循环,if条件都是成立的。到第十一次循环的时候,count>10,则if条件不成立。进入睡眠模式。程序停在SysPowerMode(3); 这一行。这时,通过按下按键S1,这可以说成是一个触发中断的行为,即中断触发事件。然后中断向量会产生一个中断请求,紧接着执行中断处理函数,将系统唤醒。中间有个延时过程。然后程序又开始进入while循环。 4)相关代码 #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯和按键的端口 #define LED2 P1_1 //定义LED2为P11口控制 #define KEY1 P0_0 //函数声明 void Delayms(uint); //延时函数 void InitLed(void); //初始化P1口 void SysPowerMode(uchar sel); //系统工作模式 // 延时函数 void Delayms(uint xms) //i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x02; //P1_1定义为输出 P1INP |= 0X02; LED2 = 0; //LED2灯熄灭 P0INP &= ~0X01; //设置P0口输入电路模式为 //上拉/ 下拉 P0IEN |= 0X01; //P00设置为中断方式 PICTL |= 0x01; //下降沿触发 } /* 系统工作模式选择函数 * para1 0 1 2 3 * mode PM0 PM1 PM2 PM3 */ void SysPowerMode(uchar mode) { uchar i,j; i = mode; if(mode<4) { SLEEPCMD |= i;//设置系统睡眠模式 for(j=0;j<4;j++);//延时 PCON = 0x01;// 进入睡眠模式 ,通过中断打断 } else { PCON = 0x00;// 系统唤醒 ,通过中断打断 } } //主函数 void main(void) { uchar count = 0;//记录灯亮和灭的次数 InitLed(); //调用初始化函数 IEN1 |= 0X20; // 开P0口总中断 P0IFG |= 0x00; //清中断标志 EA = 1; //开启CC2530的总中断 while(1) { LED2=~LED2; if(++count>=10) { count=0; SysPowerMode(3); //5次闪烁后进入睡眠状态//PM3,等待按键S1中断唤醒 } Delayms(500); } } //中断处理函数-系统唤醒 #pragma vector = P0INT_VECTOR __interrupt void P0_ISR(void) { if(P0IFG>0) { P0IFG = 0; //清标志位 } P0IF = 0; SysPowerMode(4); //正常工作模式 } 3、睡眠唤醒——定时器方式唤醒 (1)实验目的 通过设置定时器在特定时间内进行唤醒,重新进入工作模式,每次唤醒LED2闪烁3下。 (2)实验内容 1)睡眠定时器 睡眠定时器主要用于设置系统进入和退出低功耗睡眠模式之间的周期。 2)代码执行顺序 首先程序从main()开始执行,进入main()中后,先调用一系列初始化函数。然后进入while循环,执行for语句,之后推出for循环。程序进入睡眠模式,睡眠时长为3秒。程序停在SysPowerMode(2);。此时系统内部的定时器自动在计数,当定时器计数的次数达到3*32768与比较器比较值相等,此时触发中断事件产生。中断向量产生一个中断信号,程序转去中断处理函数。系统被唤醒。程序进入下一次while循环。如此反复。 3)相关代码 #include #define uint unsigned int #define uchar unsigned char #define UINT8 unsigned char #define UINT16 unsigned int #define UINT32 unsigned long //定义控制LED灯的端口 #define LED2 P1_1 //定义LED2为P1.1口控制 //函数声明 void Delayms(uint); //延时函数 void InitLed(void); //初始化P1口 void SysPowerMode(uchar sel); //系统工作模式 //延时函数 void Delayms(uint xms) //i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x02; //P1_0、P1_1定义为输出 P1INP |= 0X02; //打开下拉 LED2 = 0; //LED2灯熄灭 } /* 系统工作模式选择函数 * para1 0 1 2 3 * mode PM0 PM1 PM2 PM3 */ void SysPowerMode(uchar mode) { uchar i,j; i = mode; if(mode<4) { SLEEPCMD |= i; // 设置系统睡眠模式 for(j=0;j<4;j++); //延时 PCON = 0x01; // 进入睡眠模式 ,通过中断打断 } else { PCON = 0x00; // 系统唤醒 ,通过中断打断 } } //初始化Sleep Timer(设定后经过指定时间自行唤醒) void Init_SLEEP_TIMER(void) { /* CC2530的睡眠定时器是一个24位的计数器, 可以用来作为唤醒中断,仅在PM0和PM2有效 当睡眠定时器处于定时比较功能时, 即定时器的值等于24位比较器的值时, 就发生一次定时器比较。 */ /* 寄存器ST0、ST1、ST2简单来说: 它们就是用来设置休眠定时器计数比较值, 记录定时器计数的次数。 ST2负责TX计数器高位:23~16 ST1负责TX计数器的15~8 ST0负责TX计数器的7~0 */ ST2 = 0X00; ST1 = 0X0F; ST0 = 0X0F; EA = 1; //开CC2530总中断 /* 中断使能寄存器IEN0的第5位控制 因此可以写成IEN0 |=0x20; */ STIE = 1; //使能睡眠定时器中断 STIF = 0; //初始化睡眠定时器中断标志 } /* 读取睡眠定时器的当前计数值, 顺序必须遵循:读ST0 →读ST1 →读ST2 写入睡眠定时器的比较值, 顺序必须遵循:写ST2 →写ST1 →写ST0 当定时器的计数值=比较值时,产生中断 */ //设置睡眠时间 void Set_ST_Period(uint sec) { UINT32 sleepTimer = 0; /* 把ST2:ST1:ST0赋值给sleeptimer, 保持位数一致,为下面的sleeptimer 赋值给ST2:ST1:ST0 变量的值在内存中都是以二进制的形式存在。 */ sleepTimer |= ST0; sleepTimer |= (UINT32)ST1 << 8; sleepTimer |= (UINT32)ST2 << 16; //|:是之前存的值还在,与本次存到值拼接。 /* 低速频率为32KHZ,(与供电模式设置有关) 故每秒定时器计数32768次, 因此比较是系统内部自动的去比较次数。 */ sleepTimer += ((UINT32)sec * (UINT32)32768); /* sleeptimer赋值给ST2:ST1:ST0 */ ST2 = (UINT8)(sleepTimer >> 16); ST1 = (UINT8)(sleepTimer >> 8); ST0 = (UINT8) sleepTimer; } //主函数 void main(void) { uchar i; //循环变量记录灯亮灭次数 InitLed(); //调用初始化函数 Init_SLEEP_TIMER(); //初始化SLEEPTIMER while(1) { for(i=0;i<6;i++) //闪烁3下 { LED2=~LED2; Delayms(200); } Set_ST_Period(3);//重新进入睡眠模式, //间隔3秒 SysPowerMode(2);//进入PM2低频晶振模式 } } //中断唤醒 #pragma vector = ST_VECTOR __interrupt void ST_ISR(void) { STIF = 0; //清标志位 SysPowerMode(4); //进入正常工作模式 } Ps:在void Init_SLEEP_TIMER(void)函数中写ST2 = 0X00; ST1 = 0X0f; ST0 = 0X0f。 而在无线龙的基础实验中,是寄存器全部给0的,ST2 = 0X00; ST1 = 0X00; ST0 = 0X00;ST是24位计数器0xFFFFFF,转成十进制是16777215,除以32768,等于511.99997秒,等于8分32秒。也就是在ST0=ST1 = ST2=0X00;前提下,最大定时时间是8分32秒。 十、看门狗相关实验 1、前言 看门狗,眼熟的名字。无论在普通的51 ,还是高级ARM 。都离不开他的身影。一个完整的系统总需要一个看门狗,在你程序跑飞的时候帮你一把,使系统重新进入工作状态。它无疑是世界上最忠诚的狗。不过可千万别忘了喂它。 跑飞:是因为干扰等原因,指令指针变成了不可预知的值 2、看门狗(WDT) Watchdog Timer 中文名看门狗。是一个定时器电路,一般有一个输入,叫喂狗,一个输出到MCU的RST端,MCU正常工作的时候,每隔一段时间输出一个信号到喂狗端,给 WDT 清零,如果超过规定的时间不喂狗,(一般在程序跑飞时),WDT 定时超过,就会给出一个复位信号到MCU,使MCU复位. 防止MCU死机. 看门狗的作用就是防止程序发生死循环,或者说程序跑飞。 3、实验目的 演示打开看门狗后没有喂狗系统不断复位(程序跑飞)的情况。 4、主要寄存器 WDCTL: 7~4:清除计数器的值。喂狗:先后写入 0xA,0x5。 3~2:设置看门狗的工作模式 10:看门狗模式 1~0:设置看门狗周期 00 :1 秒 5、相关代码 #include #define uint unsigned int #define uchar unsigned char //定义控制LED灯的端口 #define LED1 P1_0 #define LED2 P1_1//定义LED2为P11口控制 //函数声明 void Delayms(uint xms);//延时函数 void InitLed(void);//初始化P1口 //延时函数 void Delayms(uint xms) //i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //初始化程序 void InitLed(void) { P1DIR |= 0x03; //P1_0、P1_1定义为输出 P1INP |= 0X03; //打开下拉 LED1 = 0; //LED1灯熄灭 LED2 = 0; //LED2灯熄灭 } void Init_Watchdog(void) { WDCTL = 0x00; //看门狗周期选择寄存器。 WDCTL |= 0x08; //看门狗模式。 } void FeetDog(void) { WDCTL = 0xa0; WDCTL = 0x50; } //主函数 void main(void) { InitLed();//调用初始化函数 Init_Watchdog(); LED1=1; while(1) { LED2=~LED2; //仅指示作用。 Delayms(300); LED1=0; //通过注释测试,观察LED1,系统在不停复位。 FeetDog();//防止程序跑飞 } } 6、对代码的理解 从main()开始执行,进入while执行到 FeetDog();看门狗定时器的定时时间就大约到了,这是进行喂狗。完成后程序进入到下次while循环。如此反复。 十一、LCD12864液晶显示相关实验 1、前言 相信玩电子的对LCD12864都不会陌生,它是我们玩单片常用的东西。现用ZigBee CC2530来驱动我们的LCD12864 液晶屏。LCD调试较串口相比可操作性更强,更直观。不失为我们以后调试的方法。大家都知道2530的IO资源是非常紧缺的,所以我们使用串行方式驱动LCD12864液晶屏。 2、实验目的 使用LCD12864 显示我们定义的内容。 3、相关代码 #include /*设置后在文件夹Output就会出现LCD.h*/ #include "LCD.h"//需要在option中设置路径 #define uint unsigned int #define uchar unsigned char //函数声明 void Delayms(uint xms);//延时函数 //延时函数 void Delayms(uint xms)//i=xms 即延时i毫秒 { uint i,j; for(i=xms;i>0;i--) for(j=587;j>0;j--); } //主函数 void main(void) { /*定义显示信息*/ uchar *mes1 = "WeBee Technology"; uchar *mes2 = "ZigBee CC2530F256"; uchar *mes3 = "Let's study ZigBee!"; /* 将P0、P1端口的所有位都打开。 */ P0DIR = 0XFF; P1DIR = 0XFF; ResetLCD(); //复位LCD initLCDM(); //初始化LCD ClearRAM(); //请液晶缓存 delay_us(100); /*打印刚刚定义的信息*/ Print8(0,0,mes1); Print8(0,2,mes2); Print8(0,4,mes3); } 4、温馨提示 如果想看某一个函数的定义,可以打开LCD.h,选中要查看的函数,右击选择“Go To Definetinon Of”可以迅速找到函数定义的细节。 十二、Zigbee协议栈简介 1、什么是协议 协议是一系列的通信标准, 通信双方需要共同按照这一标准进行正常的数据发射和接收。 2、什么是协议栈 协议栈是协议的具体实现形式,通俗点来理解就是协议栈是协议和用户之间的一个接口,开发人员通过使用协议栈来使用这个协议的,进而实现无线数据收发。 3、个人可能不是很规范的理解 我们在进行无线数据收发的时候,我们必须遵循规则,即协议。那我们怎么去遵循呢?我们不能直接去使用啊,因为那么一大堆复杂的协议无从下手啊。因此我们把这些协议集中放在一个称之为协议栈的东西里面。当然我们也不是直接从这个协议栈里取出要使用的协议,协议栈为我们提供了一个API接口程序,我们只要去调用这个程序就OK了,不需要关心协议栈是具体怎么实现的。从而完成我们应用程序的开发。 4、学会使用ZigBee协议栈 举个例子,用户实现一个简单的无线数据通信时的一般步骤: (1) 组网:调用协议栈的组网函数、加入网络函数, 实现网络的建立与节点的加入。 (2) 发送:发送节点调用协议栈的无线数据发送函 数,实现无线数据发送。 (3) 接收:接收节点调用协议栈的无线数据接收函 数,实现无线数据接收。 十三、无线点灯相关实验 1、实验要求 两块 WeBee 模块通信,一个模块作发射,另外一个模块接收,发射模块依次按下按键 S1 ,改变接收模块 LED1 的亮灭的状态。实现无线点灯功能。 2、实验内容 (1)几个重要的关键词 CCM (操作模式) HAL ( 硬件抽象层) PAN( 个人局域网 ) RF (射频) RSSI( 接收信号强度指示) (2)实验流程 ① 首先解压WeBEE CC2530 BasiRF.zip生成文件夹WeBEE CC2530 BasicRF。 ② 打开WeBee CC2530 BasicRF\ide\srf05_cc2530\iar 路径里面的工程light_switch.eww( 无线点灯)。 ③ 在 light_switch.c 里面找到 main 函数,找到下面内容,把 appLight(); 注释掉,下载到发射模块。 appSwitch(); //节点为按键 S1 P0_0 // appLight(); //节点为指示灯 LED1 P1_0 ④ 找到相同位置,这次把 appSwitch();注释掉,下载到接收模块。 //appSwitch(); //节点为按键 S1 P0_0 appLight(); //节点为指示灯 LED1 P1_0 ⑤ 可以观察到比较明显的现象。 (3)例程设计的大体结构 (4)Basic RF和Basic RF Layer Basic RF包含了IEEE 802.15.4标准的数据包接收和发送功能。但并没有用到协议栈。只是实现了让两个节点进行简单的无线通信。 Basic RF Layer为双向无线通信提供了一个协议,通过这个协议可以实现数据的发送和接收。关于通信的安全性可以通过在工程文件里面定义SECURITY_CCM。在 Project- -—— >Option 里面就可以选择。 Basic RF的工作过程:启动、接收、发送。 要求: Basic RF 的初始化过程、Basic RF 的发射过程、 Basic RF 的接收过程,具体到每个层的功能函数不必过于纠结,只要明白函数的作用以及怎么去使用就OK。 (5)关键代码 void main(void) { uint8 appMode = NONE; //不设置模块的模式 /* Config basicRF*/ basicRfConfig.panId = PAN_ID; //个人局域网的ID basicRfConfig.channel = RF_CHANNEL; basicRfConfig.ackRequest = TRUE; /*预编译。用于加密。密钥安全通信,本例程不加密*/ #ifdef SECURITY_CCM basicRfConfig.securityKey = key; #endif /*Initalise board peripherals 初始化外围设备*/ halBoardInit(); halJoystickInit(); /* Initalise hal_rf 硬件抽象层的rf进行初始化*/ if(halRfInit()= =FAILED) { HAL_ASSERT(FALSE); } /*无线点灯实验,配置灯的。halLedSet(x)*/ halLedClear(2); // 关 LED2 halLedClear(1); // 关 LED1 /*选择性下载程序,发送模块和接收模块*/ appSwitch(); //节点为按键 S1 P0_0 appLight(); //节点为指示灯 LED1 P1_0 // Role is undefined. This code should not be //reached HAL_ASSERT(FALSE); } /*appSwitch()发射函数*/ static void appSwitch() { /*初始化液晶屏的。这里用不到*/ #ifdef ASSY_EXP4618_CC2420 halLcdClearLine(1); halLcdWriteSymbol(HAL_LCD_SYMBOL_TX, 1); #endif /*Initialize BasicRF,功能的一些配置*/ basicRfConfig.myAddr = SWITCH_ADDR; if(basicRfInit(&basicRfConfig)==FAILED){ HAL_ASSERT(FALSE); } pTxData[0] = LIGHT_TOGGLE_CMD;//就是一个标志。 // 关闭接收模块。 basicRfReceiveOff(); while (TRUE) //程序进入死循环 { if(halButtonPushed()==HAL_BUTTON_1)//按键S1按下 { basicRfSendPacket(LIGHT_ADDR,pTxData,APP_PAYLOAD_LENGTH);//发送数据包的一个函数,一定长度数据给确定地址 /*多发向按键的有关内容,不需要用到*/ halIntOff(); halMcuSetLowPowerMode(HAL_MCU_LPM_3); halIntOn(); } } } /*appLight()接收函数*/ static void appLight() { #ifdef ASSY_EXP4618_CC2420 halLcdClearLine(1); halLcdWriteSymbol(HAL_LCD_SYMBOL_RX, 1); #endif // Initialize BasicRF basicRfConfig.myAddr = LIGHT_ADDR; if(basicRfInit(&basicRfConfig)==FAILED) { HAL_ASSERT(FALSE); } basicRfReceiveOn(); while (TRUE) { /*检测是否接收数据*/ while(!basicRfPacketIsReady()); /*判断是否接收到数据*/ if(basicRfReceive(pRxData,APP_PAYLOAD_LENGTH,NULL)>0) { /*判断是否是当前发送模块所发来的数据*/ if(pRxData[0] == LIGHT_TOGGLE_CMD) { halLedToggle(1);//改变灯的状态 } } } } PS: 当工作区间打不开的时候,可能是因为路径的问题,这时可以把它放到桌面,然后就可以轻松的打开了。 十四、信号传输质量检测相关实验 1、概述 PER (误包率检测)实验是 BasicRF的第二个实验, 和无线点灯一样是没有使用协议栈的点对点通讯。误包率是衡量数据分组(数据包)在规定时间内传输精确性的指标。误包率=传输中的误包数/所传输的总包数*100% 2、实验现象: 两块 WeBee 模块通信,一个模块作发射,另外一个模块接收,接收模块通过串口不在 PC 机上显示当前的误包率、RSSI值和接收到数据包的个数。 3、实验代码 void main (void) { uint8 appMode;//用来选择模式(发送或接收) appState = IDLE;//模块初始状态不工作 appStarted = TRUE;//模块开始工作的标志 /*配置Basic RF的一些信息*/ basicRfConfig.panId = PAN_ID;//设置本机地址 basicRfConfig.ackRequest = FALSE;//目标确认//就置 true /*初始化外围设备*/ halBoardInit(); // 初始化 hal_rf if(halRfInit()==FAILED) { HAL_ASSERT(FALSE); } //点亮led1(P1.0)用以表示程序开始运行 halLedSet(1); // 在液晶屏上显示PER Tester utilPrintLogo("PER Tester"); // Wait for user to press S1 to enter menu //while (halButtonPushed()!=HAL_BUTTON_1); halMcuWaitMs(350);//等待发送模块发送数据 //halLcdClear();//清屏 //信道的设置 // basicRfConfig.channel = appSelectChannel(); basicRfConfig.channel = 0x0B; //模式设置 // appMode = appSelectMode(); #ifdef MODE_SEND appMode = MODE_TX; #else appMode = MODE_RX; #endif // Transmitter application if(appMode == MODE_TX) { // No return from here appTransmitter(); } // Receiver application else if(appMode == MODE_RX) { // No return from here appReceiver(); } // Role is undefined. This code should not be reached HAL_ASSERT(FALSE); } main.c 做了哪些事情: 1 、 一大堆的初始化(都是必须的) 2 、 设置信道,发射和接收模块的信道必须一致 3 、 选择为发射或者接受模式 /* appTransmitter 函数*/ static void appTransmitter() { uint32 burstSize=0;//设定进行一次测试所发送的数据包数量 uint32 pktsSent=0;//指示当前已经发了多少个数据包 uint8 appTxPower; uint8 n; /* 初始化Basic RF */ basicRfConfig.myAddr = TX_ADDR; if(basicRfInit(&basicRfConfig)==FAILED) { HAL_ASSERT(FALSE); } /* 设置输出功率 */ halRfSetTxPower(2); /* 设置进行一次测试所发送的数据包数量 */ burstSize = 1000; basicRfReceiveOff(); /* 配置定时器和IO */ appConfigTimer(0xC8); /* 初始化数据包载荷 */ txPacket.seqNumber = 0; for(n = 0; n < sizeof(txPacket.padding); n++) { txPacket.padding[n] = n; } /* 主循环 */ while (TRUE) { while(appStarted) { if (pktsSent < burstSize)//如果数据包还没有发送完,继续执行 { UINT32_HTON(txPacket.seqNumber); // 改变发送序号的字节顺序 basicRfSendPacket(RX_ADDR, (uint8*)&txPacket, PACKET_SIZE); /* 在增加序号前将字节顺序改回为主机顺序 */ UINT32_NTOH(txPacket.seqNumber); txPacket.seqNumber++; pktsSent++; appState = IDLE; halLedToggle(1); //改变LED1的亮灭状态 halMcuWaitMs(500); } else appStarted = !appStarted; pktsSent = 0; } } } appTransmitter 函数完成的任务: 1 、初始化 Basic RF 2 、设置发射功率 3 、设定测试的数据包量 4 、配置定时器和 IO 5 、初始化数据包载荷 6 、进行循环函数,不断地发送数据包,每发送完一次, 下一个数据包的序列号自加 1 /* appReceiver()*/ 接收函数的作用: 1 、 串口在此初始化 2 、 初始化 Basic RF 3 、 不断地接收数据包, 并检查数据包序号是否为期望值,作出相应处理 4 、 串口打印出,接收包的个数\误包率及上 32 个数据包的 RSSI值的平均值 PS:由于距离比较近,所以掉包不明显的,有兴趣的可以把发送节点拿到较远的地方,然后观察掉包率。或者先打开发送模块,再打开接收模块来测试掉包,会显示出掉包情况。 十五、协议栈工作原理介绍 协议就是通信双方必须遵循的规则。协议栈就是就是将这许多的协议封装成函数而集中在一起。我们开发项目的时候只需要去调用这其中的函数就可以使用对应的协议。为了开发人员使用方便,协议栈又建立了一个属于自己的操作系统,就好比我们电脑的Windows系统。因此我们可以将协议栈就是一个小型的操作系统(osal)。协议栈的工作模式是任务轮询。 F:\ZStack-CC2530-2.5.1a\Projects\zstack\Samples\SampleApp\CC2530DB,打开:SampleApp.eww。我们所要重点关注的就是SampleApp.c和Zmain.c。 int main( void ); |--osal_init_system(); |--osalInitTasks();//任务轮询的工作模式 |--SampleApp_Init( taskID ); 写入你想要实现的东西 |--osal_start_system(); 程序一旦执行进去了,就不出来了。 Ps:#if #endif 预编译,需要的时候才会去执行,节省资源。 十六、协议栈中的串口实验 之前,我们是用单片及来做串口实验,现在我们用协议栈来做串口实验。 实验要求: 模块通过串口发送“HELLO WEBEE ! ”给电脑串口调试助手打印出来。整个实验在协议栈(TI z-stack 2.5.1a )中进行。 分三步走, 步骤如下: 1 、串口初始化 MT_UartInit(); 2 、登记任务号 MT_UartRegisterTaskID(task_id); 3 、 串口发送 HalUARTWrite(0,”Hello World\n”,12); 十七、协议栈中的按键实验 1、按键流程分析 主要是告诉我们按键工作的基本原理,分三步: 第一步,相关初始化; 第二步,按键检测; 第三步、交给上层处理。 2、修改按键 IO 十八、一 小时实现无线数据传输 终端节点将数据 0123456789无线发送到协调器,协调器通过串口发送给 PC上位机显示出来。 协调器:所谓协调器,就是网络组织的管理者。针对一般的应用模式,在一个Zigbee网络形成之后,协调器不是必须的。它最主要的作用是,依据扫描情况,选择一些合适参数建立一个网络。 整个例程很简单分两部分,发送部分和接收部分 十九、串口透传,打造无线传输模块 1 、 ZigBee 模块接收到从 PC 机发送信息,然后无线发送出去。 2 、 ZigBee 模块接收到其它 ZigBee 模块发来的信息,然后发送给 PC 。 二十、网络通讯实验(单播、组 播、广播) 1、单播 就是点对点通信,也就是 2 个设备之间的通讯,不容许有第三个设备收到信息 2、组播 组播,就是把网络中的节点分组,每一个组员发出的信息只有相同组号的组员才能收到 3、广播 广播,最广泛的也就是 一个设备上发出的信息所有设备都能接收到。 二十一、Zigbee 协议栈网络管理 简介: 每个CC2530芯片出厂时候都有一个全球唯一的32位MAC地址。 当设备连入网络中的时候,每个设备都能获得由协调器分配的16位短地址,协调器默认地址(0x0000)。一般 网络就是通过短地址进行管理。 实验现象: 路由器(编号1)、终端设备(编号2)发送自己的定义的设备号给协调器,协调器通过接收到的设备号判断设备类型,并且获取设备的短地址,通过串口打印出来。 实验操作:将修改后的程序分别以协调器、路由器、终端的方式下载到3个或以上设备,协调器连接到PC机。上电后每个设备往协调器发送自身编号,协调器通过串口打印出来。 本实验在点播例程基础上进行 修改点播信息发送函数,代码如下: void SampleApp_SendPointToPointMessage( void ) { uint8 device; //设备类型变量 if ( SampleApp_NwkState == DEV_ROUTER ) device=0x01;//编号1表示路由器 else if (SampleApp_NwkState == DEV_END_DEVICE) device=0x02; //编号2表示终端 else device=0x03; //编号3表示出错 if ( AF_DataRequest( &Point_To_Point_DstAddr, //发送设备类型编号 &SampleApp_epDesc, SAMPLEAPP_POINT_TO_POINT_CLUSTERID,//点播ID是3 1, &device, &SampleApp_TransID, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { } else { // Error occurred in request to send. } } 修改完成后系统设备自动检测自己烧写的类型,然后发送对应的编号。 路由器编号为1,终端编号为2。 数据接收方面,对接收到的数据进行判断,区分路由器和终端设备。然后在数据包中取出16位短地址。通过串口打印出来。 短地址在数据包里的存放位置。依次是pkt---srcAddr---shortAddr 在SampleApp.c中找到接收函数 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { uint16 flashTime,temp; uint8 asc_16[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; //网蜂16进制转ASCII码表 switch ( pkt->clusterId ) { case SAMPLEAPP_POINT_TO_POINT_CLUSTERID: //点播ID是3 temp=pkt->srcAddr.addr.shortAddr; //读出数据包的16位短地址 if( pkt->cmd.Data[0]==1 ) //路由器 HalUARTWrite(0,"ROUTER ShortAddr:0x",19); //提示接收到数据 if( pkt->cmd.Data[0]==2 ) //终端 HalUARTWrite(0,"ENDDEVICE ShortAddr:0x",22); //提示接收到数据 /****将短地址分解,ASC码打印*****/ HalUARTWrite(0,&asc_16[temp/4096],1); HalUARTWrite(0,&asc_16[temp%4096/256],1); HalUARTWrite(0,&asc_16[temp%256/16],1); HalUARTWrite(0,&asc_16[temp%16],1); HalUARTWrite(0,"\n",1); // 回车换行 break; } } 实验结果:将修改后的程序分别以协调器、路由器、终端的方式下载到3个或以上设备,协调器连接到PC机。上电后每个设备往协调器发送自身编号,协调器通过串口打印出来。 1、在做“点亮第一个LED”的实验时,因为需要建一个工程,于是我建了一个文件夹,用中文命名,然后将自己的LED工程放在了该文件夹中,紧接着新建了一个文件LED.c,将该文件放入刚建好的工程中,一切准备就绪以后,就开始编译,连接,下载,运行。然而没有任何结果,而我的配置也不存在任何的问题,我想应该是路径的问题,于是将路径全部改成英文,重新编译,连接等,还是不行,经过不断测试,得知,原来当你的改成有所该变,你的配置也要随着它的改变而重新配置。最后点亮了LED1。 2、为什么在项目里只能存在一个文件?(当我成功运行一个程序之后,想在新建一个文件,敲完代码之后,不能编译运行,而上一个代码也不能运行了。) Ans:一个项目里面只能有一个主函数,不能完成两个实验。 3、在串口接收和发送实验当中,灯的现象和程序好像不太吻合。没有很好的解释。 1、《ZigBee实战演练》v3.1 2、《ZigBee技术开发CC2530单片机原理及应用》QST青软实训 编著 3、互联网搜索
按网蜂ST2 = 0X00; ST1 = 0X0f; ST0 = 0X0f; 0xffffff减去0xf0f,等于0xfff0f0,转成十进制16773360,在除以32768,等于511.88232秒,也近似约等于8分32秒。
这样一来,你的程序写的再正确,实际执行结果也是不对的。疑难问题
参考资料