ZigBee开发(10)--组网实验无线电点灯
关于ZigBee协议栈的介绍,先不写,网上很多
无线点灯是大家入门 ZigBee一个很好的经典例子,里面虽然还没有用到协议栈,但它体现出来的数据发送、接收和用协议栈是差不多的,
而且 TI 公司的 Basic RF 的代码容易看懂,如果把这个实验掌握了(不要只是下载程序然后看试现象),到后面的协议栈就比较好入手了。
了解一下下面的关键字:
CCM - Counter with CBC-MAC (mode of operation)
HAL - Hardware Abstraction Layer (硬件抽象层)
PAN - Personal Area Network (个人局域网)
RF - Radio Frequency (射频)
RSSI - Received Signal Strength Indicator (接收信号强度指示)
实验现象: 两块 WeBee 模块通信,一个模块作发射,另外一个模块接收,发射模块依次按下按键 S1,改变接收模块 LED1 的亮灭的状态。实现无线点灯功能。
例程的源代码 CC2530 BasicRF.rar 是 TI 官网上下载的,用户可以去 TI 官网注册并下载。下载链接:https://pan.baidu.com/s/1vXJ4eV25A_w0EslrhwXEFA
首先说明, TI 官网的程序的开发平台是 TI 官网的开发板,硬件资料有所不同,所以要在 WeBee 板上实现无线点灯功能,必须对其代码稍作修改。
实验代码之前,还是先来看看这些看到有点头晕的文件夹吧!
文件夹结构大至如下,仅列出 CC2530 BasicRF 目录一些相关的的文件夹:每个文件夹里面放着什么东西,如果缺少其中某些,我们的灯还是否可以点亮呢?
我们来一一探讨:
docs 文件夹:
打开文件夹里面仅有一个名为 CC2530_Software_Examples 的 PDF 文档,文档的主要内容是介绍 BasicRF 的特点、结构及使用,
如果读者有 TI 的开发板的话阅读这个文档就可以做 Basic RF 里面的实验了,从中我们可以知道,
里面Basic RF 包含三个实验例程: 无线点灯、传输质量检测、谱分析应用。下面讲解的内容中也有部分内容是从这个文档中翻译所得,是一份相当有价值的参考资料。
Ide 文件夹:
打开文件夹后会有三个文件夹,及一个 cc2530_sw_examples.eww 工程,其中这个工程是上面提及的三个实验例程工程的集合,
当然也包含了我们无线点灯的实验工程!在 IAR 环境中打开,在 workspace 看到
Ide\Settings 文件夹:
是在每个基础实验的文件夹里面都会有的,它主要保存有读者自己的 IAR环境里面的设置。
Ide\srf05_CC2530 文件夹:
里面放有三个工程, light_switch.eww、 per_test.eww、 spectrum_analyzer.eww如果读者不习惯几个工程集合在一起看,也可以在这里直接打开你想要用的实验工程。
Source 文件夹:
打开文件夹里面有 apps 文件夹和 components 文件夹
Source\apps 文件夹:
存放 BasicRF 三个实验的应用实现的源代码
Source\components 文件夹:
包含着 BasicRF 的应用程序使用不同组件的源代码
打开实验工程:
打开文件夹 CC2530 BasicRF\ide\srf05_cc2530\iar 路径里面的工程light_switch.eww(无线点灯)。 我们的实验就是对它进行修改的。
并点击application 的 light_switch.c 用户的应用程序就是在里面的了
二、 Basic RF layer 介绍及其工作过程:
在介绍 Basic RF 之前,来看看这个实验例程设计的大体结构,如图所示 BasicRF 例程的软件设计框图就如一座建筑物,
Hardware layer
放在最底,肯定是你实现数据传输的基础了。
Hardware Abstraction layer
它提供了一种接口来访问 TIMER, GPIO, UART, ADC 等。这些接口都通过相应的函数进行实现。
Basic RF layer
为双向无线传输提供一种简单的协议
Application layer
是用户应用层,它相当于用户使用 Basic RF 层和 HAL 的接口,也就是说我们通过在 Application layer 就可以使用到封装好的 Basic RF 和 HAL 的函数。
本例程的要求就是读者理解掌握 Basic RF
Basic RF layer 简介:
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
图 3.8 注释 SECURITY_CCM
Project->Option 里面就可以选择,本次实验并不是什么高度机密,所以在 SECURITY_CCM 前面带 X 了。
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 //是否加密,预定义里取消了加密118 uint8* securityKey; uint8* securityNonce; #endif } basicRfCfg_t;
3.调用 basicRfInit()函数进行协议的初始化,在 basic_rf.c 代码中可以找到
uint8 basicRfInit(basicRfCfg_t* pRfConfig) /***函数功能: 对 Basic RF 的数据结构初始化,设置模块的传输通道,短地址,PAD ID。**/
发送
5. 创建一个 buffer,把 payload 放入其中。 Payload 最大为 103 个字节
6. 调用 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 实现无线传输只要学会使用这些函数就可以了 。
但是具体的实现过程远没有那么简单的,大家可以到….\CC2530BasicRF\docs 里面查看 CC2530_Software_Examples 中的 5.2.4 Basic RF operation这个章节的内容,
里面详细介绍了 Basic RF 的初始化过程、 Basic RF 的发射过程、 Basic RF 的接收过程,具体到每个层的功能函数。
WeBee 本来想将这部分的内容也详细的和读者们讲解清楚,但后来再仔细考虑还是不放上来了。因为它的具体实现过程大家看文档的那个章节就可以大概明白的了,
另一方面,实验例程的模块化编程做得很好,大家只需要明白函数的作用,学会使用它就行了,至于它内部是怎么样一层一层的实现,我们也不用太过关心。
三、 light_switch.c 代码详解:
无论你看哪个实验的代码,首先要找的就是 main 函数。从 main 函数开始:(部分已经屏蔽的代码并未贴出,详细的代码请看打开工程)
1 void main(void) 2 { 3 uint8 appMode = NONE;//不设置模块的模式 4 5 // Config basicRF 6 basicRfConfig.panId = PAN_ID;//上面讲的 Basic RF 的启动中的 7 basicRfConfig.channel = RF_CHANNEL;//初始化 basicRfCfg_t结构体的成员。 8 basicRfConfig.ackRequest = TRUE; 9 #ifdef SECURITY_CCM //密钥安全通信,本例程不加密 10 basicRfConfig.securityKey = key; 11 #endif 12 13 // Initalise board peripherals //初始化外围设备 14 halBoardInit(); 15 halJoystickInit(); 16 17 // Initalise hal_rf //硬件抽象层的 rf 进行初始化 18 if(halRfInit()==FAILED) { 19 HAL_ASSERT(FALSE); 20 } 21 22 // Indicate that device is powered 23 /***********根据 WeBee 学习底板配置**********/ 24 halLedClear(2); //关闭LED2 25 halLedClear(1); //关闭LED1 26 27 28 /************Select one and shield to another***********by boo*/ 29 /******选择性下载程序,发送模块和接收模块******/ 30 //appSwitch(); //节点为按键S1 P0_0 31 appLight(); //节点为指示灯LED1 P1_0 32 33 // Role is undefined. This code should not be reached 34 HAL_ASSERT(FALSE); 35 }
第 24~25 行: 关闭 WeBee 底板的 LED2 和 LED1。 halLedSet(x)置 1 是使灯点亮,halLedClear(x)是使灯熄灭。
第 30~31 行: 选择其中的一行,并把另外一行屏蔽掉;
这两行重要啦,一个是实现发射按键信息的功能,另一个是接收按键信息并改变 LED状态的功能。分别为 Basic RF 发射和接收。不同模块在烧写程序时选择不同功能。
注意: 程序会在 appSwitch(); 或者 appLight();里面循环或者等待,不会执行到第 29 行。
接下来看看 appSwitch()函数,它是如何实现数据发送的呢?
1 static void appSwitch() 2 { 3 halLcdWriteLine(HAL_LCD_LINE_1, " W e B e e "); 4 halLcdWriteLine(HAL_LCD_LINE_2, " ZigBee CC2530 "); 5 halLcdWriteLine(HAL_LCD_LINE_4, " SWITCH "); 6 7 #ifdef ASSY_EXP4618_CC2420 8 halLcdClearLine(1); 9 halLcdWriteSymbol(HAL_LCD_SYMBOL_TX, 1); 10 #endif 11 12 13 // Initialize BasicRF 14 basicRfConfig.myAddr = SWITCH_ADDR; 15 if(basicRfInit(&basicRfConfig)==FAILED) { 16 HAL_ASSERT(FALSE); 17 } 18 19 pTxData[0] = LIGHT_TOGGLE_CMD; 20 21 // Keep Receiver off when not needed to save power 22 basicRfReceiveOff(); 23 24 // Main loop 25 while (TRUE) { 26 //if( halJoystickPushed() )**********************by boo 27 if(halButtonPushed()==HAL_BUTTON_1)//**************by boo 28 { 29 30 basicRfSendPacket(LIGHT_ADDR, pTxData, APP_PAYLOAD_LENGTH); 31 32 // Put MCU to sleep. It will wake up on joystick interrupt 33 halIntOff(); 34 halMcuSetLowPowerMode(HAL_MCU_LPM_3); // Will turn on global 35 // interrupt enable 36 halIntOn(); 37 38 } 39 } 40 }
第 3~6 行: TI 学习板上的液晶模块的定义,我们不用管他
第 7~17 行: Basic RF 启动中的初始化,就是上面所讲的 Basic RF 启动的第 3步
第 19 行: Basic RF 发射第 1 步,把要发射的数据或者命令放入一个数据 buffer,此处把灯状态改变的命令 LIGHT_TOGGLE_CMD 放到 pTxData 中。
第 22 行: 由于模块只需要发射,所以把接收屏蔽掉以降低功耗。
第 18 行: if(halButtonPushed()==HAL_BUTTON_1)判断是否 S1 按下,函数halButtonPushed()是 halButton.c 里面的,
它的功能是:按键 S1 有被按 动 时 , 就 回 返 回 true , 则 进 入basicRfSendPacket(LIGHT_ADDR, pTxData,APP_PAYLOAD_LENGTH);
第 30 行: Basic RF 发射第 2 步, 也是发送数据最关键的一步,函数功能在前面已经讲述。
basicRfSendPacket(LIGHT_ADDR,pTxData,APP_PAYLOAD_LENGTH) /***
就是说:将 LIGHT_ADDR、 pTxData、APP_PAYLOAD_LENGTH 的 实 参 写 出 来
就是basicRfSendPacket(0xBEEF ,pTxData[0] ,1 )
把字节长度为 1 的命令,发送到地址 0xBEEF
*****/
第 33~34 行: WeBee 开发板暂时还没有 joystick(多方向按键),不用理它先。
第 36 行: 使能中断
发送的 appSwitch()讲解完毕,接下来就到我们的接收 appLight()函数了
1 static void appLight() 2 { 3 4 halLcdWriteLine(HAL_LCD_LINE_1, " W e B e e "); 5 halLcdWriteLine(HAL_LCD_LINE_2, " ZigBee CC2530 "); 6 halLcdWriteLine(HAL_LCD_LINE_4, " LIGHT "); 7 8 #ifdef ASSY_EXP4618_CC2420 9 halLcdClearLine(1); 10 halLcdWriteSymbol(HAL_LCD_SYMBOL_RX, 1); 11 #endif 12 13 // Initialize BasicRF 14 basicRfConfig.myAddr = LIGHT_ADDR; 15 if(basicRfInit(&basicRfConfig)==FAILED) { 16 HAL_ASSERT(FALSE); 17 } 18 basicRfReceiveOn(); 19 20 // Main loop 21 while (TRUE) { 22 while(!basicRfPacketIsReady()); 23 24 if(basicRfReceive(pRxData, APP_PAYLOAD_LENGTH, NULL)>0) { 25 if(pRxData[0] == LIGHT_TOGGLE_CMD) { 26 halLedToggle(1); 27 halLedToggle(2); 28 halLedToggle(3); 29 } 30 } 31 } 32 }
第 4~6 行: LCD 内容暂时不用理它
第 14~17行: Basic RF 启动中的初始化,上面 Basic RF 启动的第 3 步
第 18 行:函数 basicRfReceiveOn(),开启无线接收功能,调用这个函数后模块一直会接收,除非再调用 basicRfReceiveOff()使它关闭接收。
第 21 行:程序开始进行不断扫描的循环
第 22行: Basic RF 接收的第 1 步, while(!basicRfPacketIsReady()) 检查是否接收上层数据,
第 24 行 : Basic RF 接 收 的 第 2 步 , if(basicRfReceive(pRxData,APP_PAYLOAD_LENGTH, NULL)>0)判断否接收到有数据
第 215行: if(pRxData[0] == LIGHT_TOGGLE_CMD)判断接收到的数据是否就是发送函数里面的 LIGHT_TOGGLE_CMD 如果是,执行第 22 行
第 26~28 行: halLedToggle(1),改变 Led1,2,3 的状态。
实验操作:
第一步:打开….\CC2530 BasicRF\ide 文件夹下面的工程 在 light_switch.c 里面找到 main 函数,找到下面内容,把 appLight(); 注释掉,下载到发射模块。
/************Select one and shield to another************/
appSwitch(); //节点为按键 S1 P0_0
// appLight(); //节点为指示灯 LED1 P1_0
第二步:找到相同位置,这次把 appSwitch();注释掉,下载到接收模块。
125 |
/************Select one and shield to another***********by boo*/
//appSwitch(); //节点为按键 S1 P0_0
appLight(); //节点为指示灯 LED1 P1_0
完成烧写后上电,按下发射模块的 S1 按键,可以看到接收模块的 LED1 被点亮。
上面我们说到,这个实验例程用的是TI官网的开发板,咱们买的就可可能跟他们硬件连接不一样
会出现按键和灯的引脚不匹配现象
引脚定义在srf05_soc文件夹下的hal_board.h文件中定义,根据自己板子的硬件连结进行修改即可
// SPI #define HAL_BOARD_IO_SPI_MISO_PORT 1 #define HAL_BOARD_IO_SPI_MISO_PIN 7 #define HAL_BOARD_IO_SPI_MOSI_PORT 1 #define HAL_BOARD_IO_SPI_MOSI_PIN 6 #define HAL_BOARD_IO_SPI_CLK_PORT 1 #define HAL_BOARD_IO_SPI_CLK_PIN 5 #define HAL_BOARD_IO_EM_CS_PORT 1 #define HAL_BOARD_IO_EM_CS_PIN 4 // LCD #define HAL_BOARD_IO_LCD_CS_PORT 1 #define HAL_BOARD_IO_LCD_CS_PIN 2 #define HAL_BOARD_IO_LCD_MODE_PORT 0 #define HAL_BOARD_IO_LCD_MODE_PIN 0 // LEDs #define HAL_BOARD_IO_LED_1_PORT 1 // Green************LED1 by boo #define HAL_BOARD_IO_LED_1_PIN 0 #define HAL_BOARD_IO_LED_2_PORT 1 // Red**************LED2 by boo #define HAL_BOARD_IO_LED_2_PIN 1 #define HAL_BOARD_IO_LED_3_PORT 1 // Yellow #define HAL_BOARD_IO_LED_3_PIN 4 #define HAL_BOARD_IO_LED_4_PORT 0 // Orange #define HAL_BOARD_IO_LED_4_PIN 1 // Buttons #define HAL_BOARD_IO_BTN_1_PORT 0 // Button S1 #define HAL_BOARD_IO_BTN_1_PIN 1 // Potmeter #define HAL_POTMETER_ADC_PORT 0 #define HAL_POTMETER_ADC_CH 7 // Joystick #define HAL_BOARD_IO_JOYSTICK_ADC_PORT 0 #define HAL_BOARD_IO_JOYSTICK_ADC_PIN 6 #define HAL_BOARD_IO_JOY_MOVE_PORT 2 #define HAL_BOARD_IO_JOY_MOVE_PIN 0 // UART #define HAL_BOARD_IO_UART_RTS_PORT 0 #define HAL_BOARD_IO_UART_RTS_PIN 5
搞定!!!