【Zigbee】基础篇(4) Zigbee无线通信过程、无线发送温湿度信息
本篇文章:主要是Zigbee的无线通信,无线通信的过程,无线发送温湿度信息,抓包工具的使用。正文如下:一、无线通信1)Zigbee无线通信是什么?2)信道和频段二、无线通信收发温湿度信息三、无线通信过程分析
大家好,我是皮皮猫吖!
每文一言:你所看到的惊艳,都曾经被平庸所历练!
本篇文章:
主要是Zigbee的无线通信,无线通信的过程,无线发送温湿度信息,抓包工具的使用。
正文如下:
一、无线通信
1)Zigbee无线通信是什么?
Zigbee无线通信,需要高频的载波来提供发射效率。Zigbee模块之间要想正常的收发数据,接收模块必须把接收频率和发射模块的载波频率设置一致。
2)信道和频段
信道:无线通信的通道。
频段:载波的频率落在某些频率区段,把这些区段叫做频段。
Zigbee使用的频段主要是2.4G频段、915M频段和896M频段。在这些频段上,Zigbee总共有27个载波可以进行通信,载波也叫做信道。
3)频段上分布的信道数
2.4G频段:分布16个信道
915M频段、896M频段:分布11个信道
TI公司所有支持Zigbee底层协议的芯片只能在2.4G频段的16个信道里进行通信。
信道 | 频段 |
---|---|
11 | 2405M |
12 | 2410M |
… | … |
25 | 2475M |
26 | 2480M |
二、例子:无线通信收发数据【A节点通过无线的方式向B节点发送采集到的温湿度信息;B节点通过无线的方式向A节点发送数字"6"】
1)A节点代码
① main.c
#include<ioCC2530.h>
#include"74LS164_8LED.h"
#include"DHT11.h"
#include <stdio.h>
#include <string.h>
#define SENDVAL 6
int count = 0;
char SendPacket[]={0x11,0x61,0x88,0x00,0x07,0x20,0xEF,0xBE,0x20,0x50};
DHT dh; // 用来接收DHT11发过来的40位数据
//第一个字节0x0C含义,表示这个字节后面还有12个字节要发送
//第5 6个字节表示的是PANID
//第7 8个字节是无线模块目标设备的网络地址 0xBEEF(目标模块地址)
//第9 10就是本地模块的网络地址(自己的地址)
//第11个字节是我们有用的数据
// CRC校验码 12 13个字节 硬件自动追加
//延时函数
void Delay()
{
int y,x;
for(y=1000;y>0;y--)
for(x=30;x>0;x--);
}
//cc2530切换到32M晶振
void Init32M()
{
SLEEPCMD &=0xFB;//1111 1011 开启2个高频时钟源
while(0==(SLEEPSTA & 0x40));// 0100 0000 等待32M稳定
Delay();
CLKCONCMD &=0xF8;//1111 1000 不分频输出
CLKCONCMD &=0XBF;//1011 1111 设置32M作为系统主时钟
while(CLKCONSTA & 0x40); //0100 0000 等待32M成功成为当前系统主时钟
}
//外部中断初始化
void KeysIntCfg()
{//Key3 Key4 Key5
IEN2|=0x10;//开P1IE组中断
P1IEN|=0x02;//开Key3组内中断
PICTL|=0x02;//设置P1_1为下降沿
EA=1; //开总中断
}
/*
//串口上发送字节
void Uart0SendByte(char SendByte)
{
U0DBUF=SendByte; //把我们收到的数据通过串口再返回发出去
while(UTX0IF==0);
UTX0IF=0;
}*/
//射频的初始化
void halRfInit(void)
{
//默认配置:
EA=0;
FRMCTRL0 |= 0x60;
// Recommended RX settings
TXFILTCFG = 0x09;
AGCCTRL1 = 0x15;
FSCAL1 = 0x00;
// enable RXPKTDONE interrupt
RFIRQM0 |= 0x40;//把射频接收中断打开(射频接收到数据,进入接收中断函数)
// enable general RF interrupts
IEN2 |= 0x01;
FREQCTRL =(11+(24-11)*5);//(MIN_CHANNEL + (channel - MIN_CHANNEL) * CHANNEL_SPACING);
//设置载波为2475M,25号信道
//设置个域网ID:2个字节的地址
PAN_ID0=0x07;
PAN_ID1=0x20; //0x2007
//halRfRxInterruptConfig(basicRfRxFrmDoneIsr);
RFST = 0xEC;//清空接收缓冲器的数据(命令)
RFST = 0xE3;//开启接收使能
EA=1;
}
void RFSend(char *pstr,char len)
{
char i;
RFST = 0xEC; //确保接收是空的
RFST = 0xE3; //清接收标志位
while (FSMSTAT1 & 0x22);//等待射频发送准备好
RFST = 0xEE;//确保发送队列是空
RFIRQF1 &= ~0x02;//清发送标志位
//为数据发送做好准备工作
//RFD 为128字节的接收缓冲区/为128字节的发送缓冲区
//RFD = xxx;表示向接收缓冲器写入数据
//xxx = RFD;表示从发送缓冲区读取数据
for(i=0;i<len;i++)
{
RFD=pstr[i];
} //循环的作用是把我们要发送的数据全部压到发送缓冲区里面
RFST = 0xE9; //这个寄存器一旦被设置为0xE9,发送缓冲区的数据就被发送出去
while(!(RFIRQF1 & 0x02) );//等待发送完成
RFIRQF1 = ~0x02;//清发送完成标志
}
void main()
{
LS164_Cfg();//74LS164控制数码管的初始化
Init32M(); //主时钟晶振工作在32M (无线通信,晶振必须32M)
KeysIntCfg(); //外部中断初始化
Init_Uart();
halRfInit();//无线通信的初始化 初始化相关的寄存器,配置工作信道,和PANID
SHORT_ADDR0=0x50;
SHORT_ADDR1=0x20;//设置本模块地址 设置本模块的网络地址0x2050
//大小端模式问题,
LS164_BYTE(1);//数码管显示数字1
while(1);
}
void RevRFProc()
{
static char len;
static char ch;
len=ch=0;
RFIRQM0 &= ~0x40;
IEN2 &= ~0x01;
EA=1;
//读缓冲寄存器中的数据
//读取到了封装好的数据中的第一个字节,第一个字节的数据存储的是后面还要多少字节的数据
len=RFD;//读第一个字节判断这一串数据后面还有几个字节;
//读到 len=0x0C 12
while (len>0)
{//只要后面还有数据那么就把它们都从接受缓冲区取出来
ch=RFD;
if(3==len)
{
//如果倒数第三个字节等于7,那么我们把LED0取反
//读取到的第三个字节,就是在发送模块中编写的标志位
//读到的是几,就显示几
if(ch==6){
if(count==10){
count=0;
}
LS164_BYTE(count++);
}
}
len--;
}
EA=0;
// enable RXPKTDONE interrupt
RFIRQM0 |= 0x40;
// enable general RF interrupts
IEN2 |= 0x01;
}
#pragma vector=P1INT_VECTOR
__interrupt void Key3_ISR() //P1_1
{
char *dht11;
if(0x02 & P1IFG)
{
Delay();
if(0==P1_1)
{
P1DIR |=0X01;
P1_0 ^=1;
if(Read_DHT()){
sprintf(dht11, "湿度:%2d.%d 温度%2d.%d\n", dh.humi_H,dh.humi_L,dh.temp_H,dh.temp_L);
SendPacket[10] = '0'+dh.humi_H/10;
SendPacket[11] = '0'+dh.humi_H%10;
SendPacket[12] = '0'+dh.humi_L%10;
SendPacket[13] = '0'+dh.temp_H/10;
SendPacket[14] = '0'+dh.temp_H%10;
SendPacket[15] = '0'+dh.temp_L%10;
//Uart_Send_String(dht11, 20);
}
RFSend(SendPacket,18);
}
}
P1IFG=0;
P1IF=0;
}
#pragma vector=RF_VECTOR
__interrupt void RF_IRQ(void)
{//这个是射频中断函数,当小灯模块接收到开关模块发送来的数据时,小灯模块的CPU就会进入中断函数执行
EA=0;
if( RFIRQF0 & 0x40 )
{
RevRFProc();//把发送模块发送过来的数据取出来
RFIRQF0&= ~0x40; // Clear RXPKTDONE interrupt
}
S1CON= 0; // Clear general RF interrupt flag
RFST = 0xEC;//清接收缓冲器
RFST = 0xE3;//开启接收使能
EA=1;
}
② 74LS164_8LED
- 74LS164_8LED.h
#ifndef __74LS164_8LED_H__
#define __74LS164_8LED_H__
#include<ioCC2530.h>
#define LS164_DATA P1_3
#define LS164_CLK P1_2
#define UCHAR unsigned char
void LS164_Cfg();
void LS164_BYTE(UCHAR Data);//P1.3 DATA P1.2 CLK
#endif
- 74LS164_8LED.c
#include<ioCC2530.h>
#include"74LS164_8LED.h"
static UCHAR LED_Map[]={0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x00};
void LS164_Cfg()
{
P1SEL &=~0x0C;//xxxx 00xx 配置为普通IO模式
P1DIR |=0x0C;//xxxx 11xx 配置为输出模式
}
void LS164_BYTE(UCHAR Index) //P1.3 DATA P1.2 CLK
{
UCHAR i=0;
UCHAR Data=LED_Map[Index];
for(;i<8;i++)
{
if(0x80 & Data)
{
LS164_DATA=1;
}
else
{
LS164_DATA=0;
}
Data=Data << 1;
LS164_CLK=0;
LS164_CLK=1;
}
}
③ DHT11
- DHT11.h
#ifndef __DHT11_H_
#define __DHT11_H_
#include<iocc2530.h>
#define DATA_PIN P0_0 // DHT11 的数据引脚
typedef unsigned char uchar;
/* 定义一个结构体,用来存储DHT11传出来的40位数据 */
typedef struct{
uchar temp_H;
uchar temp_L;
uchar humi_H;
uchar humi_L;
uchar crc;
}DHT;
/* 微秒延时 */
void Delay_us(int time);
/* 毫秒延时 */
void Delay_ms(int Time);
/* 定义DHT11的数据引脚为 输出 状态 */
void DHT_OUT(void);
/* 定义DHT11的数据引脚为 输入 状态 */
void DHT_IN(void);
/* 读取一个字节的数据(在DHT11响应主机之后调用该函数) */
uchar Read_Byte(void);
/*
*主机与DHT11通信
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
*/
uchar Read_DHT(void);
void Init_32M();
void Init_Uart();
/* 串口发送一个字符 */
void Uart0_SendCh(char ch);
#endif
- DHT11.c
#include <iocc2530.h>
#include <stdio.h>
#include <string.h>
#include "DHT11.h"
extern DHT dh; // 用来接收DHT11发过来的40位数据
/* 微秒延时 */
void Delay_us(int time)
{
while(time--){
asm("NOP");
}
}
/* 毫秒延时 */
void Delay_ms(int Time)
{
while(Time--){
Delay_us(1000);
}
}
/* 定义DHT11的数据引脚为 输出 状态 */
void DHT_OUT(void){
P0SEL &= ~0x01;
P0DIR |= 0x01;
}
/* 定义DHT11的数据引脚为 输入 状态 */
void DHT_IN(void){
P0SEL &= ~0x01;
P0DIR &= ~0x01;
P0INP &= ~0x01;
P2INP &= ~0x20;
}
/* 读取一个字节的数据(在DHT11响应主机之后调用该函数) */
uchar Read_Byte(void){
uchar temp, i;
/* 一位一位的读取 */
for(i=0;i<8;i++){
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DATA_PIN==0);
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 30 us后的电平即可区别这两个状
*/
Delay_us(30); //延时30us 这个延时需要大于数据0持续的时间即可
/* 30 us后仍为高电平表示数据“1” */
if(DATA_PIN==1){
while(DATA_PIN==1); //等待数据1的高电平结束
temp |= (uchar)(0x01<<7-i); //把第7-i位置1,MSB先行
}
else{ // 30 us后为低电平表示数据“0”
temp &= (uchar)~(0x01<<7-i); //把第7-i位置0,MSB先行
}
}
return temp;
}
/*
*主机与DHT11通信
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
*/
uchar Read_DHT(void){
Delay_ms(1200);//等待DHT11稳定,延时大于1秒
DHT_OUT(); //输出模式
DATA_PIN=0; //主机拉低
Delay_ms(18); //延时18ms
DATA_PIN=1; //总线拉高
Delay_us(40); //主机延时40us
DHT_IN(); //主机设为输入 判断从机响应信号
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DATA_PIN==0){
while(DATA_PIN==0); //轮询直到从机发出 的80us 低电平 响应信号结束
while(DATA_PIN==1); //轮询直到从机发出的 80us 高电平 标置信号结束
/*开始接收数据*/
dh.humi_H=(uchar)Read_Byte();
dh.humi_L=(uchar)Read_Byte();
dh.temp_H=(uchar)Read_Byte();
dh.temp_L=(uchar)Read_Byte();
dh.crc=(uchar)Read_Byte();
/*读取结束,引脚改为输出模式*/
DHT_OUT();
DATA_PIN=1; //主机拉高
/*检查读取的数据是否正确*/
if(dh.humi_H+dh.humi_L+dh.temp_H+dh.temp_L==dh.crc)
return 1;
else
return 0;
}
else
return 0;
}
void Init_32M(){
SLEEPCMD &=0xFB;//1111 1011 开启2个高频时钟源
while(0==(SLEEPSTA & 0x40));// 0100 0000 等待32M稳定
Delay_us(63);
CLKCONCMD &=0xF8;//1111 1000 不分频输出
CLKCONCMD &=0XBF;//1011 1111 设置32M作为系统主时钟
while(CLKCONSTA & 0x40); //0100 0000 等待32M成功成为当前系统主时钟
SLEEPCMD |=0x04;
}
void Init_Uart()
{
Init_32M(); //使用32MHz的外部晶振
PERCFG&=~0x01; //有2个备用位置,0使用备用位置1;1使用备用位置2
P0SEL |= 0x0C; //P0_2 RXD P0_3 TXD 外设功能 0000 1100
U0CSR |= 0xC0; //串口接收使能 1100 0000 工作UART模式+允许接受
U0UCR |= 0x00; //无奇偶校验,1位停止位
U0GCR |= 11; //U0GCR与U0BAUD配合
U0BAUD |= 216; // 波特率设为115200
IEN0 |= 0X04; //开串口接收中断 'URX0IE = 1',也可以写成 URX0IE=1;
EA=1;
}
/* 串口发送一个字符 */
void Uart0_SendCh(char ch)
{
U0DBUF = ch; //将该字符写入串口数据发送寄存器
while(UTX0IF == 0); //检查标志位
UTX0IF = 0; //将标志位清零
}
/* 串口发送一个字符串 */
void Uart_Send_String(char *Data,int len)
{
{
int j;
for(j=0;j<len;j++) //一个字符一个字符的发送
{
Uart0_SendCh(*Data++);
}
}
}
2)B节点代码
① main.c
#include<ioCC2530.h>
#include"74LS164_8LED.h"
#include<stdio.h>
#include<string.h>
#define SENDVAL 6
int count = 0;
char SendPacket[]={0x0c,0x61,0x88,0x00,0x07,0x20,0x50,0x20,0xBE,0xEF,SENDVAL};
void Delay()
{
int y,x;
for(y=1000;y>0;y--)
for(x=30;x>0;x--);
}
//cc2530切换为32M晶振
void Init32M()
{
SLEEPCMD &=0xFB;//1111 1011 开启2个高频时钟源
while(0==(SLEEPSTA & 0x40));// 0100 0000 等待32M稳定
Delay();
CLKCONCMD &=0xF8;//1111 1000 不分频输出
CLKCONCMD &=0XBF;//1011 1111 设置32M作为系统主时钟
while(CLKCONSTA & 0x40); //0100 0000 等待32M成功成为当前系统主时钟
}
//串口初始化
void Uart0_Cfg()
{
PERCFG &=0xFE;//把这个寄存器的第零位强行清零 1111 1110
//就是把串口0的脚位置配置在备用位置1 即P0_2 P0_3
P0SEL |=0x0C;//让P0_2 P0_3这两个脚工作在片上外设模式,而不是普通IO口 0000 1100
U0CSR |=0xC0;
U0UCR =0; //串口0 典型的串口配置 校验位 停止位之类的东西
U0GCR =11;
U0BAUD =216;//就是重官方数据手册中波特率表格中参照115200时的 配置值,前提是系统时钟在32M
IEN0 |=0x04; //开接收数据的中断 0000 0100
EA=1;
}
//外部中断初始化
void KeysIntCfg()
{//Key3 Key4 Key5
IEN2|=0x10;//开P1IE组中断
P1IEN|=0x02;//开Key3组内中断
PICTL|=0x02;//设置P1_1为下降沿
EA=1; //开总中断
}
//串口上发送字节
void Uart0SendByte(char SendByte)
{
U0DBUF=SendByte; //把我们收到的数据通过串口再返回发出去
while(UTX0IF==0);
UTX0IF=0;
}
/* 串口发送一个字符串 */
void Uart_Send_String(char *Data,int len)
{
{
int j;
for(j=0;j<len;j++) //一个字符一个字符的发送
{
Uart0SendByte(*Data++);
}
}
}
//射频初始化
void halRfInit(void)
{
EA=0;
FRMCTRL0 |= 0x60;
// Recommended RX settings
TXFILTCFG = 0x09;
AGCCTRL1 = 0x15;
FSCAL1 = 0x00;
// enable RXPKTDONE interrupt
RFIRQM0 |= 0x40;
// enable general RF interrupts
IEN2 |= 0x01;
//选择25号信道
FREQCTRL =(11+(24-11)*5);//(MIN_CHANNEL + (channel - MIN_CHANNEL) * CHANNEL_SPACING);
//设置个域网ID:进行通信的模块,个域网ID必须相同
PAN_ID0=0x07;
PAN_ID1=0x20;
//halRfRxInterruptConfig(basicRfRxFrmDoneIsr);
RFST = 0xEC;//清接收缓冲器
RFST = 0xE3;//开启接收使能
EA=1;
}
void RFSend(char *pstr,char len)
{
char i;
RFST = 0xEC; //确保接收是空的
RFST = 0xE3; //清接收标志位
while (FSMSTAT1 & 0x22);//等待射频发送准备好
RFST = 0xEE;//确保发送队列是空
RFIRQF1 &= ~0x02;//清发送标志位
//为数据发送做好准备工作
//RFD 为128字节的接收缓冲区/为128字节的发送缓冲区
//RFD = xxx;表示向接收缓冲器写入数据
//xxx = RFD;表示从发送缓冲区读取数据
for(i=0;i<len;i++)
{
RFD=pstr[i];
} //循环的作用是把我们要发送的数据全部压到发送缓冲区里面
RFST = 0xE9; //这个寄存器一旦被设置为0xE9,发送缓冲区的数据就被发送出去
while(!(RFIRQF1 & 0x02) );//等待发送完成
RFIRQF1 = ~0x02;//清发送完成标志
}
void main()
{
LS164_Cfg();//74LS164控制数码管的初始化
Init32M(); //主时钟晶振工作在32M
KeysIntCfg(); //外部中断初始化
Uart0_Cfg();
halRfInit();
//Uart0_Cfg();
//设置本模块地址
SHORT_ADDR0=0xEF;
SHORT_ADDR1=0xBE;//设置本模块地址 0xBEEF
LS164_BYTE(2);
while(1);
}
void RevRFProc()
{
static char len;
static char ch;
char *str;
len=ch=0;
RFIRQM0 &= ~0x40;
IEN2 &= ~0x01;
EA=1;
//读缓冲寄存器中的数据
//读取到了封装好的数据中的第一个字节,第一个字节的数据存储的是后面还要多少字节的数据
len=RFD;//读第一个字节判断这一串数据后面还有几个字节;
//读到 len=0x0C 12
while (len>0)
{//只要后面还有数据那么就把它们都从接受缓冲区取出来
ch=RFD;
if(len<=8 && len>2)
{
//如果倒数第三个字节等于7,那么我们把LED0取反
//读取到的第三个字节,就是在发送模块中编写的标志位
//读到的是几,就显示几
if(len==8){
sprintf(str, "湿度:");
Uart_Send_String(str, 5);
}
if(len==6){
Uart0SendByte('.');
}
if(len==5){
sprintf(str, " 温度:");
Uart_Send_String(str, 6);
}
if(len==3){
Uart0SendByte('.');
}
Uart0SendByte(ch);
if(len==3){
Uart0SendByte('\n');
}
}
len--;
}
if(count==10){
count = 0;
}
LS164_BYTE(count++);
EA=0;
// enable RXPKTDONE interrupt
RFIRQM0 |= 0x40;
// enable general RF interrupts
IEN2 |= 0x01;
}
#pragma vector=RF_VECTOR
__interrupt void RF_IRQ(void)
{//这个是射频中断函数,当小灯模块接收到开关模块发送来的数据时,小灯模块的CPU就会进入中断函数执行
EA=0;
if( RFIRQF0 & 0x40 )
{
RevRFProc();//把发送模块发送过来的数据取出来
RFIRQF0&= ~0x40; // Clear RXPKTDONE interrupt
}
S1CON= 0; // Clear general RF interrupt flag
RFST = 0xEC;//清接收缓冲器
RFST = 0xE3;//开启接收使能
EA=1;
}
#pragma vector=P1INT_VECTOR
__interrupt void Key3_ISR() //P1_1
{
if(0x02 & P1IFG)
{
Delay();
if(0==P1_1)
{
P1DIR |=0X01;
P1_0 ^=1;
RFSend(SendPacket,11);
}
}
P1IFG=0;
P1IF=0;
}
② 74LS164_8LED
- 74LS164_8LED.h
#ifndef __74LS164_8LED_H__
#define __74LS164_8LED_H__
#include<ioCC2530.h>
#define LS164_DATA P1_3
#define LS164_CLK P1_2
#define UCHAR unsigned char
void LS164_Cfg();
void LS164_BYTE(UCHAR Data);//P1.3 DATA P1.2 CLK
#endif
- 74LS164_8LED.c
#include<ioCC2530.h>
#include"74LS164_8LED.h"
static UCHAR LED_Map[]={0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x00};
void LS164_Cfg()
{
P1SEL &=~0x0C;//xxxx 00xx 配置为普通IO模式
P1DIR |=0x0C;//xxxx 11xx 配置为输出模式
}
void LS164_BYTE(UCHAR Index) //P1.3 DATA P1.2 CLK
{
UCHAR i=0;
UCHAR Data=LED_Map[Index];
for(;i<8;i++)
{
if(0x80 & Data)
{
LS164_DATA=1;
}
else
{
LS164_DATA=0;
}
Data=Data << 1;
LS164_CLK=0;
LS164_CLK=1;
}
}
三、如果后续需要使用Zigbee进行无线通信的话,只需要在下面对应的部分修改为自己需要使用的代码即可:
1)数据包代码
PANID:这是一个2个字节的编码,用来区别不同的Zigbee无线局域网,个域网ID。
//无线数据包的内容
#define SENDVAL 5
char SendPacket[]={0x0C,0x61,0x88,0x00,0x07,0x20,0xEF,0xBE,0x20,0x50,SENDVAL};
//第一个字节0x0C含义,表示这个字节后面还有12个字节要发送
//第5 6个字节表示的是PANID
//第7 8个字节是无线模块目标设备的网络地址 0xBEEF(目标模块地址)
//第9 10就是本地模块的网络地址(自己的地址)
//第11个字节是我们有用的数据
// CRC校验码 12 13个字节 硬件自动追加
2)射频初始化代码
3)A节点射频接收无线数据处理代码【发送节点接收无线数据处理代码】
4)B节点射频接收无线数据处理代码【接收节点处理无线数据代码】
5)A节点封装数据包过程【发送节点封装数据过程】
四、无线通信过程
1)在Zigbee的无线局域网中,节点按照网络里面的功能划分为:协调器、路由器、中断。
硬件可以一模一样,节点之所以在网络里表现不同的功能,是因为它们下载了不同功能版本的代码。
我们说一个模块到底是协调器、路由器还是终端?前提是它必须在一个Zigbee无线局域网里,如果它还没有入网,那么它仅仅是一个下载了响应功能代码的模块而已。
下载了路由器和终端代码的模块,上电后第一件事是去寻找网络,请求加入;网络是不会平白无故产生的,所以创建网络这个工作由下载了协调器代码的模块来完成,下载协调器代码的模块上电的第一件事是去创建网络。
任何一个网络,第一个节点一定是该网络的协调器,一个网络里有且仅有一个协调器。
2)协调器
协调器:创建一个无线局域网络的模块叫做协调器。
协调器在创建网络之后,协调器的地位和路由器将是一样的,都是负责把终端的消息发送出去。
3)其他模块入网的过程
任何一个Zigbee模块要加入到某个网络,一定要一个处于该网络里的节点作为介绍人,并且这个介绍人不能是终端节点。
在节点加入到网络以后,介绍人节点和被介绍加入的节点互为父子关系,介绍人节点是被介绍加入节点的父节点,被介绍加入节点是介绍节点的子节点。
当被加入节点有多个介绍人可以选择加入的时候,根据相对于被加入节点的信号强度等一些其他的参数,选择最佳的介绍人节点加入。
Zigbee网络组建以后,网络里的节点可以进行相互通信,数据通信的方式有4种:单播、广播、组播、绑定。
4)各个功能型模块【路由器、终端、协调器】入网前的操作
① 路由器节点
路由器在加入局域网络之前,会一直发送信标请求帧,信标请求帧的作用是,让在它附近的所有具备介绍人资格的节点,都回复信标帧,这些返回的信标帧被这个想要加入的无线模块拿到,路由器模块通过这些信标帧,选出最佳介绍人节点,请求加入。
② 终端节点
终端节点在入网前的行为和下载了路由器代码模块的入网前行为是一样的。
③ 协调器节点
协调器在创建局域网络之前,会发送了一帧信标请求帧,发送这一帧也会得到周围具备介绍人资格的节点回复信标帧,但是协调器拿到这些信标帧,用来判断周围的环境情况,为创建网络做准备。
当协调器创建网络成功以后,就会发送一个数据帧,这个帧里面可以看到协调器的地址0x0000、PANID(创建的网络ID),可以把这个帧叫做网络连接状态帧。
五、路由器与协调器无线通信的过程:
1)协调器发出网络连接状态帧,表明协调器稳定工作,与路由器入网无关
- Dest Address:目的地址
- Source Address:自己的网络地址
- Dest PAN:目的网络
- 0XFFFF:表示是广播
2)路由器模块发出信标请求帧,用于发现周围的网络,请求加入
3)协调器模块发出信标请求帧,路由器模块在拿到这个帧之后,可以得到协调器模块相对于自己的信号强度,判断是不是最佳介绍人。
- Source PAN:自己当前所处于的网络
- Source Address:自己的网络地址
4)重复2-3的过程
5)补充:ACK、物理地址
在Zigbee网络里,如果一个模块发出的射频帧,非常明确的指明接收目标节点的地址,那么目标节点在接收到这个帧以后,硬件会自动回复一个ACK,表明已经收到了。
TI在CC2530出厂的时候在flash上固化一个8个字节编号唯一的值,这个值是这颗芯片的物理地址,又叫MAC地址,或叫IEEE地址,3种说法说的都是一回事。
6)路由器模块做出的判断帧:发送到协调模块的帧
这个帧的作用是,在前面路由器模块收到了协调器模块的信标帧,通过信标帧判断协调器是当前路由器模块的最佳介绍人。
路由器模块发送这一帧是告诉协调器,你是我当前的最佳介绍人,请你从作为我入网的介绍人,介绍我入网。并且在这个帧上携带了路由器自己的MAC地址。这个MAC地址是介绍人模块(协调器模块)给被介绍人模块(路由器模块)分配网络的地址的依据。
- Source Address:路由器的MAC地址
- Dest Address:目的地址
- Dest PAN:目的网络
- Source PAN:自己所在的网络
7)协调器发送给路由器ACK帧。
路由器模块明确了要发往的地址,所以协调器模块在收到信息后,硬件回复给路由器模块ACK,表明我已经收到了你发过来的帧!
8)路由器模块发给协调器模块的请求帧。
路由器模块请求协调器,请协调器模块根据路由器之前发送MAC地址,给路由器分配的网络地址,并把分配的网络地址发给路由器。这是一个数据请求帧。
9)协调器发送给路由器ACK帧。
协调器回复给路由器ACK,表明收到了路由器发过来的数据请求帧。
10)协调器把为路由器分配好的网络地址发给路由器。
这个帧需要非常明确的发给路由器模块,但是路由器模块还不知道自己的网络地址是多少,所有在指定目标地址的时候用MAC地址。
- Source Address:协调器的MAC地址(6550f)
- Dest Address:路由器MAC地址(5581f)
- 0xE9EB:路由器被分配的网络地址
11)路由器接收到协调器发送的帧,路由器向协调器发送ACK帧。
路由器模块根据自己的MAC地址收到了协调器分配给自己的网络地址,硬件自动回复ACK,表明已经收到了协调器发过来的帧。
12)路由器模块入网宣告。
告诉当前网络里所有的节点,我已经入网了,我的网络地址是0xE9EB。
- Source Address:路由器的网络地址
13)协调器转发路由器广播的帧。
协调器模块在收到了路由器模块发的入网宣告帧以后,转发了路由器广播的帧。
14)协调器和路由器发送网络连接状态帧。
协调器模块和路由器模块在工作稳定时,发出的网络连接状态帧。
协调器和路由器在入网后,稳定工作时的行为是,每隔一段时间发送一次网络连接状态帧,默认是15S
六、终端与协调器无线通信过程
1)入网过程,终端的入网过程和路由器入网的过程,所有的行为都是一样的【(1)—(13)】
2)与路由器不同的是,终端在入网之后,终端节点会向它的父节点所在网络中的协调器节点发送数据请求帧,告诉父节点,自己还在线。协调器在收到终端发送的数据请求帧,硬件自动回复ACK。
七、上述是使用抓包工具(USBDongle抓取无线局域网里面的无线数据包
1)抓包工具USBDongle配置:
① 选择IEEE 802.15.4/Zigbee
② 选择驱动
③ 选择抓包信道:需要和你使用无线通信的信道相同
④ 选择ZigBee 2007/PRO
资源链接:https://pan.baidu.com/s/1KBxHLfRfJeEBVyTq3H5TGw
提取码:zjzb
希望本篇文章对大家有所帮助,后续会继续分享Zigbee相关学习知识…
如果文章内容有错误的地方,请在留言处留下你的见解,方便大家共同学习。谢谢!
如有侵权或其他任何问题请联系:QQ1370922071,本文主要用于学习交流,转载请声明!
作者:皮皮猫吖
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)