概述

  • 什么叫从零建立起自己的任务呢?

从零建立起自己的任务就是将Zigbee协议栈当中的应用层任务部分的代码全部删除自己重写。

  • 为什么要重新建立自己的任务呢?
    • 因为协议栈的应用层任务代码写的十分的杂乱,与其在它的任务代码当中添加自己的代码,不如重新写一份,让应用层的代码变得更加的清晰、可控。
    • 自己从零写一遍应用层的代码可以更好的认识整个Zigbee协议栈的结构。
    • 少了协议栈官方繁杂的代码,可以更加清晰的将自己的功能模块化。

Zigbee协议栈的运行机制

Zigbee协议栈的运行机制类似于非抢占式实时操作系统,是一种基于事件驱动的轮询式操作系统。其基本原理与我们平时写的嵌入式程序是一样的。

平时我们写的嵌入式程序基本的框架都是先写“初始化程序部分”,然后再在while循环里面加入我们要添加的任务轮询,当然程序中还有许多中断服务,这些就构成了我们的一个嵌入式程序基本框架。
嵌入式程序基本框架:

  1. void main(void)
  2. {
  3. // 初始化程序
  4. ……
  5. while(1)
  6. {
  7. // 任务轮询
  8. ……
  9. }
  10. }
  11. void xxx_ISR(void)
  12. {
  13. // 中断处理
  14. ……
  15. }

Zigbee协议栈的运行机制和上面我们说到的普通嵌入式程序框架基本是一样的。首先我们先来找到Zigbee协议栈的“main函数”在哪里。“main函数”在“ZMain.c”文件当中。

Zigbee协议栈中的main函数内容:

  1. int main( void )
  2. {
  3. // 初始化程序
  4. ……
  5. // 任务轮询
  6. osal_start_system();
  7. }

我们可以看到,在Zigbee协议栈的“main函数”当中,也是和上面的“嵌入式程序基本框架”一样,在“osal_start_system();”函数前面的全部都是初始化函数,初始化各种驱动。“osal_start_system();”函数里面是一个for(;;)语句的无限循环。现在我们就先不看那些初始化程序,先来研究下这个无限循环里面是怎么实现任务的调度的。

我们先把循环代码贴在下面,去掉一些现在对我们研究任务调度无用的宏定义,让代码变得更整洁一些。

  1. void osal_run_system( void )
  2. {
  3. uint8 idx = 0;
  4. osalTimeUpdate(); // 不用管
  5. Hal_ProcessPoll(); // 不用管
  6. // 查询“任务列表”里有无任务有事件发生要处理
  7. do {
  8. if (tasksEvents[idx]) // Task is highest priority that is ready.
  9. {
  10. break;
  11. }
  12. } while (++idx < tasksCnt);
  13. // 处理任务的事件
  14. if (idx < tasksCnt)
  15. {
  16. uint16 events;
  17. halIntState_t intState;
  18. HAL_ENTER_CRITICAL_SECTION(intState);
  19. events = tasksEvents[idx];
  20. tasksEvents[idx] = 0; // Clear the Events for this task.
  21. HAL_EXIT_CRITICAL_SECTION(intState);
  22. activeTaskID = idx;
  23. events = (tasksArr[idx])( idx, events );
  24. activeTaskID = TASK_NO_TASK;
  25. HAL_ENTER_CRITICAL_SECTION(intState);
  26. tasksEvents[idx] |= events; // Add back unprocessed events to the current task.
  27. HAL_EXIT_CRITICAL_SECTION(intState);
  28. }
  29. }

从上面的这一段代码我们可以看到,整个任务轮询代码就在处理两件事情,“查询有无任务有事件发生”和“处理任务的事件”。那么怎么产生任务事件呢?这时候就有两个常用的函数:

uint8 osal_set_event( uint8 task_id, uint16 event_flag ) uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )


这两个函数都是用来通知任务有事件发生。当任务有事件发生时,会在相对应的“事件处理函数”中进行处理。

建立自己的任务并添加到系统中

  • 建立自己的代码文件

建立自己的代码文件“Gateway.c”和“Gateway.h”文件到“Z-Stack Mesh 1.0.0\Project\zstack\Sample\GenericApp\Source”文件夹中,然后将文件添加到工程中(右键点击Workspace框中的“App”文件夹->Add->Add File,找到自己的代码文件,然后把它们添加进工程)。

  • 屏蔽掉源码原来的应用层文件

屏蔽方法:右键点击Workspace框中的“GenericApp.c”文件->“Option”->打勾弹框左上角的“Exclude from build”,这样编译器在编译工程时便不会将此文件编译在内;同理屏蔽掉“GenericApp.h”文件。

操作完成后APP文件夹应如下图所示:
如何在协议栈中从零建立自己的任务 - 图1

  • 建立自己的任务初始化函数和任务处理函数

模仿“GenericApp.c”中的源码在“Gateway.c”文件中建立自己的任务初始化函数和任务处理函数。

  1. -------------------------------------------- Gateway.c ---------------------------------------
  2. include "OSAL.h"
  3. include "AF.h"
  4. include "ZDApp.h"
  5. include "ZDObject.h"
  6. include "ZDProfile.h"
  7. >
  8. include "GenericApp.h"
  9. include "DebugTrace.h"
  10. if !defined( WIN32 ) || defined( ZBIT )
  11. #include "OnBoard.h"
  12. endif
  13. include "Gateway.h"
  14. // 任务初始化函数
  15. void Gateway_Init( uint8 task_id )
  16. {
  17. }
  18. // 任务处理函数
  19. uint16 Gateway_ProcessEvent ( uint8 task_id, uint16 events )
  20. {
  21. return 0;
  22. }
  23. -------------------------------------------- Gateway.h ---------------------------------------
  24. ifndef __GATEWAY_H
  25. define __GATEWAY_H
  26. ifdef __cplusplus
  27. extern "C"
  28. {
  29. endif
  30. void Gateway_Init( uint8 task_id );
  31. uint16 Gateway_ProcessEvent ( uint8 task_id, uint16 events );
  32. ifdef __cplusplus
  33. }
  34. endif
  35. endif
  • 将自己的任务初始化函数和任务处理函数添加到协议栈系统的运行机制中

在“OSAL_GenericApp.c”文件中添加自己的任务初始化函数和任务处理函数到系统的运行机制中。

1) 添加新函数的头文件:

  1. //#include "GenericApp.h"
  2. include "Gateway.h"

2)添加任务初始化函数:
如何在协议栈中从零建立自己的任务 - 图2

3)添加任务处理函数:
如何在协议栈中从零建立自己的任务 - 图3
按下编译键,0错误0警告。到此我们自己的任务就建立完成了。

在系统中使用自己的任务

我们新建的任务已经加入到Zigbee协议栈中了,那么该如何使用我们的任务呢?

我们可以利用我们上面提到过的uint8 osalset_event( uint8 task_id, uint16 event_flag )和uint8 osal start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )这两个函数做一个闪烁LED灯的功能来演示如何使用自己的任务。

先在“Gateway.h”中定义“LED灯闪烁事件”号:#define EVENT_FLASH_LED 0x0001。然后在“Gateway.c”中的任务初始化函数中利用osal_set_event()函数通知自己的任务有“LED灯闪烁事件”发生。

  1. ------------------------------------------ Gateway.c ------------------------------------------
  2. static void Init_IndicatorLight(void)
  3. {
  4. // P0_7,LED1,低电平亮,高电平灭
  5. P0SEL &= ~(1<<7); // 通用IO口
  6. P0DIR |= (1<<7); // IO口方向输出
  7. P0_7 = 1; // LED灯灭
  8. }
  9. void Gateway_Init( uint8 task_id )
  10. {
  11. g_gateway_taskid = task_id;
  12. // 初始化LED灯
  13. Init_IndicatorLight();
  14. // 通知g_gateway_taskid任务有LED灯闪烁事件发生
  15. osal_set_event(g_gateway_taskid, EVENT_FLASH_LED);
  16. }

然后在自己的任务处理函数中处理该事件。在处理函数中定时500毫秒通知g_gateway_taskid任务有LED灯闪烁事件发生,这样LED灯就会每秒亮灭一次,达到闪烁LED灯的效果。

  1. uint16 Gateway_ProcessEvent( uint8 task_id, uint16 events )
  2. {
  3. // 收到LED灯闪烁事件
  4. if ( events & EVENT_FLASH_LED )
  5. {
  6. // 置反LED灯
  7. if(P0_7==1)
  8. {
  9. P0_7 = 0;
  10. }
  11. else
  12. {
  13. P0_7 = 1;
  14. }
  15. // 定时500毫秒后通知g_gateway_taskid任务有LED灯闪烁事件发生
  16. osal_start_timerEx(g_gateway_taskid, EVENT_FLASH_LED, 500);
  17. return (events ^ EVENT_FLASH_LED);
  18. }
  19. return 0;
  20. }

按下编译按钮,0错误0警告,然后再将程序烧录到CC2530设备中,我们就可以看到1秒钟闪烁一次的LED灯了。


qrcode_for_gh_e95b474fcf08_344.jpg