使用ZIGBEE协议栈将数据上传至阿里云(STM32)(一)

ZIGBEE协议栈介绍

Zigbee是一种低功耗、低成本的无线通信协议,特别适用于物联网设备之间的通信。Zigbee协议栈是一组硬件和软件层级结构,用于支持Zigbee设备之间的通信。

Zigbee协议栈通常由以下几个层级组成:

1. 物理层(Physical Layer):负责处理物理无线通信的细节,例如频率、功率控制和数据传输速率等。物理层使用的是2.4GHz频段,并支持多种调制方式。

2. 媒介接入控制层(Medium Access Control, MAC):负责管理通信介质的访问,以便多个设备可以按照一定的规则共享通信介质。MAC层处理一些基本的网络控制功能,例如设备的寻址、帧结构定义和冲突避免。

3. 网络层(Network Layer):负责设备的路由和组网管理。网络层处理设备之间的寻址、路由选择和数据包转发等任务。它使用分层路由技术,可实现灵活的网络拓扑结构,如星形、网状和多跳网络等。

4. 应用层(Application Layer):负责定义应用层协议和数据格式,以便不同设备之间可以进行有效的通信。应用层定义了设备之间的命令、控制和数据交换等。

Zigbee协议栈的设计目标是提供低功耗、低数据速率和较短通信距离的通信解决方案。它广泛应用于家庭自动化、智能电网、工业自动化和物联网等领域,能够支持大规模设备的无缝连接和通信。

物理层

其中物理层可简单理解为,将一个设备的数据转化为电磁波信号之后,通过物理介质发送给另一个设备,再由另一个设备解读电磁波信号获取数据。

MAC层

MAC(媒体接入控制层)的作用是有序地利用网络通讯资源来进行可靠通信。

由于电磁波和物理介质的限制,同一物理资源在同一时刻只能被一个网络设备占用来发送数据,为控制多个网络设备发送数据。MAC层将设备划分成协调器和普通设备(路由器和终端),协调器产生并发送信标帧,普通设备根据所发出的信标帧和协调器同步,即可组建网络。

网络层(核心协议)

基于IEEE 802.15.4协议之上,主要负责多个设备之间的组网,其优点为支持更多设备进行组网、能够进行多通道通信服务、支持自组网;其次负责设备之间的控制指令和设备状态数据的传输,如温度监测、开关控制;还负责网络安全管理。

目前较多采用的是ZigBee 2007 (Pro)版本。

应用层(一套标准规范,即应用协议)

应用层规定了对象的属性和状态。如灯具备开关的属性,这个属性由开(1)和关(0)两种状态。ZigBee技术开发一般就是指基于ZigBee应用协议的技术开发。

 为解决同一领域不同公司设备兼容性问题,ZigBee联盟根据不同领域(Profile)推出了不同的协议。

之后,为解决不同领域的兼容性问题,ZigBee联盟推出ZigBee3.0协议,提升了安全性和稳定性,是目前应用最为广泛的ZigBee应用协议。

TI 技术方案(Chipcon)

 TI Z-Stack是ZigBee协议的代码实现, 是TI(德州仪器)针对 ZigBee 3.0 协议而开发的技术方案,也称为TI ZigBee 3.0 协议栈,包含应用层(Z-Stack 3.0)和内核层(2.7.1)。

本次项目采用TI Z-Stack3.0.1_2协议栈以及CC2530芯片进行学习开发,Z-Stack 3.0.1 是到目前为止最新版本。

Z-Stack 3.0官方下载地址 https://www.ti.com/tool/Z-STACK 。

ZigBee设备类型

操作系统的任务调度

任务调度涉及任务、任务池、优先级、轮询和系统调度周期。其中任务池为储存多个任务的缓冲区,适用于任务池的数据结构有多种,比如队列(先进先出)、堆栈(先进后出)和树状结构(遍历),这些数据结构的实现方式可以是静态的数组或者是动态的链表等;优先级为在同一时刻执行任务的等级划分;轮询为系统每隔一段时间在任务池中检查需要处理的任务;“每隔一段时间”指的就是操作系统调度周期。

简单的操作系统原理代码如下:

void main()
{
    initLed();//初始化LED灯
    taskListInit();//初始化任务池
    
    addTask(2000, TASK_LED_ON);//往任务池中添加一个任务,即2s后打开LED
    addTask(3000, TASK_LED_OFF);//往任务池中添加一个任务,即3s后关闭LED
    
    //每隔1ms轮询1次
    while(1) {
        delayMs(1);//暂停1ms
        polling();//轮询
    }
}

OSAL(Operating System Abstraction Layer,系统抽象层)

可以通俗地理解为一个简化版的操作系统,为Z-Stack的正确运行提供了内存管理、中断管理和任务调度等基本功能。

ZMain.c文件,可以找到main()函数,其代码如下:

int main( void )  
{  
  // Turn off interrupts  
  osal_int_disable( INTS_ALL );   	// 关闭所有中断
  
  // Initialization for board related stuff such as LEDs  
  HAL_BOARD_INIT();  	// 初始化板载资源,比如PA、时钟源等
  
  // Make sure supply voltage is high enough to run  
  zmain_vdd_check();  // 检测供电电压是否可以支撑芯片正常运行
  
  // Initialize board I/O  
  InitBoard( OB_COLD );  // 初始化板载I/O,比如按键配置为输入
  
  // Initialze HAL drivers  
  HalDriverInit();  // 初始化硬件适配层,比如串口、显示器等
  
  // Initialize NV System  
  osal_nv_init( NULL );  // 初始化NV(芯片内部FLASH的一块空间)
  
  // Initialize the MAC  
  ZMacInit();  // 初始化MAC层(数据链路层)
 
  // Determine the extended address  
  zmain_ext_addr();  // 确定芯片的物理地址
  
#if defined ZCL_KEY_ESTABLISH  
  // Initialize the Certicom certificate information.  
  zmain_cert_init();  // 初始化认证信息
#endif  
  
  // Initialize basic NV items  
  zgInit();  // 初始化存储在NV中的协议栈全局信息,如网络启动方式等
  
#ifndef NONWK  
// Since the AF isn't a task, call it's initialization routine
  afInit();  // 初始化AF(射频)
#endif  
  
  // Initialize the operating system  
  osal_init_system();  // 初始化OSAL(操作系统抽象层)
  
  // Allow interrupts  
  osal_int_enable( INTS_ALL );  // 使能所有中断
  
  // Final board initialization  
  InitBoard( OB_READY );  // 初始化板载IO资源,比如按键
  
  // Display information about this device  
  zmain_dev_info();  // 在显示器上显示设备物理地址
 
  /* Display the device info on the LCD */  
#ifdef LCD_SUPPORTED  
  zmain_lcd_init();  // 在显示器上显示设备信息,比如制造商等
#endif   


#ifdef WDT_IN_PM1  
  /* If WDT is used, this is a good place to enable it. */  
  WatchDogEnable( WDTIMX );  // 启动看门狗功能
#endif  

  /* 进入系统轮询 */  
  osal_start_system(); // No Return from here  
 

  return 0;  // Shouldn't get here.  
} // main()

上述代码中有两个关键的函数,代码如下:

//初始化OSAL,包括初始化任务池
osal_init_system();

//轮询任务池
osal_start_system();

osal_init_system()函数代码如下:

uint8 osal_init_system( void )
{
#if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
  // 初始化内存分配系统
  osal_mem_init();
#endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */

  // 初始化消息队列
  osal_qHead = NULL;

  // 初始化OSAL定时器
  osalTimerInit();

  // 初始化电源管理系统
  osal_pwrmgr_init();

#ifdef USE_ICALL
  osal_prepare_svc_enroll();
#endif /* USE_ICALL */

  // 初始化任务池
  osalInitTasks();

#if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
  // Setup efficient search for the first free block of heap.
  osal_mem_kick();
#endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */

#ifdef USE_ICALL
  // Initialize variables used to track timing and provide OSAL timer service
  osal_last_timestamp = (uint_least32_t) ICall_getTicks();
  osal_tickperiod = (uint_least32_t) ICall_getTickPeriod();
  osal_max_msecs = (uint_least32_t) ICall_getMaxMSecs();
  /* Reduce ceiling considering potential latency */
  osal_max_msecs -= 2;
#endif /* USE_ICALL */

  return ( SUCCESS );
}

 osal_start_system()函数代码如下:

void osal_start_system( void )
{
#ifdef USE_ICALL
  /* Kick off timer service in order to allocate resources upfront.
   * The first timeout is required to schedule next OSAL timer event
   * as well. */
  ICall_Errno errno = ICall_setTimer(1, osal_msec_timer_cback,
                                     (void *) osal_msec_timer_seq,
                                     &osal_timerid_msec_timer);
  if (errno != ICALL_ERRNO_SUCCESS)
  {
    ICall_abort();
  }
#endif /* USE_ICALL */

#if !defined ( ZBIT ) && !defined ( UBIT )
  //主循环
  for(;;)
#endif
  {
	//系统轮询调度
    osal_run_system();

#ifdef USE_ICALL
    ICall_wait(ICALL_TIMEOUT_FOREVER);
#endif /* USE_ICALL */
  }
}

在osal_start_system()函数的主循环中,循环调用了 osal_run_system()函数,该函数主要工作轮询任务池。

void osal_run_system( void )    
{    
    uint8 idx = 0;    
   
  /* 更新时间,并整理出到期的任务。系统的时钟周期是:320us */  
  osalTimeUpdate();  
  Hal_ProcessPoll();// 硬件适配层中断查询  
    
  do {    
    if (tasksEvents[idx])// 查看是否有任务需要处理  
    {    
      break;    
    }    
  } while (++idx < tasksCnt);// 轮询整个任务池  
    
  if (idx < tasksCnt)//循环结束后,如果idx < tasksCnt表示任务池有任务需要处理
  {    
    uint16 events;    
    halIntState_t intState;   
    HAL_ENTER_CRITICAL_SECTION(intState);//关闭中断  
    events = tasksEvents[idx];//evets中保存了该任务中的待处理事件  
    tasksEvents[idx] = 0;//清空此任务中的所有待处理事件 
    HAL_EXIT_CRITICAL_SECTION(intState);//恢复中断  
    
    activeTaskID = idx;    
    events = (tasksArr[idx])( idx, events ); // 处理任务中的事件  
    activeTaskID = TASK_NO_TASK;    
    
    HAL_ENTER_CRITICAL_SECTION(intState);//关闭中断  
    tasksEvents[idx] |= events;//保存还没被处理的事件到任务中  
    HAL_EXIT_CRITICAL_SECTION(intState);//恢复中断  
  }    

#if defined( POWER_SAVING ) && !defined(USE_ICALL)    
 else// Complete pass through all task events with no activity? {
    osal_pwrmgr_powerconserve(); //如果没有任务需要处理则进入低功耗  
 }    
#endif    
    
  /* Yield in case cooperative scheduling is being used. */    
#if defined (configUSE_PREEMPTION)&&(configUSE_PREEMPTION == 0) {    
    osal_task_yield();    
 }    
#endif

Z-Stack 3.0采用了独热码(one-hot code)的方式对事件类型(UINT16 events)进行编码。

假设events的值为0000 0000 0101 0101,其中的右起第1、3、5和7位为1,于是可以理解为事件集合events包含了用户事件A、C、E和G。可以得到events最多可以包含15种用户事件,和一种系统事件。

创建事件:在zcl_samplesw.h文件中定义一个用户事件,代码如下:

#define SAMPLEAPP_TEST_EVT 0x0040

处理事件:在zcl_samplesw.c文件中的应用层事件处理函数中添加相关的处理,代码如下:

  if ( events & SAMPLEAPP_TEST_EVT )
  {
    printf("Hello World!\r\n");

    //把events中的第3位清0,然后把这个值作为函数的返回值,表示events中的这个事件已经被处理了
    return ( events ^ SAMPLEAPP_TEST_EVT );
  }

触发事件:

在OSAL_Timers.h文件中,可以找到触发事件的API,函数声明如下:

uint8 osal_start_timerEx(uint8 task_id,uint16 event_id,uint32 timeout_value);

如果希望在触发事件的3s后处理刚才自定义的事件,可在应用层初始化函数zclSampleSw_Init()的末尾位置添加如下代码:

osal_start_timerEx(
    zclSampleSw_TaskID,//标记本事件属于应用层任务
    SAMPLEAPP_TEST_EVT,//标记本事件的类型
    3000);//表示3000ms后才处理这个事件
//zclSampleSw_TaskID是一个全局变量,用于标记这个事件是属于应用层任务的。

动态内存的申请和释放

/**
 * @fn osal_mem_alloc
 * 
 * @brief 动态申请内存空间
 *
 * @param   size - 申请多少个字节的内存空间
 * 
 * @return  返回该内存空间的指针
 */
void *osal_mem_alloc( uint16 size );

/**
 * @fn osal_mem_free
 * 
 * @brief 动态释放内存空间
 *
 * @param   ptr - 待释放的内存空间指针
 * 
 */
void osal_mem_free( void *ptr );

动态内存操作

/**
 * @fn osal_memcpy
 * 
 * @brief 把内存空间的内容复制到另一个内存空间中
 *
 * @param   void* - 目标内存空间
 * @param   const void GENERIC * - 源内存空间
 * @param   unsigned int - 复制多少个字节
 * 
 * @return
 */
void *osal_memcpy(void*, const void GENERIC *,unsigned int);  

/**
 * @fn osal_memset
 * 
 * @brief 把内存空间的值设置为指定的值
 *
 * @param   dest - 内存空间
 * @param   value - 指定的值
 * @param   len - 把从dest起的len个字节的存储空间的值设置为value
 * 
 * @return
 */
extern void *osal_memset( void *dest, uint8 value, int len );

示例代码如下:

//事件:SAMPLEAPP_TEST_EVT
if ( events & SAMPLEAPP_TEST_EVT )
{
    //字符串:”Hello World!\n”
    char *str = "Hello World!\n";

    //从堆空间中申请32个字节的内存空间
    char *mem = osal_mem_alloc(32);
    
    //如果申请成功
    if (mem != NULL) {
        //清零内存空间
        osal_memset(mem, 0, 32);

        //将字符串拷贝到内存空间中
        osal_memcpy(mem, str, osal_strlen(str));

        //打印内存空间内存
        printf(mem);

        //释放内存空间
        osal_mem_free(mem);
    }

    //重新触发事件,3000毫秒后执行
    osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_TEST_EVT, 3000);

    //消除已经处理的事件,然后返回未处理的事件
    return ( events ^ SAMPLEAPP_TEST_EVT );    
}

内容来自善学坊,如有侵权,立即删除。

ZigBee 3.0 开发指南 (topthink.com)

/**********************************分割线**********************************

后续章节内容:

二、ZIGBEE单播(完成)

三、ZIGBEE串口发送及数据上传(待完成)

物联沃分享整理
物联沃-IOTWORD物联网 » 使用ZIGBEE协议栈将数据上传至阿里云(STM32)(一)

发表评论