7天快速入門Zigbee:如何在協議棧中從零建立自己的任務

7天快速入門Zigbee:如何在協議棧中從零建立自己的任務


目錄

  1. 概述
  2. Zigbee協議棧的運行機制
  3. 建立自己的任務並添加到系統中
  4. 在系統中使用自己的任務

1 概述

  這篇文章主要是要讓大家瞭解清楚如何在Zigbee協議棧當中從零建立起自己的任務。
  什麼叫從零建立起自己的任務呢?從零建立起自己的任務就是將Zigbee協議棧當中的應用層任務部分的代碼全部刪除自己重寫。
  爲什麼要重新建立自己的任務呢?第一,因爲協議棧的應用層任務代碼寫的十分的雜亂,與其在它的任務代碼當中添加自己的代碼,不如重新寫一份,讓應用層的代碼變得更加的清晰、可控。第二,自己從零寫一遍應用層的代碼可以更好的認識整個Zigbee協議棧的結構。第三,少了協議棧官方繁雜的代碼,可以更加清晰的將自己的功能模塊化。

2 Zigbee協議棧的運行機制

  Zigbee協議棧的運行機制類似於非搶佔式實時操作系統,是一種基於事件驅動的輪詢式操作系統。其基本原理與我們平時寫的嵌入式程序是一樣的。
  平時我們寫的嵌入式程序基本的框架都是先寫“初始化程序部分”,然後再在while循環裏面加入我們要添加的任務輪詢,當然程序中還有許多中斷服務,這些就構成了我們的一個嵌入式程序基本框架。
嵌入式程序基本框架:

void main(void)
{
  // 初始化程序
  ……
  while(1)
  {
    // 任務輪詢
    ……
  }
}

  Zigbee協議棧的運行機制和上面我們說到的普通嵌入式程序框架基本是一樣的。首先我們先來找到Zigbee協議棧的“main函數”在哪裏。“main函數”在“ZMain.c”文件當中。

Zigbee協議棧中的main函數內容:

int main( void )
{
  // 初始化程序
  ……
  // 任務輪詢
  osal_start_system();
}

  我們可以看到,在Zigbee協議棧的“main函數”當中,也是和上面的“嵌入式程序基本框架”一樣,在“osal_start_system();”函數前面的全部都是初始化函數,初始化各種驅動。“osal_start_system();”函數裏面是一個for(;;)語句的無限循環。現在我們就先不看那些初始化程序,先來研究下這個無限循環裏面是怎麼實現任務的調度的。
  我們先把循環代碼貼在下面,去掉一些現在對我們研究任務調度無用的宏定義,讓代碼變得更整潔一些。

void osal_run_system( void )
{
  uint8 idx = 0;

  osalTimeUpdate(); // 不用管
  Hal_ProcessPoll();    // 不用管

  // 查詢“任務列表”裏有無任務有事件發生要處理
  do {
    if (tasksEvents[idx])  // Task is highest priority that is ready.
    {
      break;
    }
  } while (++idx < tasksCnt);

  // 處理任務的事件
  if (idx < tasksCnt)
  {
    uint16 events;
    halIntState_t intState;

    HAL_ENTER_CRITICAL_SECTION(intState);
    events = tasksEvents[idx];
    tasksEvents[idx] = 0;  // Clear the Events for this task.
    HAL_EXIT_CRITICAL_SECTION(intState);

    activeTaskID = idx;
    events = (tasksArr[idx])( idx, events );
    activeTaskID = TASK_NO_TASK;

    HAL_ENTER_CRITICAL_SECTION(intState);
    tasksEvents[idx] |= events;  // Add back unprocessed events to the current task.
    HAL_EXIT_CRITICAL_SECTION(intState);
  }
}

  從上面的這一段代碼我們可以看到,整個任務輪詢代碼就在處理兩件事情,“查詢有無任務有事件發生”和“處理任務的事件”。那麼怎麼產生任務事件呢?這時候就有兩個常用的函數:uint8 osal_set_event( uint8 task_id, uint16 event_flag )uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value ),這兩個函數都是用來通知任務有事件發生。當任務有事件發生時,會在相對應的“事件處理函數”中進行處理。

3 建立自己的任務並添加到系統中

3.1 建立自己的代碼文件
  建立自己的代碼文件“Gateway.c”和“Gateway.h”文件到“Z-Stack Mesh 1.0.0\Project\zstack\Sample\GenericApp\
Source”文件夾中,然後將文件添加到工程中(右鍵點擊Workspace框中的“App”文件夾->Add->Add File,找到自己的代碼文件,然後把它們添加進工程)。
3.2 屏蔽掉源碼原來的應用層文件
  屏蔽方法:右鍵點擊Workspace框中的“GenericApp.c”文件->“Option”->打勾彈框左上角的“Exclude from build”,這樣編譯器在編譯工程時便不會將此文件編譯在內;同理屏蔽掉“GenericApp.h”文件。
  操作完成後APP文件夾應如下圖所示:
操作完成後APP文件夾應如上所示
3.3 建立自己的任務初始化函數和任務處理函數
  模仿“GenericApp.c”中的源碼在“Gateway.c”文件中建立自己的任務初始化函數和任務處理函數。

-------------------------------------------- Gateway.c ---------------------------------------
#include "OSAL.h"include "AF.h"include "ZDApp.h"include "ZDObject.h"include "ZDProfile.h"include "GenericApp.h"include "DebugTrace.h"if !defined( WIN32 ) || defined( ZBIT )
  #include "OnBoard.h"endifinclude "Gateway.h"

// 任務初始化函數
void Gateway_Init( uint8 task_id )
{

}
// 任務處理函數
uint16 Gateway_ProcessEvent ( uint8 task_id, uint16 events )
{
  return 0;
}
-------------------------------------------- Gateway.h ---------------------------------------
#ifndef __GATEWAY_H
#define __GATEWAY_H

#ifdef __cplusplus
extern "C"
{
#endif

void Gateway_Init( uint8 task_id );
uint16 Gateway_ProcessEvent ( uint8 task_id, uint16 events );

#ifdef __cplusplus
}
#endifendif

3.4 將自己的任務初始化函數和任務處理函數添加到協議棧系統的運行機制中
  在“OSAL_GenericApp.c”文件中添加自己的任務初始化函數和任務處理函數到系統的運行機制中。
1) 添加新函數的頭文件:


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

2)添加任務初始化函數:
這裏寫圖片描述
3)添加任務處理函數:
這裏寫圖片描述
  按下編譯鍵,0錯誤0警告。到此我們自己的任務就建立完成了。

4 在系統中使用自己的任務

  我們新建的任務已經加入到Zigbee協議棧中了,那麼該如何使用我們的任務呢?
  我們可以利用我們上面提到過的uint8 osal_set_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燈閃爍事件”發生。

------------------------------------------ Gateway.c ------------------------------------------
static void Init_IndicatorLight(void)
{
  // P0_7,LED1,低電平亮,高電平滅
  P0SEL &= ~(1<<7);     // 通用IO口
  P0DIR |= (1<<7);      // IO口方向輸出
  P0_7 = 1;             // LED燈滅
}

void Gateway_Init( uint8 task_id )
{
  g_gateway_taskid = task_id;
  // 初始化LED燈
  Init_IndicatorLight();
  // 通知g_gateway_taskid任務有LED燈閃爍事件發生
  osal_set_event(g_gateway_taskid, EVENT_FLASH_LED);
}

  然後在自己的任務處理函數中處理該事件。在處理函數中定時500毫秒通知g_gateway_taskid任務有LED燈閃爍事件發生,這樣LED燈就會每秒亮滅一次,達到閃爍LED燈的效果。

uint16 Gateway_ProcessEvent( uint8 task_id, uint16 events )
{
  // 收到LED燈閃爍事件
  if ( events & EVENT_FLASH_LED )
  {
    // 置反LED燈
    if(P0_7==1)
    {
      P0_7 = 0;
    }
    else
    {
      P0_7 = 1;
    }
    // 定時500毫秒後通知g_gateway_taskid任務有LED燈閃爍事件發生
    osal_start_timerEx(g_gateway_taskid, EVENT_FLASH_LED, 500);
    return (events ^ EVENT_FLASH_LED);
  }

  return 0;
}

  按下編譯按鈕,0錯誤0警告,然後再將程序燒錄到CC2530設備中,我們就可以看到1秒鐘閃爍一次的LED燈了。
  軟件源碼的下載地址在下面的評論區有給出。
大家的支持就是我分享技術的動力,希望大家需轉載時能附上原作者的博客:https://blog.csdn.net/u012993936,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章