我是靠谱客的博主 激动可乐,最近开发中收集的这篇文章主要介绍萌新的Zigbee学习日记(3.1)组网练习,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

既然要组网,那么就得熟悉一点计算机网络的知识,不同层之间的应用其实存在透明,只需要对相同层做出改动就可。手中的资料是使用的Z-Stack协议栈。

TI 推出的 ZigBee 2007 协议栈也称 Z-Stack协议栈,Z-Stack 协议栈中提供了一个名为操作系统抽象层 OSAL 的协议栈调度程序。对于用户来说,除了能够看到这个调度程序外,其它任何协议栈操作的具体 实现细节都被封装在库代码中。用户在进行具体的应用开发时只能够通过调用 API 接口来进行,而无权知道 ZigBee 协议栈实现的具体细节,也没必要去知道。可以将它看作是一个大工程,或者是一个操作系统,采用任务轮询的方法运行。

因此ZigBee 协议栈已经实现了 ZigBee 协议,用户可以使用协议 栈提供的 API 进行应用程序的开发,在开发过程中完全不必关心 ZigBee 协议的 具体实现细节,要关心的问题是:应用层的数据是使用哪些函数通过什么方式把 数据发送出去或者把数据接收过来的。所以最重要的是我们要学会使用 ZigBee 协议栈。

用户实现一个简单的无线数据通信时的一般步骤:

1、组网:调用协议栈的组网函数、加入网络函数,实现网络的建立与节点的加 入。

2、发送:发送节点调用协议栈的无线数据发送函数,实现无线数据发送。

3、接收:接收节点调用协议栈的无线数据接收函数,实现无线数据接收。

然后还有一些关键字需要掌握

CCM - Counter with CBC-MAC (mode of operation)

HAL - Hardware Abstraction Layer (硬件抽象层)

PAN - Personal Area Network (个人局域网)

RF - Radio Frequency (射频)

RSSI - Received Signal Strength Indicator (接收信号强度指示)

Hardware layer 放在最底,肯定是你实现数据传输的基础 了。

Hardware Abstraction layer 它提供了一种接口来访问 TIMER,GPIO, UART,ADC 等。这些接口都通过相应的 函数进行实现。

Basic RF layer 为双向无线传输提供一种简单的协议。

Application layer 是用户应用层,它相当于用户使用 Basic RF 层和 HAL 的接口,也就是说我们通过 在 Application layer 就可以使用到封装好的 Basic RF 和 HAL 的函数。

 Basic RF 由 TI 公司提供,它包含了 IEEE 802.15.4 标准的数据包的收发功能但 并没有使用到协议栈,它仅仅是是让两个结点进行简单的通信,也就是说 Basic RF 仅仅是包含着 IEEE 802.15.4 标准的一小部分而已。其主要特点有:

1、不会自动加入协议、也不会自动扫描其他节点也没有组网指示灯(LED3)。

2、没有协议栈里面所说的协调器、路由器或者终端的区分,节点的地位都是相 等的。

3、没有自动重发的功能。

Basic RF layer 为双向无线通信提供了一个简单的协议,通过这个协议能够进 行数据的发送和接收。Basic RF 还提供了安全通信所使用的 CCM-64 身份验证和 数据加密,它的安全性读者可以通过在工程文件里面定义 SECURITY_CCM 在 Project->Option 里面就可以选择。

Basic RF 的工作过程:启动、发射、接收 

启动

1、确保外围器件没有问题

2、创建一个 basicRfCfg_t 的数据结构,并初始化其中的成员,在 basic_rf.h 代码中可以找到


typedef struct {
 uint16 myAddr; //16 位的短地址(就是节点的地址)
 uint16 panId; //节点的 PAN ID
 uint8 channel; //RF 通道(必须在 11-26 之间)
 uint8 ackRequest; //目标确认就置 true
 #ifdef SECURITY_CCM //是否加密,预定义里取消了加密
 uint8* securityKey;
 uint8* securityNonce;
 #endif
} basicRfCfg_t;

//uint16 16位无符号整形
//uint8  8位无符号整形

3、调用 basicRfInit()函数进行协议的初始化,在 basic_rf.c 代码中可以找到 uint8 basicRfInit(basicRfCfg_t* pRfConfig)

函数功能:对 Basic RF 的数据结构初始化,设置模块的传输通道,短地址, PAD ID。

 发送

1、创建一个 buffer,把 payload 放入其中。Payload 最大为 103 个字节

2、调用 basicRfSendPacket()函数发送,并查看其返回值 在 basic_rf.c 中可以找到 uint8 basicRfSendPacket(uint16 destAddr, uint8* pPayload, uint8 length)

destAddr 目的短地址

pPayload 指向发送缓冲区的指针

length 发送数据长度

函数功能:给目的短地址发送指定长度的数据,发送成功刚返回 SUCCESS, 失败则返回 FAILED

接收

1、上层通过 basicRfPacketIsReady()函数来检查是否收到一个新数据包 在 basic_rf.c 中可以找到 uint8 basicRfPacketIsReady(void)

函数功能:检查模块是否已经可以接收下一个数据,如果准备好刚返回 TRUE

2、 调用 basicRfReceive()函数,把收到的数据复制到 buffer 中。 代码可以在 basic_rf.c 中可以找到 uint8 basicRfReceive(uint8* pRxData, uint8 len, int16* pRssi)

函数功能:接收来自 Basic RF 层的数据包,并为所接收的数据和 RSSI 值 配缓冲区 如果能看懂启动、发射、接收就可以说你基本上能使用这个无线模块了。

看到这里大家就会觉得无线传输怎么会那么简单,真的只调用那几个函数 就可以实现了吗? 是的,使用 Basic RF 实现无线传输只要学会使用这些函数就可 以了。 但是具体的实现过程远没有那么简单的,大家可以到….CC2530 BasicRFdocs 里面查看 CC2530_Software_Examples 中的 5.2.4 Basic RF operation 这 个章节的内容,里面详细介绍了 Basic RF 的初始化过程、Basic RF 的发射过程、 Basic RF 的接收过程,具体到每个层的功能函数,实际上内部的运行并不是像这样简单的几个函数便解决了。

从main函数开始观察,运行的是哪几个函数,再对函数进行分析。
 void main(void)
 {
 uint8 appMode = NONE; //不设置模块的模式
 // Config basicRF 调用结构体basicRfConfig里面的成员(函数)。
 basicRfConfig.panId = PAN_ID; //上面讲的 Basic RF 的启动中的第 2 步初始化 basicRfCfg_t结构体的成员。
 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);
 }
 /***********根据 WeBee 学习底板配置**********/
 halLedSet(2); // 关 LED2(P1_1=1)
 halLedClear(1); // 开 LED1(P1_0=0)

 /******选择性下载程序,发送模块和接收模块******/
 appSwitch(); //节点为按键 S1 P0_4
 appLight(); //节点为指示灯 LED1 P1_0
 // Role is undefined. This code should not be reached
 HAL_ASSERT(FALSE);
 }

第 22~23 行:关闭 WeBee 底板的 LED2,开 LED1。由于 WeBee 设计的LED 电路是低电平点亮的,与 TI 不同,更符合以前大家学习单片机的习惯,所以 halLedSet()置 1 是使灯熄灭,不过这个没关系,关键是掌握怎么使用就可以了。
第 26~27 行:选择其中的一行,并把另外一行屏蔽掉;这两行重要啦,一个是实现发射按键信息的功能,另一个是接收按键信息并改变LED 状态的功能。分别为 Basic RF 发射和接收。不同模块在烧写程序时选择不同功能。
注意: 程序会在 appSwitch(); 或者 appLight();里面循环或者等待,不会执行到第 29 行。



 

接下来看看 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;
 // Keep Receiver off when not needed to save power
 basicRfReceiveOff();
 // Main loop
 while (TRUE) //程序进入死循环
 {
 if(halButtonPushed()==HAL_BUTTON_1) //按键 S1 被按下
 {
 basicRfSendPacket(LIGHT_ADDR,pTxData,APP_PAYLOAD_LENGTH);
 // Put MCU to sleep. It will wake up on joystick interrupt
 halIntOff();
 halMcuSetLowPowerMode(HAL_MCU_LPM_3); // Will turn on global
 // interrupt enable
 halIntOn();
 }
 }
 }
第 3~6 行:TI 学习板上的液晶模块的定义,我们不用管他
第 8~11 行:Basic RF 启动中的初始化,就是上面所讲的 Basic RF 启动的第 3 步
第 12 行:Basic RF 发射第 1 步,把要发射的数据或者命令放入一个数据 buffer,此处把灯状态改变的命令 LIGHT_TOGGLE_CMD 放到 pTxData 中。
第 14 行:由于模块只需要发射,所以把接收屏蔽掉以降低功耗。
第 18 行:if(halButtonPushed()==HAL_BUTTON_1)判断是否 S1 按下,函数halButtonPushed()是 halButton.c 里面的,它的功能是:按键 S1有 被 按 动 时 , 就 回 返 回 true ,则进入basicRfSendPacket(LIGHT_ADDR, pTxData, APP_PAYLOAD_LENGTH);
第 20 行: Basic RF 发射第 2 步,也是发送数据最关键的一步,函数功能在前
面已经讲述。
 basicRfSendPacket(LIGHT_ADDR, pTxData, APP_PAYLOAD_LENGTH)就是说:将 LIGHT_ADDR、pTxData、APP_PAYLOAD_LENGTH 的实参写出来就是 basicRfSendPacket(0xBEEF ,pTxData[1] ,1 )把字节长度为1 的命令,发送到地址 0xBEEF
第 22~23 行:WeBee 开发板暂时还没有 joystick(多方向按键),不用理它先。
第 25 行:使能中断发送的 appSwitch()讲解完毕,接下来就到我们的接收 appLight()函数了

 

 static void appLight()
 {
 /*************************************************
 halLcdWriteLine(HAL_LCD_LINE_1, "Light");
 halLcdWriteLine(HAL_LCD_LINE_2, "Ready");
 ***************************************************/
 #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();
 // Main loop
 while (TRUE)
 {
 while(!basicRfPacketIsReady());
 if(basicRfReceive(pRxData, APP_PAYLOAD_LENGTH, NULL)>0) {
 if(pRxData[0] == LIGHT_TOGGLE_CMD)
 {
 halLedToggle(1);
 }
 }
 }
 }
第 7~10 行:LCD 内容暂时不用理它
第 12~15 行:Basic RF 启动中的初始化,上面 Basic RF 启动的第 3 步
第 16 行:函数 basicRfReceiveOn(),开启无线接收功能,调用这个函数后模块一直会接收,除非再调用 basicRfReceiveOff()使它关闭接收。
第 18 行:程序开始进行不断扫描的循环
第 19 行:Basic RF 接收的第 1 步,while(!basicRfPacketIsReady()) 检查是否接收上层数据,
第 20 行:Basic RF 接收的第 2步,if(basicRfReceive(pRxData, APP_PAYLOAD_LENGTH,NULL)>0)判断否接收到有数据
第 21 行:if(pRxData[0] == LIGHT_TOGGLE_CMD)判断接收到的数据是否就是发送函数里面的 LIGHT_TOGGLE_CMD 如果是,执行第 22 行
第 22 行:halLedToggle(1),改变 Led1 的状态。


实验操作:
第一步:打开….CC2530 BasicRFide 文件夹下面的工程 在 light_switch.c 里面找到 main 函数,找到下面内容,把 appLight(); 注释掉,下载到发射模块。
 /************Select one and shield to another************/
 appSwitch(); //节点为按键 S1 P0_4
 // appLight(); //节点为指示灯 LED1 P1_0
第二步:找到相同位置,这次把 appSwitch();注释掉,下载到接收模块。
 /************Select one and shield to another***********by boo*/
 //appSwitch(); //节点为按键 S1 P0_4
 appLight(); //节点为指示灯 LED1 P1_0
完成烧写后上电,按下发射模块的 S1 按键,可以看到接收模块的 LED1 被点亮。

再具体点的话,可以参考这位的https://blog.csdn.net/thanksgining/article/details/42388153?_t_t_t=0.813206068938598&utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242

其实单片机的运用,也和我们写程序一样,最主要最开始的环节都是初始化系统,避免脏数据影响运行,满足了运行的环境后,再开始运行我们所需要的程序(不积跬步无以至千里嘛)。

实际上组网这块,我感觉并没有怎么接触Zigbee的协议栈,只是借用Z-Stack作为了一个操作系统,在操作系统之上进行再运用的过程。

然后还有几个常用的搓一遍,其实就是用typedef 将后边的数据类型声明为新的一类,后边的_t也只是告诉我们是经过typedef这一操作的。

typedef unsigned char   uint8_t;     //无符号8位数
typedef signed   char   int8_t;      //有符号8位数
typedef unsigned int    uint16_t;    //无符号16位数
typedef signed   int    int16_t;     //有符号16位数
typedef unsigned long   uint32_t;    //无符号32位数
typedef signed   long   int32_t;     //有符号32位数
typedef float           float32;     //单精度浮点数
typedef double          float64;     //双精度浮点数

这些数据类型是 C99 中定义的,具体定义在:/usr/include/stdint.h    ISO C99: 7.18 Integer types <stdint.h>

/* There is some amount of overlap with <sys/types.h> as known by inet code */
#ifndef __int8_t_defined
# define __int8_t_defined
typedef signed char             int8_t; 
typedef short int               int16_t;
typedef int                     int32_t;
# if __WORDSIZE == 64
typedef long int                int64_t;
# else
__extension__
typedef long long int           int64_t;
# endif
#endif
 
/* Unsigned.  */
typedef unsigned char           uint8_t;
typedef unsigned short int      uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int            uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int       uint64_t;
#else
__extension__
typedef unsigned long long int  uint64_t;
#endif

具体使用的时候还是得看输出的是字符字符还是什么别的了。

格式化输出:

unit64_t     %llu   

unit32_t     %u

unit16_t    %hu

注意:

必须小心 uint8_t 类型变量的输出,例如如下代码,会输出什么呢?

uint8_t fieldID = 67;
cerr<< "field=" << fieldID <<endl;
结果发现是:field=C 而 不是我们所想的 field=67

这是由于 typedef unsigned char uint8_t; 
uint8_t 实际是一个 char, cerr << 会输出 ASCII 码是 67 的字符,而不是 67 这个数字.

因此,输出 uint8_t 类型的变量实际输出的是其对应的字符, 而不是真实数字.

若要输出 67,则可以这样:

cerr<< "field=" << (uint16_t) fieldID <<endl;

结果是:field=67

同样: uint8_t 类型变量转化为字符串以及字符串转化为 uint8_t 类型变量都要注意, uint8_t类型变量转化为字符串时得到的会是ASCII码对应的字符, 字符串转化为 uint8_t 变量时, 会将字符串的第一个字符赋值给变量.(其实就像是%c 与 %d的差别)
 

#include <iostream>
#include <stdint.h>
#include <sstream>
using namespace std;
 
 
int main()
{
    uint8_t fieldID = 67;
 
    // uint8_t --> string
    string s;
    ostringstream strOStream;//构造一个流 叫 strOsream
    strOStream << fieldID;
    s = strOStream.str();
    cerr << s << endl;
    
    // string --> uint8_t
    s = "65"; 
    stringstream strStream;
    strStream << s;
    strStream >> fieldID;
    strStream.clear();
    cerr << fieldID << endl;
}

 代替sprintf的std::ostringsteam输出流可以参考https://blog.csdn.net/lanxuezaipiao/article/details/16358159

也是看着看着看学到别的去了,哈哈。不过学习的过程确实是愉悦的。

明天就结束Zigbee的学习了,下一步是去学习STM8与STM32,再学习一点arm有关的东西,还有SPI,IIC,UART的知识。

最后

以上就是激动可乐为你收集整理的萌新的Zigbee学习日记(3.1)组网练习的全部内容,希望文章能够帮你解决萌新的Zigbee学习日记(3.1)组网练习所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(70)

评论列表共有 0 条评论

立即
投稿
返回
顶部