在无线通讯的网络中,想必zigbee大家一定不会陌生,这一个主打低功耗的通讯技术、可靠、传输速率第,分为四层构造:应用层、网络层、MAC、物理层;四层各司其职,先从最底层说起吧;
物理层:我们在使用无线通讯的时候需要知道一点就是,通讯双方是必须保持一致的,如果不保持一致是无法通讯的,那么如何来实现这种通讯一致呢,那么就交给了物理层,通过编写同步码来实现同步,一个设备发同步码,里一个设备就接收同步码,如果可以做到发送的同时接收到就说明同步了;
MAC:这一层也就是数据链路层,在ZigBee通讯它的主要作用就是防止数据冲突,因为我们的无线通讯是通过一种广播的形式,在发送数据的时候很容易受到别的数据信号的影响,所以ZigBee制定一套csma-ca机制,当发数据的时候,先拿到时间戳,首先各个数据在时间上有错开的发送的机制,然后在接收信标帧,对比其他冲突的信号,如果优先级低的话就重新这个过程,这样可以有效避免冲突;
网络层:这个实现了一个中继转发的过程,因为是无线通讯最多也就在百多米范围内,如果是管理那种大范围的设备,比如只能农耕,不就需要用到一种中继方然设备吗,就像以前的烽火台,可以将信号传播好远;
应用层:这个应该是我们最熟悉的了,就是实现各种各样的功能的一层了;
说完四层结构,让我们再来聊一聊他使用的标准IEEE,这是一个通讯标准协会,大部分是由一些通讯公司组成的,还有一个值拓扑结构,ZigBee用到的主要有三种,星型拓扑、簇型拓扑、网状型,星型拓扑就是路由器、中断连接在一个协调器上面,这个协调器可以说是一个大网关,簇型的话就是我们说的树形,就像一个树杈一样的分散下去,而网状型就是一个接一个的路由,然后很多的终端连接在上面;
我们也经常还听到信道这个东西,那么信道是用来干嘛的呢?
其实这就要涉及到我们的频道的概念,经常听到的频道有2.4GHZ、868、915MHz,2.4GHZ是国内常用的,其他两个是欧洲美国的,2.4GHZ含有16个信道,不过是11~26的编号,所以后面写代码的时候就要知道不是从零开始,其实有可能很多人用2.4GHZ的时候可能就会存在很多干扰,于是就分出了16个信道,如果遇到连个相同频道的无线电就可以通过使用不同的信道来实现一种隔离的效果;
好了,基本介绍就到这里,我为大家准备了一些资料,是cc2530官方下载的示例代码,我们就使用该代码来进行工程的移植,简化代码,降低我们编写程序的难度:
1、这些资料我放在了公众号:代码栈;回复代码移植即可领取;
解压之后你可以看到上面的文件结构,我们主要需要使用到的文件放在了source;
不过先让我们打开里面的官方案例,在移植的时候我们需要查找一下那一些是我们需要的,这里我告诉大家:basic.rf.c和hal.rf.c是我们肯定要加的,我们先打开ide文件夹:
点击第二个,按照一下步骤打开一个示例:
打开之后我们可以找到里面的文件:
basic.rf.c
hal.rf.c
然后打开我们自己的工程进行移植;
2、现在自己的工程里面新建一个文件夹(BasicRf):
不知道大家的工程结构是怎么样的,但是现在就按照我这样做,main函数放在app里面,应用函数放在drive,工程创建在project,这里新建的文件夹就用来存放我们移植的文件:
打开自己的工程,注意IAR有一个毛病,双击打开的话会覆盖之前打开的,所以我们回到桌面点击app拖动工程到IAR里,然后就可以打开两个了,方便我们拍错和移植:
我们在工程目录里面新建一个文件夹用来存放移植文件,因为IAR里面的工程结构是虚拟的,所以我们需要再建一次,我建议和外面保持一致:
3、现在我们开始将上面说到的两个文件移植过来:
告诉大家如何找到我们需要的文件,只需要将鼠标移动到示例代码的文件上:
然后会弹出一个文件路径,我们跟着这个路径去找就可以了,现将Basic-ri和hal_rf文件移动到BasicRf文件夹,也就是我们新建的那个文件夹,注意,一般我们要找一下.c和.h文件是否存在,如果有的话我们需要全部拷贝过来,下面这个示例工程一定要打开因为我们接下来要频繁在里面寻找文件:
有一些文件就只有.h没有.c,大家注意:
像这里的has_types.h,就没有;
4、大家慢慢找,就按照上面的方法根据路径找,找到之后拷贝到这个文件夹,然后在工程中导入
导入之后单个文件编译一下,可以看到报了错:
不用慌,很正常,因为代码移植做的就是拍错,先看报了什么错:
说我们没有包含头文件,所以我们去示例工程中找一下这个文件然后加进来:
hal_int.h
找到之后同样加进来就可以了,是先放到外面的文件夹,然后到工程里面导入就可以,然后再编译一下可以看到有有一堆错误:
可以看到又是有文件没加进来,对了,大家知道怎么单个文件编译吧?
点这个:
好了,我们继续到示例工程里面找这个文件:
hal_types.h
然后拷贝到外面的BasicRf,再到工程中的BasicRf中导入,不过这个文件没有.c,所以我们拷贝了就可以:
再次编译会发现报了一个错,这次是说无法打开这个文件,我们直接换了了这一行:
这次报的错是无法找到:
hal_types.h
其实是说我们没有包含文件夹路径,还记得怎么包含文件夹路径吗:
然后又来了一个错,我们继续:
继续找到文件然后添加就可以了:
还报错,我们继续加:
好了,现在就只剩一个警告:
那么我们双击这个警告,复制一下,到示例工程里面,打开basic_rf.c文件搜索一下这个函数:
搜到之后右击,点开定义发现定位到了这段代码:
void halMcuWaitUs(uint16 usec)
{
usec>>= 1;
while(usec--)
{
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
}
}
我们复制下来,丢到这个函数的前面:
就好了,下面连个警告不用管:
5、然后我们对其他移植进来的文件一个个编译,如果有错就按照上面的方法找就可以:
然后在编译hal_int.c的时候发现有这个错误,我们将头文件删除即可:
删除之后编译又出现一个错误,这个其实是总中断,在我们的iocc2530里面,所以我们加上头文件就可以:
看到,加完之后就没有了,接下来继续编译其他文件:
遇到同样的问题继续删除头文件是出现了上面的错误,那么使用我们之前讲到的复制函数到示例工程中找文件的方法,如果缺少文件就加进来:
最终用到的文件就这么些,通过上面的方法将这里的.c文件全部排一遍错之后,下面我们就编译成功了;
这些警告的意思是之前项目做的变量没有使用到,这个不需要管;
6、编写发送与接收程序:
我们打开:
#include "basic_rf.h"
文件来看一看我们需要用到哪一些函数,就制作一个使用两块板子,一块发送数据一块接收数据后开灯的程序,其实两块板子跑的是同一个程序,只不过我们使用工程宏定义来做了一个区分:
typedef struct {
uint16 myAddr;
uint16 panId;
uint8 channel;
uint8 ackRequest;
#ifdef SECURITY_CCM
uint8* securityKey;
uint8* securityNonce;
#endif
} basicRfCfg_t;
/***********************************************************************************
* GLOBAL FUNCTIONS
*/
uint8 basicRfInit(basicRfCfg_t* pRfConfig);
uint8 basicRfSendPacket(uint16 destAddr, uint8* pPayload, uint8 length);
uint8 basicRfPacketIsReady(void);
int8 basicRfGetRssi(void);
uint8 basicRfReceive(uint8* pRxData, uint8 len, int16* pRssi);
void basicRfReceiveOn(void);
void basicRfReceiveOff(void);
可以看到上面的代码,这是#include "basic_rf.h"文集里的内容,我们需要用到的函数有以下几个:
uint8 basicRfInit(basicRfCfg_t* pRfConfig);
第一个是初始化函数,参数是一个结构体:
typedef struct {
uint16 myAddr;
uint16 panId;
uint8 channel;
uint8 ackRequest;
#ifdef SECURITY_CCM
uint8* securityKey;
uint8* securityNonce;
#endif
} basicRfCfg_t;
看到结构体,我们只需要用到前面四个就可以,分别代表的意思为:我的地址(0000~ffff)、zigbee的网络标识(0000~ffff)、信道(11~26)、是否需要一个ack响应(1、0);
一般我们在选择地址和网络的时候不会选择0000和ffff因为它们太特殊,而且oxffff通常用来做广播地址,也就是在范围内所有的单片机都可以收到,注意我们的信道是一定需要一致的,不然会被隔离,最后一个响应一般是1,;
uint8 basicRfSendPacket(uint16 destAddr, uint8* pPayload, uint8 length);
这个函数是用来发送数据的,第一个参数就是第二块板的地址,第二个参数就是需要发送的数据数组,第三个是发多少;
uint8 basicRfPacketIsReady(void);
这个函数是一个判断函数,一般用在接收数据的时候,我们使用这个函数判断是否做好了接收准备,一般为1的时候我们才开始接收,而接收数据就使用下面一个函数:
uint8 basicRfReceive(uint8* pRxData, uint8 len, int16* pRssi);
这个函数用来接收数据,含有三个参数,第一个接收数据的数组,第二个是接收的长度,第三个是信号强度,虽然是一个16位,但是我们还是存8位好一点,0~255反应更客观,我们只需要强转一下就可以了;
void basicRfReceiveOn(void);
这个函数要注意,我们一定要调用一下,因为这是接收的开关;
7、代码演示:
//2.3 定义通讯的结构体
basicRfCfg_t pRfConfig;
//2.4 配置结构体的成员
//这里需要根据不同的工程名来设定不同的地址
#ifdef SWITCH
pRfConfig.myAddr = 0x0001;
#else
pRfConfig.myAddr = 0x0002;
#endif
pRfConfig.panId = 0x1000;
pRfConfig.channel = 11;
pRfConfig.ackRequest = 1;
首先初始化我们是需要的,这里我们定义一个结构体,我们需要设置四个参数,之前不是说过我们一套程序两块板使用怎么实现吗?看这里,使用#ifdef else #endif来判断一些就可以,我们将接收工程的工程宏这是为SWITCH ,然后发送板就会使用0x0001这个地址,而接收板不做处理,那么地址就位0x0002;
//2.1 初始化函数
basicRfInit(&pRfConfig);
//2.5 保持接收的打开
basicRfReceiveOn();
//1.3定义一个需要发送数据的长度和采集数据的存储变量
unsigned char len = 0;
//2.6 我们需要一个判断来确定按钮的状态
unsigned char switchstate = 0;
float ret = 0;
//1.1 通过传感器产生的数据,发送到pc端显示,
while(1)
{
//2.1 编写无线通讯代码
//我们需要不同的工程宏来判断是发送还是接收逻辑
#ifdef SWITCH
//2.4 发送逻辑
//每次按下按钮我们将switchstate取反
if(Key1Getstate())
{
switchstate = !switchstate;
}
//2.7 我们按照地址发送数据,一次第一个
basicRfSendPacket(0x0002,TestTxBuf,1);
#else
//2.8 判断是否收到数据
if(basicRfPacketIsReady())
{
//收到之后我们接收到数组中,然后判断亮灯
//接收函数的第三个参数是信号的强度
basicRfReceive(TestRxBuf,sizeof(TestRxBuf),(unsigned short*)pRssi);
if(TestRxBuf[0])
{
LEDOn(0);
}
else
{
LEDOff(0);
}
}
#endif
定义玩结构体之后,我们调用一下初始化函数将结构体传进去,然后同样使用宏判断来分开我们的发送逻辑和接收逻辑;这里使用到之前的案件模块,我们调用按键1的监听函数,并且定义了一个变量switchstate来当做状态赋值给数组发送出去,那么接收板就会收到一个变化的0、1数据,我们在接收逻辑中处理接收数组就可以了;
首先判断程序是否做好了接收准备,然后使用我们定义好的数组接收,长度不知道就设置最大值,然后强度我们定义的是一个char型强转为short型:
//创建一个存储单个数据的数组
static unsigned char TestTxBuf[100];
//创建一个接收数据的数组
static unsigned char TestRxBuf[100];
//定义一个强度
static unsigned char pRssi;
8、分开工程并取名,因为要是用到工程宏,所以我们拷贝一个工程,分别将放在新建的light和:
switch文件夹下面,其实代码是样的
因为怕引起歧义,我们将工程名改一下,当然你也可以不该,一改四个文件都要改,而且我们使用notepad++将里面和之前的文件名字相同的代码改为现在的名字:
而且四个文件都需要改,改完之后保存就可以了;
最后我们在switch工程下面添加宏就可以了,打开工程然后点开option:
那么这一期我们就到这里,学会的快去试试吧,下一期会做一个进阶效果;