CC2530 ZDDC 设备开发
本文将介绍如何使用 ZDDC 协议和 ACOINFO ZigBee 通用设备应用规范开发一个能接入到 EdgerOS 的 ZigBee 智能灯泡设备。
本实验使用开发板上的 LED 灯来模拟灯泡,LED 灯只有开和关两种状态,属于开关量,可以使用 DIO 通道 0;同时该 ZigBee 智能灯泡还会周期性主动上报内部电压,电压值是模拟量,可以使用 AIO 通道 0。
硬件准备
CC2530 开发板
CC2530 是美国 TI 德州仪器公司推出的一款增强型 8051 ZigBee MCU,带有 8KB RAM,FLASH 容量最大可达 256KB, 结合 TI 提供的 ZigBee 协议栈(Z-Stack™),形成一个强大和完整的 ZigBee 解决方案。
本实验使用的是一块创思通信的 CC2530 开发板(包括核心板和底板),实物图如下所示:
ZigBee 仿真器
CC2530 开发板的 FLASH 编程和调试需要用到 ZigBee 仿真器(型号为 SmartRF04EB),实物图如下所示:
软件准备
安装 IAR
开发 CC2530 需要用到 IAR for 8051 集成开发环境,本实验使用的 IAR 版本为:IAR EW for 8051 10.10.1,安装完成后如下所示:
安装 SmartRF Flash Programmer
ZigBee 仿真器需要使用 SmartRF Flash Programmer 软件,版本为:SmartRF_Flash_Programmer-1.12.8,点击获取,安装完成后如下所示:
开发应用
本小节将介绍如何在 ACOINFO TI CC2530 ZigBee 软件包的基础上开发一个智能灯泡。
下载 ACOINFO TI CC2530 ZigBee 软件包
ACOINFO TI CC2530 ZigBee 软件包基于 TI 官方的 ZigBee 协议栈 Z-Stack 3.0.2 进行二次开发。
在一个较短的不带有中文的目录,使用 git 工具从 MS-RTOS github 社区 下载 ACOINFO TI CC2530 ZigBee 软件包,命令如下:
git clone https://github.com/ms-rtos/ti_cc2530_zstack.git
如果 github 下载速度慢,可以试试 gitee:
git clone https://gitee.com/ms-rtos/ti_cc2530_zstack.git
应用层参考代码位于:Z-Stack 3.0.2\Projects\zstack\HomeAutomation\AcoinfoZigBeeSample\Source
,如下图所示:
复制工程
进入目录 Z-Stack 3.0.2\Projects\zstack\HomeAutomation
,复制 AcoinfoZigBeeSample
文件夹并粘贴重命名为 AcoinfoZigBeeSampleLight
,如下图所示:
进入 Z-Stack 3.0.2\Projects\zstack\HomeAutomation\AcoinfoZigBeeSampleLight\CC2530DB
目录,双击打开 acoinfoSample.eww
工程,如下图所示:
修改读写属性列表
打开 App 文件夹,如下图所示:
acoinfo_generic_profile.h
文件为 ACOINFO ZigBee 通用设备应用规范的相关定义,代码如下:
/*
* 端点号
*/
#define ACOINFO_ZB_ENDPOINT 1
/*
* Cluster ID
*/
#define ACOINFO_ZB_BASIC_CLUSTER 0x0000 // ZHA 相关基础信息
#define ACOINFO_ZB_DIO_CLUSTER 0x0006 // DIO 开关量
#define ACOINFO_ZB_AIO_CLUSTER 0x000C // AIO 模拟量
#define ACOINFO_ZB_MEM_CLUSTER 0x0500 // MEM 内存
#define ACOINFO_ZB_COMMON_CLUSTER 0x0300 // 通用属性
/*
* Attribute ID
*/
#define ACOINFO_ZB_MODEL_ID_ATTR 0x0005 // model ID
#define ACOINFO_ZB_MANUFACTURER_NAME_ATTR 0x0004 // manufacturer 名
#define ACOINFO_ZB_DIO_ATTR_BASE 0x2000 // DIO Attribute ID base
#define ACOINFO_ZB_DIO_ATTR_NR 32 // DIO 通道个数
#define ACOINFO_ZB_DIO_ATTR_END (ACOINFO_ZB_DIO_ATTR_BASE + ACOINFO_ZB_DIO_ATTR_NR)
#define ACOINFO_ZB_AIO_ATTR_BASE 0x2000 // AIO Attribute ID base
#define ACOINFO_ZB_AIO_ATTR_NR 32 // AIO 通道个数
#define ACOINFO_ZB_AIO_ATTR_END (ACOINFO_ZB_AIO_ATTR_BASE + ACOINFO_ZB_AIO_ATTR_NR)
#define ACOINFO_ZB_MEM_ATTR_BASE 0x2000 // MEM Attribute ID base
#define ACOINFO_ZB_MEM_ATTR_NR 32 // MEM 通道个数
#define ACOINFO_ZB_MEM_ATTR_END (ACOINFO_ZB_MEM_ATTR_BASE + ACOINFO_ZB_MEM_ATTR_NR)
#define ACOINFO_ZB_COMMON_ATTR_BASE 0x2000 // Common Attribute ID base
/*
* 通用属性 ID
*/
#define ACOINFO_ZB_DIO_ACTIVE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 0) // DIO 有效通道
#define ACOINFO_ZB_DIO_WRITE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 1) // DIO 可写通道
#define ACOINFO_ZB_AIO_ACTIVE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 2) // AIO 有效通道
#define ACOINFO_ZB_AIO_WRITE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 3) // AIO 可写通道
#define ACOINFO_ZB_MEM_ACTIVE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 4) // MEM 有效通道
#define ACOINFO_ZB_MEM_WRITE_ATTR (ACOINFO_ZB_COMMON_ATTR_BASE + 5) // MEM 可写通道
在 acoinfo_sample_data.c
文件中定义 ACOINFO ZigBee 通用设备的 model ID 和 manufacturer 名字,代码如下:
static const uint8 acoinfoSample_GenericModel[] = {
22,'a','c','o','i','n','f','o','.','z','i','g','b','e','e','.','g','e','n','e','r','i',
'c'
}; // model ID
static const uint8 acoinfoSample_ManufacturerName[] = {
17, 'a','c','o','i','n','f','o','.','l','i','g','h','t','.','l','e','d'
}; // manufacturer 名字
在 acoinfo_sample.h
文件中定义 LED 所使用的 DIO 通道号和 Vdd 所使用的 AIO 通道号,代码如下:
#define ACOINFO_SAMPLE_LED_ATTR_CHAN_NUM 0 // LED DIO 通道号
#define ACOINFO_SAMPLE_VDD_ATTR_CHAN_NUM 0 // Vdd AIO 通道号
根据上述代码所使用的通道号在 acoinfo_sample_data.c
文件中定义 DIO 和 AIO 的通道掩码,LED 使用 DIO 通道 0 可读可写; Vdd 使用 AIO 通道 0 只读,代码如下:
static uint32 dioActiveMask = 0x1; // DIO 有效通道掩码
static uint32 dioWritableMask = 0x1; // DIO 可写通道掩码
static uint32 aioActiveMask = 0x1; // AIO 有效通道掩码
static uint32 aioWritableMask = 0x0; // AIO 可写通道掩码
在 acoinfo_sample_data.c
文件中修改变量 acoinfoSample_Attrs[]
,只保留如下代码:
CONST zclAttrRec_t acoinfoSample_Attrs[] =
{
/*
* 添加 ACOINFO ZigBee 通用设备的 model ID (只读)
*/
ACOINFO_ZB_GENERIC_MODEL( acoinfoSample_GenericModel )
/*
* 添加 manufacturer 名字(只读)
*/
ACOINFO_ZB_MANUFACTURER_NAME( acoinfoSample_ManufacturerName )
/*
* 添加一个开关量,即 LED 所使用的属性(可读写)
*/
ACOINFO_ZB_DIO( ACOINFO_SAMPLE_LED_ATTR_CHAN_NUM, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE )
/*
* 添加一个模拟量,即 Vdd 所使用的属性(只读)
*/
ACOINFO_ZB_AIO( ACOINFO_SAMPLE_VDD_ATTR_CHAN_NUM, ACCESS_CONTROL_READ )
/*
* 添加开关量通用属性(只读)
*/
ACOINFO_ZB_COMMON( ACOINFO_ZB_DIO_ACTIVE_ATTR, dioActiveMask )
ACOINFO_ZB_COMMON( ACOINFO_ZB_DIO_WRITE_ATTR, dioWritableMask )
/*
* 添加模拟量通用属性(只读)
*/
ACOINFO_ZB_COMMON( ACOINFO_ZB_AIO_ACTIVE_ATTR, aioActiveMask )
ACOINFO_ZB_COMMON( ACOINFO_ZB_AIO_WRITE_ATTR, aioWritableMask )
};
下面介绍上述各个成员的参数:
ACOINFO_ZB_GENERIC_MODEL(str)
参数
str
类型为:字符串;访问控制权限为:只读;数据存放格式:数组的第 0 个元素存放字符串的长度值,从第一个元素开始存放字符串,字符串之间用.
隔开;ACOINFO ZigBee 通用设备的 model ID 固定为字符串acoinfo.zigbee.generic
。ACOINFO_ZB_MANUFACTURER_NAME(str)
参数
str
类型为:字符串;访问控制权限为:只读;数据存放格式:数组第 0 个元素存放字符串的长度值,从第一个元素开始存放字符串,字符串之间用.
隔开,规则为device_vendor.device_type.device_name
,即设备厂商.设备类型.设备名
。ACOINFO_ZB_DIO(n, flag)
参数
n
表示通道号(0~31);参数
flag
表示访问控制权限(可读/可写)。ACOINFO_ZB_AIO(n, flag)
参数
n
表示通道号(0~31);参数
flag
表示访问控制权限(可读/可写)。ACOINFO_ZB_COMMON(attr, var)
参数
attr
:通用属性 ID;参数
var
:属性的值,类型为无符号 32 位,采用位图的方式表示,每一位代表一个通道,var
的值要与通道号对应。
修改读写回调函数
打开 App 文件夹中的 acoinfo_sample.c
文件,修改属性读写回调函数 acoinfoSample_ReadWriteCallBack
代码如下所示:
static
ZStatus_t acoinfoSample_ReadWriteCallBack( uint16 clusterId,//cluster ID
uint16 attrId,//属性ID = 对应属性通道的基数 + 通道号
uint8 oper,//操作方法:读属性值/写属性值/获取值的长度
uint8 *pValue,//属性值的数据缓冲区
uint16 *pLen //属性值的长度缓冲区
)
{
ZStatus_t ret = ZCL_STATUS_FAILURE;
switch( clusterId )
{
/*
* 处理 DIO 属性数据
*/
case ACOINFO_ZB_DIO_CLUSTER:
ret = acoinfoSample_HandleDioData( attrId, oper, pValue, pLen );
break;
/*
* 处理 AIO 属性数据
*/
case ACOINFO_ZB_AIO_CLUSTER:
ret = acoinfoSample_HandleAioData( attrId, oper, pValue, pLen );
break;
/*
* 处理 MEM 属性数据
*/
case ACOINFO_ZB_MEM_CLUSTER:
break;
default:
break;
}
return ( ret );
}
添加 LED 控制代码
代码中 LED 的开关控制是通过判断属性值来实现的,即:当 EdgerOS 发送过来的开关量属性为 true
时,打开 LED,否则关掉 LED。
在 acoinfo_sample.c
文件中定义 LED 所使用的属性 ID,代码如下:
#define ACOINFO_SAMPLE_LED_ATTR_ID \
ACOINFO_ZB_DIO_ATTR_BASE + ACOINFO_SAMPLE_LED_ATTR_CHAN_NUM
在该文件中定义 LedOnOffStatus
用来保存 LED 开关的状态和属性值,代码如下:
static bool LedOnOffStatus = false; //false 表示关
在该文件中定义属性值的长度,DIO 属性值的长度固定为 1,代码如下:
#define SAMPLEAPP_DIO_ATTR_DATA_LEN 1
在该文件的开关量处理函数中 acoinfoSample_HandleDioData
添加如下代码:
static ZStatus_t acoinfoSample_HandleDioData( uint16 attrId,
uint8 oper,
uint8 *pValue,
uint16 *pLen )
{
ZStatus_t ret = ZCL_STATUS_FAILURE;
switch( oper )
{
/*
* 读取 LED 的状态
*/
case ZCL_OPER_READ:
if ( attrId == ACOINFO_SAMPLE_LED_ATTR_ID )
{
*pValue = LedOnOffStatus;
*pLen = SAMPLEAPP_DIO_ATTR_DATA_LEN;
ret = ZCL_STATUS_SUCCESS;
}
break;
/*
* 控制 LED 的状态
*/
case ZCL_OPER_WRITE:
if ( attrId == ACOINFO_SAMPLE_LED_ATTR_ID )
{
LedOnOffStatus = *pValue;
acoinfoSample_LedOnOff( LedOnOffStatus );
ret = ZCL_STATUS_SUCCESS;
}
break;
/*
* 获取属性值的长度
*/
case ZCL_OPER_LEN:
*pLen = SAMPLEAPP_DIO_ATTR_DATA_LEN;
ret = ZCL_STATUS_SUCCESS;
break;
default:
break;
}
return ( ret );
}
代码中 LED ON/OFF 函数 acoinfoSample_LedOnOff
的实现如下:
static void acoinfoSample_LedOnOff( bool status )
{
uint8 mode;
if (status == true)
{
mode = HAL_LED_MODE_ON;
debugMessage( "turn ON\r\n" );
}
else
{
mode = HAL_LED_MODE_OFF;
debugMessage( "turn OFF\r\n" );
}
HalLedSet( HAL_LED_1, mode );
}
添加 Vdd 读取代码
在 acoinfo_sample.c
文件中定义 Vdd 所使用的属性 ID,代码如下:
#define ACOINFO_SAMPLE_VDD_ATTR_ID \
ACOINFO_ZB_AIO_ATTR_BASE + ACOINFO_SAMPLE_VDD_ATTR_CHAN_NUM
定义 vddValue
来保存内部电压值,代码如下:
static float vddValue = 0;
定义 AIO 属性值的长度,固定为 4,代码如下:
#define SAMPLEAPP_AIO_ATTR_DATA_LEN 4
在模拟量处理函数 acoinfoSample_HandleAioData
中添加如下代码:
static ZStatus_t acoinfoSample_HandleAioData( uint16 attrId,
uint8 oper,
uint8 *pValue,
uint16 *pLen )
{
ZStatus_t ret = ZCL_STATUS_FAILURE;
switch( oper )
{
/*
* 获取内部电压值
*/
case ZCL_OPER_READ:
if ( attrId == ACOINFO_SAMPLE_VDD_ATTR_ID )
{
vddValue = acoinfoSample_VddValueGet();
*(float *)pValue = vddValue;
*pLen = SAMPLEAPP_AIO_ATTR_DATA_LEN;
ret = ZCL_STATUS_SUCCESS;
}
break;
case ZCL_OPER_WRITE:
break;
/*
* 获取属性值的长度
*/
case ZCL_OPER_LEN:
*pLen = SAMPLEAPP_AIO_ATTR_DATA_LEN;
ret = ZCL_STATUS_SUCCESS;
break;
default:
break;
}
return ( ret );
}
代码中内部电压读取函数 acoinfoSample_VddValueGet
的实现如下所示:
static float acoinfoSample_VddValueGet( void )
{
HalAdcSetReference( HAL_ADC_REF_125V );
return ( ((float)HalAdcCheckVddRaw() / 127 * 1.15) );
}
添加电压上报代码
在 acoinfo_sample.c
文件中定义一个周期性上报事件,代码如下:
#define SAMPLEAPP_END_DEVICE_REPORT_EVT 0x0002 // 事件 ID
#define SAMPLEAPP_END_DEVICE_REPORT_DELAY 5000 // 任务周期
在 acoinfo_sample.c
文件的应用层初始化函数 acoinfoSample_Init
中使用定时器启动一个任务,周期性上报内部电压值,启动定时任务代码如下:
osal_start_timerEx( acoinfo_Sample_TaskID, SAMPLEAPP_END_DEVICE_REPORT_EVT,
SAMPLEAPP_END_DEVICE_REPORT_DELAY );
在 acoinfo_sample.c
文件的事件处理函数 acoinfoSample_event_loop
中添加如下代码处理上报任务:
if ( events & SAMPLEAPP_END_DEVICE_REPORT_EVT )
{
/*
* 处理上报事件
*/
acoinfoSample_ReportEvent();
/*
* 启动下一次任务
*/
osal_start_timerEx( acoinfo_Sample_TaskID,
SAMPLEAPP_END_DEVICE_REPORT_EVT,
SAMPLEAPP_END_DEVICE_REPORT_DELAY );
}
注意 处理完上报事件后,使用 osal_start_timerEx 启动下一次任务。
在上报事件处理函数中使用 zcl_SendReportCmd
上报内部电压,函数 acoinfoSample_ReportEvent
的代码实现如下:
static void acoinfoSample_ReportEvent( void )
{
static uint8 seqNum = 0;
afAddrType_t dstAddr;
zclReportCmd_t *pReportCmd;
float vddValue;
pReportCmd = (zclReportCmd_t *)osal_mem_alloc( sizeof(zclReportCmd_t) + sizeof(zclReport_t) );
if (pReportCmd == NULL)
{
return;
}
/*
* Get Vdd value
*/
vddValue = acoinfoSample_VddValueGet();
dstAddr.addrMode = afAddr16Bit;
dstAddr.endPoint = ACOINFO_ZB_ENDPOINT;
dstAddr.addr.shortAddr = 0;
pReportCmd->numAttr = 1;
pReportCmd->attrList[0].attrID = ACOINFO_SAMPLE_VDD_ATTR_ID;
pReportCmd->attrList[0].dataType = ZCL_DATATYPE_SINGLE_PREC;
pReportCmd->attrList[0].attrData = (uint8 *)&vddValue;
zcl_SendReportCmd( ACOINFO_ZB_ENDPOINT, &dstAddr,
ACOINFO_ZB_AIO_CLUSTER, pReportCmd,
ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, seqNum++ );
osal_mem_free( pReportCmd );
}
代码中的 cluster ID
是:ACOINFO_ZB_AIO_CLUSTER
,attribute ID
是 ACOINFO_SAMPLE_VDD_ATTR_ID
,上报的值为节点的内部电压,发送的目的地是 0,即 EdgerOS 的 ZigBee 协调器地址。
编译工程
点击编译按钮或按键盘 F7 键编译工程,将生成 Hex 文件,如下图所示:
烧写镜像
使用 Mini USB 线连接 CC2530 开发板到 PC 的 USB 插口,使用 ZigBee 仿真器把开发板连接到 PC,如下所示:
打开 SmartRF Flash Programmer
,选择 hex
文件所在路径,烧写 hex
文件,具体操作如下所示:
入网验证
验证前,请参考《 ZDDC 协议介绍》章节中的 ZigBee 设备入网流程,完成以下步骤:
进入扫描设备界面,如下图所示:
将 CC2530 开发板接入 PC,打开设备管理器,查看 PC 为 CC2530 开发板分配的 COM 号,如下图所示:
使用 PuTTY 作为串口调试工具,打开 PuTTY 进入
PuTTY Configuration
界面,选择Session
类,Connection type
选择:Serial
,Serial line
为 CC2530 开发板接入 PC 后分配的 COM 号(如 COM7),Speed
设置为115200
,最后点击Open
按钮进入 PuTTY 终端:开发板上电,按下 S1 执行关联入网操作,串口打印结果如下图所示:
ZDO state 为:
Started as device after authentication
,表示入网成功即成功连接到 EdgerOS 。此时,EdgerOS 会发现新的设备,点击添加按钮完成新设备的添加:
如果 ZDO state 的打印结果为:Initialized - not started automatically
,表示入网失败,需要按下 RESET 重启设备或者按下 S1 重新关联入网。
在该工程中实现的功能及其相关描述如下表所示:
功能 | cluster | 通道号 | 访问控制权限 | 动作 |
---|---|---|---|---|
LED 控制 | 开关量 | 0 | 可读写 | ON/OFF |
Vdd 上报 | 模拟量 | 0 | 只读 | 上报 |