CC2530 ZDDC 设备开发

更新时间:
2024-04-25
下载文档

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 开发板(包括核心板和底板),实物图如下所示:

CC2530

ZigBee 仿真器

CC2530 开发板的 FLASH 编程和调试需要用到 ZigBee 仿真器(型号为 SmartRF04EB),实物图如下所示:

SmartRF04EB

软件准备

安装 IAR

开发 CC2530 需要用到 IAR for 8051 集成开发环境,本实验使用的 IAR 版本为:IAR EW for 8051 10.10.1,安装完成后如下所示:

IAR

安装 SmartRF Flash Programmer

ZigBee 仿真器需要使用 SmartRF Flash Programmer 软件,版本为:SmartRF_Flash_Programmer-1.12.8,点击获取open in new window,安装完成后如下所示:

SmartRF-Flash-Programmer

开发应用

本小节将介绍如何在 ACOINFO TI CC2530 ZigBee 软件包的基础上开发一个智能灯泡。

下载 ACOINFO TI CC2530 ZigBee 软件包

ACOINFO TI CC2530 ZigBee 软件包基于 TI 官方的 ZigBee 协议栈 Z-Stack 3.0.2 进行二次开发。

在一个较短的不带有中文的目录,使用 git 工具从 MS-RTOS github 社区open in new window 下载 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,如下图所示:

source-code

复制工程

进入目录 Z-Stack 3.0.2\Projects\zstack\HomeAutomation,复制 AcoinfoZigBeeSample 文件夹并粘贴重命名为 AcoinfoZigBeeSampleLight,如下图所示:

AcoinfoZigBeeSampleLight

进入 Z-Stack 3.0.2\Projects\zstack\HomeAutomation\AcoinfoZigBeeSampleLight\CC2530DB 目录,双击打开 acoinfoSample.eww 工程,如下图所示:

acoinfoSample.eww

修改读写属性列表

打开 App 文件夹,如下图所示:

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 )
};

下面介绍上述各个成员的参数:

  1. ACOINFO_ZB_GENERIC_MODEL(str)

    参数 str 类型为:字符串;访问控制权限为:只读;数据存放格式:数组的第 0 个元素存放字符串的长度值,从第一个元素开始存放字符串,字符串之间用 . 隔开;ACOINFO ZigBee 通用设备的 model ID 固定为字符串 acoinfo.zigbee.generic

  2. ACOINFO_ZB_MANUFACTURER_NAME(str)

    参数 str 类型为:字符串;访问控制权限为:只读;数据存放格式:数组第 0 个元素存放字符串的长度值,从第一个元素开始存放字符串,字符串之间用 . 隔开,规则为device_vendor.device_type.device_name ,即 设备厂商.设备类型.设备名

  3. ACOINFO_ZB_DIO(n, flag)

    参数 n 表示通道号(0~31);

    参数 flag 表示访问控制权限(可读/可写)。

  4. ACOINFO_ZB_AIO(n, flag)

    参数 n 表示通道号(0~31);

    参数 flag 表示访问控制权限(可读/可写)。

  5. 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_CLUSTERattribute IDACOINFO_SAMPLE_VDD_ATTR_ID,上报的值为节点的内部电压,发送的目的地是 0,即 EdgerOS 的 ZigBee 协调器地址。

编译工程

点击编译按钮或按键盘 F7 键编译工程,将生成 Hex 文件,如下图所示:

compile

烧写镜像

使用 Mini USB 线连接 CC2530 开发板到 PC 的 USB 插口,使用 ZigBee 仿真器把开发板连接到 PC,如下所示:

CC2530-SmartRF04EB

打开 SmartRF Flash Programmer,选择 hex 文件所在路径,烧写 hex 文件,具体操作如下所示:

Download

入网验证

验证前,请参考《 ZDDC 协议介绍》章节中的 ZigBee 设备入网流程,完成以下步骤:

  1. 进入扫描设备界面,如下图所示:

    device-search

  2. 将 CC2530 开发板接入 PC,打开设备管理器,查看 PC 为 CC2530 开发板分配的 COM 号,如下图所示:

    com-port

    使用 PuTTY 作为串口调试工具,打开 PuTTY 进入 PuTTY Configuration 界面,选择 Session 类, Connection type 选择:SerialSerial line 为 CC2530 开发板接入 PC 后分配的 COM 号(如 COM7),Speed 设置为 115200,最后点击 Open 按钮进入 PuTTY 终端:

    PuTTY

  3. 开发板上电,按下 S1 执行关联入网操作,串口打印结果如下图所示:

    Net-validation

    ZDO state 为:Started as device after authentication,表示入网成功即成功连接到 EdgerOS 。

  4. 此时,EdgerOS 会发现新的设备,点击添加按钮完成新设备的添加:

    new-device

    added-device

如果 ZDO state 的打印结果为:Initialized - not started automatically,表示入网失败,需要按下 RESET 重启设备或者按下 S1 重新关联入网。

在该工程中实现的功能及其相关描述如下表所示:

功能cluster通道号访问控制权限动作
LED 控制开关量0可读写ON/OFF
Vdd 上报模拟量0只读上报
文档内容是否对您有所帮助?
有帮助
没帮助