ZigBee无线点灯实验(即Basic RF工程),由TI公司提供的学习例程,是入门ZigBee无线通讯的经典实验,暂时没有用到协议栈,但实验所体现出来的数据发送、接收和使用协议栈的数据通讯模式是差不多的,而且 TI 公司的 Basic RF 工程代码段都有进行注释容易看懂及运用,将这个实验掌握后,将可以更好的过渡到协议栈的内容学习
学习无线控制实验需要线简单了解以下几个关键字
实验中用到的Basic RF 工程源码CC2530 BasicRF.rar需要用户到TI官网注册下载,所下载的案例源码仅针对TI公司的ZigBee开发板进行配置的,烧录到其他ZigBee开发板需要修改配置后才能正常使用
Basic RF工程下载连接 TI官网Basic RF工程压缩包下载
拿到工程源码CC2530 BasicRF.rar解压后可以看到目录下有3个文件夹,如图分别是docs文件夹、ide文件夹和source文件夹
将docs文件夹、ide文件夹和source文件夹展开后如下
docs文件夹
打开文件夹里面仅有一个名为 CC2530_Software_Examples 的 PDF 文档,文档的主要内容是介绍 BasicRF 的特点、结构及使用,如果使用 TI 的开发板的话阅读这个文档就可以做 Basic RF 里面的实验了,阅读文档可以知道,Basic RF工程包含三个实验例程:无线点灯、传输质量检测、谱分析应用。
ide文件夹
打开文件夹后会有三个文件夹和一个 cc2530_sw_examples.eww 工程,这个工程是上面提及的三个实验例程工程的集合,也包含了无线点灯的实验工程,在IAR 环境中打开,在 workspace 看到3个实验工程如图
ide\settings文件夹
在每个基础实验的文件夹里面都会有的,主要保存用户工程的 IAR 环境配置
ide\srf05_cc2530\iar文件夹
这里放有三个工程,light_switch.eww、per_test.eww、spectrum_analyzer.eww,如果不习惯cc2530_sw_examples.eww 工程中几个工程集合在一起看,可以在这里直接对应的实验工程
source文件夹
文件夹里面有 apps 文件夹和 components 文件夹
source\apps文件夹
存放 Basic RF 三个实验(light_switch、per_test、spectrum_analyzer)的应用实现的源代码,即用户代码存放的地方
source\components文件夹
包含 BasicRF 的应用程序使用不同组件的源代码
Hardware layer
硬件层,在最底下,是实现数据传输的基础
Hardware Abstraction layer
它提供了一种接口来访问 TIMER,GPIO,UART,ADC 等硬件资源,这些接口都通过相应的函数进行实现
Basic RF layer
为双向无线传输提供一种简单的协议,Basic RF 由 TI 公司提供,它包含了 IEEE 802.15.4 标准的数据包的收发功能但并没有使用到协议栈,它仅仅是是让两个结点进行简单的通信,也就是说Basic RF 仅仅是包含着 IEEE 802.15.4 标准的一小部分而已。其主要特点有:
Basic RF layer 为双向无线通信提供了一个简单的协议,通过这个协议能够进行数据的发送和接收
Application layer
是用户应用层,它相当于用户使用 Basic RF 层和 HAL 的接口,也就是说我们通过在 Application layer 就可以使用到封装好的 Basic RF 和 HAL 的函数
TI的Basic RF工程目录下CC2530 BasicRF\ide\srf05_cc2530\iar\light_switch.eww打开无线点灯工程
Basic RF 还提供了安全通信所使用的 CCM-64 身份验证和数据加密,它的安全性可以通过在工程文件里面定义 SECURITY_CCM,右键工程列表打开Option,如图工程中不需要启用安全通信加密,在SECURITY_CCM前面加上x,取消加密
确保外围器件没有问题,创建一个 basicRfCfg_t 的数据结构,并初始化其中的成员,在basic_rf.h 代码中可以找到结构体定义
/***********************************************************************************
* TYPEDEFS
*/
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;
调用 basicRfInit()函数进行RF无线通信协议的初始化,在 basic_rf.c 代码中可以找到函数定义,函数对 Basic RF 的数据结构初始化,设置节点的传输通道,短地址,PAN ID
uint8 basicRfInit(basicRfCfg_t* pRfConfig)
{
if (halRfInit()==FAILED)
return FAILED;
halIntOff();
// Set the protocol configuration
pConfig = pRfConfig;
rxi.pPayload = NULL;
txState.receiveOn = TRUE;
txState.frameCounter = 0;
// Set channel
halRfSetChannel(pConfig->channel);
// Write the short address and the PAN ID to the CC2520 RAM
halRfSetShortAddr(pConfig->myAddr);
halRfSetPanId(pConfig->panId);
// if security is enabled, write key and nonce
#ifdef SECURITY_CCM
basicRfSecurityInit(pConfig);
#endif
// Set up receive interrupt (received data or acknowlegment)
halRfRxInterruptConfig(basicRfRxFrmDoneIsr);
halIntOn();
return SUCCESS;
}
创建一个buffer存放无线发送的指令,调用 basicRfSendPacket()函数发送,并查看其返回值,在 basic_rf.c 中可以找到函数定义,其中传参为目标节点的短地址 destAddr
,发送的buffer指针 pPayload
和buffer长度 length
,调用函数给目的短地址发送指定长度的数据,发送成功时返回 SUCCESS,失败则返回 FAILED
uint8 basicRfSendPacket(uint16 destAddr, uint8* pPayload, uint8 length)
{
uint8 mpduLength;
uint8 status;
// Turn on receiver if its not on
if(!txState.receiveOn) {
halRfReceiveOn();
}
// Check packet length
length = min(length, BASIC_RF_MAX_PAYLOAD_SIZE);
// Wait until the transceiver is idle
halRfWaitTransceiverReady();
// Turn off RX frame done interrupt to avoid interference on the SPI interface
halRfDisableRxInterrupt();
mpduLength = basicRfBuildMpdu(destAddr, pPayload, length);
#ifdef SECURITY_CCM
halRfWriteTxBufSecure(txMpdu, mpduLength, length, BASIC_RF_LEN_AUTH, BASIC_RF_SECURITY_M);
txState.frameCounter++; // Increment frame counter field
#else
halRfWriteTxBuf(txMpdu, mpduLength);
#endif
// Turn on RX frame done interrupt for ACK reception
halRfEnableRxInterrupt();
// Send frame with CCA. return FAILED if not successful
if(halRfTransmit() != SUCCESS) {
status = FAILED;
}
// Wait for the acknowledge to be received, if any
if (pConfig->ackRequest) {
txState.ackReceived = FALSE;
// We'll enter RX automatically, so just wait until we can be sure that the ack reception should have finished
// The timeout consists of a 12-symbol turnaround time, the ack packet duration, and a small margin
halMcuWaitUs((12 * BASIC_RF_SYMBOL_DURATION) + (BASIC_RF_ACK_DURATION) + (2 * BASIC_RF_SYMBOL_DURATION) + 10);
// If an acknowledgment has been received (by RxFrmDoneIsr), the ackReceived flag should be set
status = txState.ackReceived ? SUCCESS : FAILED;
} else {
status = SUCCESS;
}
// Turn off the receiver if it should not continue to be enabled
if (!txState.receiveOn) {
halRfReceiveOff();
}
if(status == SUCCESS) {
txState.txSeqNumber++;
}
#ifdef SECURITY_CCM
halRfIncNonceTx(); // Increment nonce value
#endif
return status;
}
调用basicRfPacketIsReady()函数检测节点是否准备好读取新的数据包,在 basic_rf.c 中可以找到函数定义,如果准备好则返回TURE
uint8 basicRfPacketIsReady(void)
{
return rxi.isReady;
}
调用 basicRfReceive()函数,把收到的数据复制到接收节点的buffer 中,代码可以在 basic_rf.c 中可以找到函数定义,函数接收来自 Basic RF 层的数据包,并为所接收的数据和 RSSI 值配置缓冲区
uint8 basicRfReceive(uint8* pRxData, uint8 len, int16* pRssi)
{
// Accessing shared variables -> this is a critical region
// Critical region start
halIntOff();
memcpy(pRxData, rxi.pPayload, min(rxi.length, len));
if(pRssi != NULL) {
if(rxi.rssi < 128){
*pRssi = rxi.rssi - halRfGetRssiOffset();
}
else{
*pRssi = (rxi.rssi - 256) - halRfGetRssiOffset();
}
}
rxi.isReady = FALSE;
halIntOn();
// Critical region end
return min(rxi.length, len);
}
上面的配置流程了解了无线点灯的代码配置流程后,开始修改工程源码,无线点灯实验需要用到2块ZigBee节点板,一块作为ZigBee无线发送节点(按键发送无线指令),另一块作为ZigBee无线接收节点(接收无线指令控灯),在Basic RF工程中功能实现的代码都在light_switch.c文件中,开头用宏定义配置了节点主要参数,包含发送节点和接收节点的16位短地址、RF通道、PAN ID等关键参数
/***********************************************************************************
* CONSTANTS
*/
// Application parameters
#define RF_CHANNEL 25 // 2.4 GHz RF channel
// BasicRF address definitions
#define PAN_ID 0x2007
#define SWITCH_ADDR 0x2520
#define LIGHT_ADDR 0xBEEF
#define APP_PAYLOAD_LENGTH 1
#define LIGHT_TOGGLE_CMD 0
// Application states
#define IDLE 0
#define SEND_CMD 1
// Application role
#define NONE 0
#define SWITCH 1
#define LIGHT 2
#define APP_MODES 2
创建发送节点和接收节点的buffer,创建节点结构体
/***********************************************************************************
* LOCAL VARIABLES
*/
static uint8 pTxData[APP_PAYLOAD_LENGTH];
static uint8 pRxData[APP_PAYLOAD_LENGTH];
static basicRfCfg_t basicRfConfig;
在main函数里有很多配置是实验中用不到的,比如LCD屏幕显示、遥感按键操作等,可以直接屏蔽不需要的代码部分,修改后的main函数如下(屏蔽部分已删除,详细可自行查看工程),main函数中配置了发送节点/接收节点的结构体共同部分(PAN_ID、RF_CHANNEL、TRUE),初始化板级硬件和RF无线功能、关闭板载LED,然后是按照不同节点进入不同的节点函数进行循环(发送节点进入appSwitch()函数,屏蔽appLight()函数,接收节点进入appLight()函数,屏蔽appSwitch()函数),工程不会运行到HAL_ASSERT(FALSE)函数,以下为发送节点的main函数
void main(void)
{
// Config basicRF
basicRfConfig.panId = PAN_ID;
basicRfConfig.channel = RF_CHANNEL;
basicRfConfig.ackRequest = TRUE;
#ifdef SECURITY_CCM
basicRfConfig.securityKey = key;
#endif
// Initalise board peripherals
halBoardInit();
// Initalise hal_rf
if(halRfInit()==FAILED) {
HAL_ASSERT(FALSE);
}
// Indicate that device is powered
halLedClear(1);
halLedClear(2);
//无线点灯核心函数
appSwitch();
// appLight();
// Role is undefined. This code should not be reached
HAL_ASSERT(FALSE);
}
发送节点函数appSwitch(),将点灯指令放到pTxData发送数组中,配置发送节点短地址后初始化节点结构体,发送节点不需要做无线接收调用basicRfReceiveOff()函数关闭无线接收功能;在while循环中判断按键是否按下,按下则调用basicRfSendPacket()函数发送无线信息,LED2闪烁提示无线信息已发送,随后进入低功耗模式等待按键再次按下
static void appSwitch()
{
#ifdef ASSY_EXP4618_CC2420
halLcdClearLine(1);
halLcdWriteSymbol(HAL_LCD_SYMBOL_TX, 1);
#endif
pTxData[0] = LIGHT_TOGGLE_CMD;
// Initialize BasicRF
basicRfConfig.myAddr = SWITCH_ADDR;
if(basicRfInit(&basicRfConfig)==FAILED) {
HAL_ASSERT(FALSE);
}
// Keep Receiver off when not needed to save power
basicRfReceiveOff();
// Main loop
while (TRUE) {
// 按键判断
if( halButtonPushed() == HAL_BUTTON_1 ) {
basicRfSendPacket(LIGHT_ADDR, pTxData, APP_PAYLOAD_LENGTH);
HAL_LED_TGL_2();
halMcuWaitMs(50);
HAL_LED_TGL_2();
// Put MCU to sleep. It will wake up on joystick interrupt
halIntOff();
halMcuSetLowPowerMode(HAL_MCU_LPM_3); // Will turn on global
// interrupt enable
halIntOn();
}
}
}
接收节点函数appLight()(需要屏蔽main函数中的appSwitch()),配置接收节点短地址后初始化节点结构体,basicRfReceiveOn()函数打开无线接收功能,在while循环中检测接收节点是否做好接收无线数据包的准备,然后判断是否收到数据包;在收到数据包后判断无线指令是否为点灯指令,是的话闪烁LED2提示成功接收无线消息,将LED1状态进行反转完成控灯操作
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) {
HAL_LED_TGL_2();
halMcuWaitMs(50);
HAL_LED_TGL_2();
halLedToggle(1);
}
}
}
}
如果所用到的ZigBee开发板并不是TI公司的开发板,需要检查工程中的按键引脚和LED引脚定义是否可以直接应用到所用的ZigBee开发板上,在hal_board.h文件中找到LED和按键的端口引脚宏定义,按照ZigBee开发板原理图做对应修改
// LEDs
#define HAL_BOARD_IO_LED_1_PORT 1 // Green
#define HAL_BOARD_IO_LED_1_PIN 0
#define HAL_BOARD_IO_LED_2_PORT 1 // Red
#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 1 // Button S1 by YUAN
#define HAL_BOARD_IO_BTN_1_PIN 2
工程编译成功后分别上传至ZigBee发送节点和ZigBee接收节点
按下发送节点的按键进行控制指令发送,观察接收节点板的LED1状态