zigbee系统篇——ZStack系统抽象层详解

了解硬件和更多资料可点击:点击了解

新建一个物联网行业交流学习QQ群,感兴趣可加:928840648

=====CUT=====

*** 本章学习目的 ***

1)理解Z-Stack3.0系统抽象层(OSAL)。

2)掌握OSAL的使用。

4.1  OSAL的运行过程

OSAL也就是系统抽象层,其实并不是真正意义上的操作系统,不过实现了协议栈(ZStack)运行所必需的任务调度功能、内存管理、中断管理等基本功能。为了方便学习,我们将工程进行的裁剪,去除文档和附件,只留下协议栈的组件和工程文件:

本节课我们使用的和上节课一样的工程进行讲解(不同工程只是在应用层不一样,工程的结构是一样的):

打开工程:

* 我们先进入ZMain.c这个文件,并且找到main入口函数:

入口函数main的工作有两个:初始化、系统轮询(进行任务调度)。

  1. int main( void )  
  2. {  
  3.   // Turn off interrupts  
  4.   osal_int_disable( INTS_ALL );       // 关闭所有中断
  5.   
  6.   // Initialization for board related stuff such as LEDs  
  7.   HAL_BOARD_INIT();   // 初始化板载资源,比如PA、时钟源等
  8.   
  9.   // Make sure supply voltage is high enough to run  
  10.   zmain_vdd_check();  // 检测供电电压是否可以支撑芯片正常运行
  11.   
  12.   // Initialize board I/O  
  13.   InitBoard( OB_COLD );  // 初始化板载I/O,比如按键配置为输入
  14.   
  15.   // Initialze HAL drivers  
  16.   HalDriverInit();  // 初始化硬件适配层,比如串口、显示器等
  17.   
  18.   // Initialize NV System  
  19.   osal_nv_init( NULL );  // 初始化NV(芯片内部FLASH的一块空间)
  20.   
  21.   // Initialize the MAC  
  22.   ZMacInit();  // 初始化MAC层(数据链路层)
  23.   
  24.   // Determine the extended address  
  25.   zmain_ext_addr();  // 确定芯片的物理地址
  26.   
  27. #if defined ZCL_KEY_ESTABLISH  
  28.   // Initialize the Certicom certificate information.  
  29.   zmain_cert_init();  // 初始化认证信息
  30. #endif  
  31.   
  32.   // Initialize basic NV items  
  33.   zgInit();  // 初始化存储在NV中的协议栈全局信息,如网络启动方式等
  34.   
  35. #ifndef NONWK  
  36. // Since the AF isn't a task, call it's initialization routine
  37.   afInit();  // 初始化AF(射频)
  38. #endif  
  39.   
  40.   // Initialize the operating system  
  41.   osal_init_system();  // 初始化OSAL(操作系统抽象层)
  42.   
  43.   // Allow interrupts  
  44.   osal_int_enable( INTS_ALL );  // 使能所有中断
  45.   
  46.   // Final board initialization  
  47.   InitBoard( OB_READY );  // 初始化板载IO资源,比如按键
  48.   
  49.   // Display information about this device  
  50.   zmain_dev_info();  // 在显示器上显示设备物理地址
  51.   
  52.   /* Display the device info on the LCD */  
  53. #ifdef LCD_SUPPORTED  
  54.   zmain_lcd_init();  // 在显示器上显示设备信息,比如制造商等
  55. #endif  
  56.   
  57.  
  58.  
  59. #ifdef WDT_IN_PM1  
  60.   /* If WDT is used, this is a good place to enable it. */  
  61.   WatchDogEnable( WDTIMX );  // 启动看门狗功能
  62. #endif  
  63.  
  64.   /* 进入系统轮询 */  
  65.   osal_start_system(); // No Return from here  
  66.   
  67.  
  68.   return 0;  // Shouldn't get here.  
  69. // main()  

重点的两个函数:

// Initialize the operating system  

osal_init_system();  // 初始化OSAL(操作系统抽象层)

 

/* 进入系统轮询 */  

osal_start_system(); // No Return from here  

我们在“2. 操作系统的调度原理”中讲到了系统轮询,轮询的是任务池;这两个函数其实做的事情原理是一样的。osal_init_system()初始化了任务池,osal_start_system()基于调度周期在任务池中进行任务调度!用IAR看代码可在函数名上单击右键—— go to definition of…,便可以进入函数。

* osal_init_system()

  1. uint8 osal_init_system( void )    
  2. {    
  3. #if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS    
  4.   // Initialize the Memory Allocation System    
  5.   osal_mem_init();  // 初始化内存堆栈,用于动态内存申请、释放  
  6. #endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */    
  7.     
  8.   // Initialize the message queue    
  9.   osal_qHead = NULL;  // 初始化消息队列  
  10.     
  11.   // Initialize the timers    
  12.   osalTimerInit();  // 初始化系统时钟,系统轮询周期的基础  
  13.     
  14.   // Initialize the Power Management System    
  15.   osal_pwrmgr_init();  // 初始化电源管理,主要用于低功耗  
  16.     
  17. #ifdef USE_ICALL    
  18.   /* Prepare memory space for service enrollment */    
  19.   osal_prepare_svc_enroll();    
  20. #endif /* USE_ICALL */    
  21.     
  22.   // Initialize the system tasks.    
  23.   osalInitTasks();  // 初始化任务池  
  24.     
  25. #if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS    
  26.   // Setup efficient search for the first free block of heap.    
  27.   osal_mem_kick();    
  28. #endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */    
  29.     
  30. #ifdef USE_ICALL    
  31.  // Initialize variables used to track timing and provide OSAL timer service
  32.   osal_last_timestamp = (uint_least32_t) ICall_getTicks();    
  33.   osal_tickperiod = (uint_least32_t) ICall_getTickPeriod();    
  34.   osal_max_msecs = (uint_least32_t) ICall_getMaxMSecs();   
  35.  
  36.   /* Reduce ceiling considering potential latency */    
  37.   osal_max_msecs -= 2;    
  38. #endif /* USE_ICALL */    
  39.     
  40.   return ( SUCCESS );    
  41. }    

重要的函数: osalInitTasks();  // 初始化任务池

我们知道ZStack框架上是分层的,比如HAL(硬件适配层)、OSAL(系统抽象层)、MAC(数据链路层)、MT(监视器)、APP(应用层)等等;这一点很重要,因为ZStack框架设计上每一层都是任务池中的任务,而且任务是具有优先级的;系统在轮询进行任务调度时,会扫描每一层,如果发现有任务并且任务已经到期就会被处理!

* osalInitTasks()

  1. void osalInitTasks( void )    
  2. {    
  3.   uint8 taskID = 0;    
  4.     
  5.   /* 申请任务池内存空间并进行初始化 */  
  6. tasksEvents=(uint16 *)osal_mem_alloc(sizeof(uint16)*tasksCnt);  
  7. osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));    
  8.     
  9.   /* 下面是初始化各个层的任务,有些名称不认识是因为和 
  10.       ZigBee协议有关,我们暂且忽略,后面篇章会讲解到! 
  11.    */  
  12.   macTaskInit( taskID++ );  // 初始化MAC(数据链路层)任务  
  13.   nwk_init( taskID++ );     // 初始化网络层任务  
  14. #if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)    
  15.   gp_Init( taskID++ );  // 初始化GP层任务  
  16. #endif    
  17.   Hal_Init( taskID++ );  // 初始化HAL(硬件适配层)任务  
  18. #if defined( MT_TASK )    
  19.   MT_TaskInit( taskID++ );  // 初始化MT(监视器)任务  
  20. #endif    
  21.   APS_Init( taskID++ );  // 初始化APS层任务  
  22. #if defined ( ZIGBEE_FRAGMENTATION )    
  23.   APSF_Init( taskID++ );  // 初始化APSF层任务  
  24. #endif    
  25.   ZDApp_Init( taskID++ );  // 初始化ZDApp层任务  
  26. #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )    
  27.   ZDNwkMgr_Init( taskID++ );  // 初始化ZDNwkMgr层任务  
  28. #endif    
  29.   // Added to include TouchLink functionality     
  30.   #if defined ( INTER_PAN )    
  31.     StubAPS_Init( taskID++ );  // 初始化StubAPS层任务  
  32.   #endif    
  33.   // Added to include TouchLink initiator functionality     
  34.   #if defined( BDB_TL_INITIATOR )    
  35.     touchLinkInitiator_Init( taskID++ ); // 初始化touchLink任务  
  36.   #endif    
  37.   // Added to include TouchLink target functionality     
  38.   #if defined ( BDB_TL_TARGET )    
  39.     touchLinkTarget_Init( taskID++ );  // 初始化touchLink任务  
  40.   #endif    
  41.   zcl_Init( taskID++ );  // 初始化ZCL任务  
  42.   bdb_Init( taskID++ );  // 初始化BDB任务  
  43.   
  44.   zclSampleSw_Init( taskID++ );  // 初始化应用层任务  
  45.       
  46. #if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)    
  47.   zclOTA_Init( taskID );  // 初始化OTA层任务  
  48. #endif    
  49. }    

taskID其实就是任务的标识符,系统轮询也是根据taskID来的,taskID越小表示的是该任务的优先级越高(因为系统更早的轮询到该任务);实际上用户的开发是集中在应用层上面的,比如这里的zclSampleSw_Init函数初始化的就是应用层的任务,而且是针对智能插座的应用,大家会发现除了不同应用的应用层初始化函数名称不一样之外,其他都是一样的。接下来我们将进入到系统轮询函数:

* osal_start_system() -> osal_run_system()

  1. void osal_run_system( void )    
  2. {    
  3.   uint8 idx = 0;    
  4.     
  5.   /* 更新时间,并整理出到期的任务,系统的时钟周期是:320US */  
  6.   osalTimeUpdate();  
  7.   Hal_ProcessPoll();  // 硬件适配层中断查询  
  8.     
  9.   do {    
  10.     if (tasksEvents[idx])      // 查看是否有任务需要处理  
  11.     {    
  12.       break;    
  13.     }    
  14.   } while (++idx < tasksCnt);  // 轮询整个任务池  
  15.     
  16.   if (idx < tasksCnt)  // 找到需要处理的任务  
  17.   {    
  18.     uint16 events;    
  19.     halIntState_t intState;   
  20.     HAL_ENTER_CRITICAL_SECTION(intState);  // 关中断  
  21.     events = tasksEvents[idx];      // 任务需要处理的所有事件  
  22.     tasksEvents[idx] = 0;  // Clear the Events for this task.    
  23.     HAL_EXIT_CRITICAL_SECTION(intState);   // 恢复中断  
  24.     
  25.     activeTaskID = idx;    
  26.     events = (tasksArr[idx])( idx, events ); // 处理任务中的事件  
  27.     activeTaskID = TASK_NO_TASK;    
  28.     
  29.     HAL_ENTER_CRITICAL_SECTION(intState);  // 关中断  
  30.     tasksEvents[idx] |= events;  // 保存还没被处理的事件到任务中  
  31.     HAL_EXIT_CRITICAL_SECTION(intState);   // 恢复中断  
  32.   }    
  33. #if defined( POWER_SAVING ) && !defined(USE_ICALL)    
  34.  else// Complete pass through all task events with no activity? {    
  35.     osal_pwrmgr_powerconserve(); // 没有任务需要处理则进入低功耗  
  36.  }    
  37. #endif    
  38.     
  39.   /* Yield in case cooperative scheduling is being used. */    
  40. #if defined (configUSE_PREEMPTION)&&(configUSE_PREEMPTION == 0) {    
  41.     osal_task_yield();    
  42.  }    
  43. #endif    

注:源程序不止上面这么多的,为了简化程序方便理解,我们删除了那些没有使用到的代码!!!“处理任务中的事件”会在下一节课讲解到!

系统轮询是轮询每一层(任务),但是任务是由多个事件组成的,也就是说轮询到需要处理的任务时,是需要处理该任务相应的已经到时间的事件,而有些事件还没到时间所以不会被处理,等待下次时间到了再处理!

这里需要重新提一下:ZStack每一层的任务实际上是可以包含多个事件的,比如我们应用层任务可以同时包含“开灯1”和“关灯2”、“获取温湿度”等事件,只不过不一定在同一时间被执行,比如“开灯1”执行后再过3秒才会执行“关灯2”这个动作!

4.2  ZStack应用层

我们“系统篇”的程序大部分是在应用层进行讲解的,应用层:

我们首先进入到OSAL_SampleSw.c这个文件中,这个文件中只有一个函数:osalInitTasks(),这个函数在上一节课已经讲过,功能是初始化任务池;这个文件和OSAL关系密切,里面有一个数组:

  1. const pTaskEventHandlerFn tasksArr[] = {    
  2.   macEventLoop,    
  3.   nwk_event_loop,    
  4. #if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)    
  5.   gp_event_loop,    
  6. #endif      
  7.   Hal_ProcessEvent,    
  8. #if defined( MT_TASK )    
  9.   MT_ProcessEvent,    
  10. #endif    
  11.   APS_event_loop,    
  12. #if defined ( ZIGBEE_FRAGMENTATION )    
  13.   APSF_ProcessEvent,    
  14. #endif    
  15.   ZDApp_event_loop,    
  16. #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )    
  17.   ZDNwkMgr_event_loop,    
  18. #endif    
  19.   //Added to include TouchLink functionality    
  20.   #if defined ( INTER_PAN )    
  21.     StubAPS_ProcessEvent,    
  22.   #endif    
  23.   // Added to include TouchLink initiator functionality    
  24.   #if defined ( BDB_TL_INITIATOR )    
  25.     touchLinkInitiator_event_loop,    
  26.   #endif    
  27.   // Added to include TouchLink target functionality    
  28.   #if defined ( BDB_TL_TARGET )    
  29.     touchLinkTarget_event_loop,    
  30.   #endif    
  31.   zcl_event_loop,    
  32.   bdb_event_loop,    
  33.   zclSampleSw_event_loop,    
  34. #if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)    
  35.   zclOTA_event_loop    
  36. #endif    
  37. };    

细心的同学可以发现,这里面的程序和初始化任务池函数osalInitTasks()的内容是对应的,那么这个数组有什么用呢?数组中的成员名称都带有“event”这个关键字!其实这个数组中存放的是函数,是每一层任务的事件处理函数,我们前面讲过,任务包含多个事件,事件的处理函数就在这里了!!

比如应用层任务的事件处理函数是:zclSampleSw_event_loop

接下来我们进入应用层最主要的文件中:

这个文件中我们可以找到两个函数(240行和303行):

函数zclSampleSw_Init()是应用层(任务)的初始化函数,我们先前讲过;而另一个函数zclSampleSw_event_loop()是应用层的事件处理函数,也就是说应用层有事件需要处理时,就会来到这个函数。到这里我们就将OSAL和应用层的关系理清了!

事件的编码方式:

我们可以看到应用层的事件处理函数(其它层的处理函数的参数是一样的),uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ),其中函数参数 uint16 events说明事件是16位的变量所表示的,那是不是最多有65536个事件呢?答案是:“NO!”;这和事件的编码方式密切相关!

ZStack的事件编码方式采用“独热码(one-hot code)”,“独热码”直观来说就是有多少个状态就有多少比特位,比如uint16有16个比特位可以代表16种状态。另外,ZStack事件分为“系统事件”和“用户事件”两种,uint16的最高位为1时表示“系统事件”,为0表示“用户事件”,那么剩下的15位就是15种事件了;总结一下就是:ZStack每一层最多可以有15个用户事件!

uint16 events最高位为0才表示用户事件,所以用户事件有如下15种:

例:我们定义用户事件可以这样定义(在zcl_samplesw.h中进行定义):

#define  USER_TEST_EVT       0x0001

下节课我们会讲如何设置、运行一个事件!

4.3  ZStack事件的应用

* 定义用户事件:

我们以应用层为例,在zcl_samplesw.h中定义一个自己的用户事件:

* 处理用户事件(在samplesw.c的函数zclSampleSw_event_loop中):

  1. uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events )  
  2. {    
  3.   afIncomingMSGPacket_t *MSGpkt;    
  4.   (void)task_id;  // Intentionally unreferenced parameter    
  5.         
  6.   /* SYS_EVENT_MSG0x8000表示系统事件,也就是说检测uint16最高位 */  
  7.   if ( events & SYS_EVENT_MSG )    
  8.   {    
  9.     .........   // 系统事件暂且不讲解  
  10.       
  11.     /* 消除已经处理的事件然后返回未处理的事件! */  
  12.     return (events ^ SYS_EVENT_MSG);   
  13.   }      
  14.     
  15. #if ZG_BUILD_ENDDEVICE_TYPE        
  16.   if ( events & SAMPLEAPP_END_DEVICE_REJOIN_EVT )  // 用户事件  
  17.   {    
  18.     bdb_ZedAttemptRecoverNwk();    
  19.     return ( events ^ SAMPLEAPP_END_DEVICE_REJOIN_EVT );    
  20.   }    
  21. #endif    
  22.     
  23.   if ( events & SAMPLEAPP_LCD_AUTO_UPDATE_EVT )  // 用户事件  
  24.   {    
  25.     UI_UpdateLcd();    
  26.     return ( events ^ SAMPLEAPP_LCD_AUTO_UPDATE_EVT );    
  27.   }    
  28.     
  29.   if ( events & SAMPLEAPP_KEY_AUTO_REPEAT_EVT )  // 用户事件  
  30.   {    
  31.     UI_MainStateMachine(UI_KEY_AUTO_PRESSED);    
  32.     return ( events ^ SAMPLEAPP_KEY_AUTO_REPEAT_EVT );    
  33.   }    
  34.       
  35.   // Test Event    
  36.   if ( events & SAMPLEAPP_TEST_EVT )  // 用户事件,自定义!  
  37.   {    
  38.     printf("Hello World!\r\n");    
  39.         
  40.     return ( events ^ SAMPLEAPP_TEST_EVT );    
  41.   }    
  42.       
  43.   // Discard unknown events    
  44.   return 0;    
  45. }    

事件首先进行检测,也就是events & ....,其实是通过检测对应的比特位是否存在,如果存在表示有该事件,进行处理,处理完成后必须消除该事件所对应的比特位events ^ ....,然后返回其他未处理的事件。我们程序很简单,只是通过printf("Hello World!\r\n");打印出信息!

* 设置用户事件:

设置用户事件是有专门的API的,这个API是由OSAL所提供,因此首先我们要理解OSAL有哪些文件,分别有什么功能;OSAL除了做系统轮询进行任务事件调度外,也提供比如内存管理、Flash管理、电源管理、系统定时器服务等等这些基本功能。

展开OSAL内容如下:

设置用户事件的API在OSAL_Timers.h中:

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

这个函数有三个参数:

task_id:任务ID,也就是具体那一层(任务)的标识符。

event_id:事件ID,也就是任务中的哪一个事件。

timeout_value:多少毫秒后才处理这个事件。

如果我们希望3秒后处理我们自定义的事件,可以这样调用API,调用的位置在应用层初始化函数(zclSampleSw_Init())的最后位置:

osal_start_timerEx(zclSampleSw_TaskID,SAMPLEAPP_TEST_EVT, 3000);

其中zclSampleSw_TaskID是一个全局变量,保存应用层的任务ID,保存的过程是在应用层初始化函数一开始:
     

最终设置用户事件的代码如下:

** 调试仿真

点击”Make”对工程进行编译:

然后通过仿真器连接开发板和电脑的USB口,然后将程序烧录到开发板中并进入仿真中,调出“Terminal  I/O”窗口,最后“全速运行程序”:

 

可以看到,3秒后系统轮询发现应用层有任务需要处理,所以调度相应的任务处理函数(zclSampleSw_event_loop),任务处理函数中找到需要处理的事件进行处理(打印出“Hello World!”)。

本节课的内容虽然有点多,但却非常重要,是我们后续课程的基础,希望大家能够掌握,为后面课程打下良好的基础!!!

了解硬件和更多资料可点击:点击了解

新建一个物联网行业交流学习QQ群,感兴趣可加:928840648

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
随着农业科技的不断发展,智能化农业已成为农业生产的重要趋势。基于ZigBee的智能喷灌装置是智能化农业中的一种重要应用,它可以实现远程控制、自动化管理以及数据采集等功能,提高农业生产的效率和质量。 本文主要介绍基于ZigBee的智能喷灌装置的系统设计。首先,我们需要设计硬件部分,包括喷灌装置、传感器、控制器等。其次,我们需要设计软件部分,包括嵌入式系统的程序设计、无线通信协议设计等。 硬件部分的设计: 智能喷灌装置的硬件主要包括喷灌器、传感器、控制器和电源等。其中,喷灌器可以根据传感器检测到的土壤湿度和气象条件自动调整喷灌量,实现精准喷灌。传感器可以检测土壤湿度、温度、气压等参数,控制器可以根据传感器检测到的参数对喷灌器进行控制。电源部分可以选择太阳能电池板,实现全天候运行。 软件部分的设计: 智能喷灌装置的软件部分主要包括嵌入式系统的程序设计和无线通信协议设计。嵌入式系统的程序设计可以采用C语言或者汇编语言编写,实现传感器数据的采集和处理、控制器的控制等功能。无线通信协议设计可以采用ZigBee协议,实现智能喷灌装置与远程控制终端之间的无线通信。 总结: 基于ZigBee的智能喷灌装置可以实现智能化喷灌,提高农业生产的效率和质量。设计时需要考虑硬件和软件两方面的要求,包括喷灌器、传感器、控制器、电源等硬件部分的设计,嵌入式系统的程序设计和无线通信协议设计等软件部分的设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值