OSAL运行机制

1.概述

OSAL为操作系统抽象层,并非是正真意义上的操作系统,Ti的Z-Stack协议栈和蓝牙协议栈都运行在此基础之上,且OSAL有且仅有一个main()函数,其位置在ZMain->ZMain.c下,具体代码如下:

int main( void )
{
  osal_int_disable( INTS_ALL ); // 关中断
  HAL_BOARD_INIT();//初始化板载外设,如LED
  zmain_vdd_check(); // 检查芯片是否上电正常
  InitBoard( OB_COLD );//初始化I/O,LED,定时器等
  HalDriverInit();//初始化硬件驱动
  osal_nv_init( NULL );//初始化Flash存储器
  ZMacInit();//初始化MAC层
  zmain_ext_addr();//确定IEEE64位地址
  zgInit()
#ifndef NONWK
  // Since the AF isn't a task, call it's initialization routine
  afInit();
#endif
  osal_init_system();//初始化操作系统
  osal_int_enable( INTS_ALL );//使能中断
  InitBoard( OB_READY );//设置标志表示系统初始化完毕
  zmain_dev_info();//Display information about this device
#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
  LS164_Cfg();//数码管初始化
  KeysIntCfg();//按键初始化
  InitUart();//串口初始化
  osal_start_system(); // No Return from here,至此协议栈开始运行
  return 0;  // Shouldn't get here.
} 

在上述代码中我们只需要关注3个部分
osal_init_system()
osal_start_system();
LS164_Cfg();//数码管初始化(这里是自己添加的相关初始化函数,建议添加在最后,用以覆盖掉协议栈里面的初始化

2.任务与任务处理函数

系统分配了8个任务,每一个任务对应一个任务处理函数,在代码中主要体现在一个函数和一个数组之中,函数为任务分配函数osalInitTasks(),数组保存任务处理函数的函数名

  • osal_init_system()函数位于OSAL->OSAL.c下,具体如下:
uint8 osal_init_system( void )
{
  osal_mem_init(); // 初始化内存分配
  osal_qHead = NULL; // 初始化消息队列,用于不同任务之间的通信
  osalTimerInit();//初始化定时器
  osal_pwrmgr_init();//初始化电源管理系统
  osalInitTasks();//初始化系统任务
  osal_mem_kick();//Setup efficient search for the first free block of heap.
  return ( SUCCESS );
}

其中需要关注osalInitTasks();函数用于初始化系统任务位于APP->OSAL_GenericApp.c下,具体实现如下:

void osalInitTasks( void )
{
  uint8 taskID = 0;
  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
  macTaskInit( taskID++ );//0
  nwk_init( taskID++ );//1
  Hal_Init( taskID++ );/2
#if defined( MT_TASK )
  MT_TaskInit( taskID++ );//3
#endif
  APS_Init( taskID++ );//4
#if defined ( ZIGBEE_FRAGMENTATION )
  APSF_Init( taskID++ );//5
#endif
  ZDApp_Init( taskID++ );//6
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
  ZDNwkMgr_Init( taskID++ );//7
#endif
  GenericApp_Init( taskID );//8
}

在任务分配上,协议栈为每一层分配了不同的任务,在代码中通过taskID体现,应用层任务为8,在此调用应用层初始化函数GenericApp_Init( taskID );,传入任务ID号8

  • tasksArr[],同样位于APP->OSAL_GenericApp.c下
const pTaskEventHandlerFn tasksArr[] = {
  macEventLoop,
  nwk_event_loop,
  Hal_ProcessEvent,
#if defined( MT_TASK )
  MT_ProcessEvent,
#endif
  APS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )
  APSF_ProcessEvent,
#endif
  ZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
  ZDNwkMgr_event_loop,
#endif
  GenericApp_ProcessEvent
};

在该数组中保存了每一个层的任务处理函数,应用层为GenericApp_ProcessEvent

3.任务触发和任务处理过程

3.1任务,事件和消息

  • 任务:任务为最高层,协议栈给每个层定义了一个任务,如:用户层任务号为8
  • 事件:一个任务有16个事件,每个事件用16位独热码表示(为什么用独热码?),其中SYS_EVENT_MSG是系统的事件,是协议栈中定义好的,也是一个强制事件
  • 消息:消息位于事件之下,每一个事件包含256个消息(0x0000~0xFFFF)

3.2任务处理

  • 开始任务的代码如下:
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
  for(;;)  // Forever Loop,死循环
#endif
  {
    uint8 idx = 0;

    osalTimeUpdate();
    Hal_ProcessPoll();  // This replaces MT_SerialPoll() and osal_check_timer().
    
    do {
      if (tasksEvents[idx])  // 如果任务变量非0
      {
        break;//任务事件不为0,跳出循环,idx为当前任务号
      }
    } while (++idx < tasksCnt);

    if (idx < tasksCnt)
    {
      uint16 events;//定义事件号
      halIntState_t intState;

      HAL_ENTER_CRITICAL_SECTION(intState);
      events = tasksEvents[idx];//将tasksEvevts数组内序号为idx的任务号给evevts
      tasksEvents[idx] = 0;  // 将此任务的任务号清零
      HAL_EXIT_CRITICAL_SECTION(intState);

      events = (tasksArr[idx])( idx, events );//找到任务事件,由此调用任务事件处理函数,通过指针调用(此处为调用任务的关键地方)

      HAL_ENTER_CRITICAL_SECTION(intState);
      tasksEvents[idx] |= events;  // 如果任务未处理,则返回未处理的任务号
      HAL_EXIT_CRITICAL_SECTION(intState);
    }
#if defined( POWER_SAVING )
    else  // Complete pass through all task events with no activity?
    {
      osal_pwrmgr_powerconserve();  // Put the processor/system into sleep
    }
#endif
  }
}
  • 任务处理函数(以应用层任务为例)
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )
{
  afIncomingMSGPacket_t *MSGpkt;
  afDataConfirm_t *afDataConfirm;

  byte sentEP;
  ZStatus_t sentStatus;
  byte sentTransID;       
  (void)task_id;  

  if ( events & SYS_EVENT_MSG )
  {
    MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );//从消息队列中取出引发系统事件的消息
    while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )
      {
        case ZDO_CB_MSG:
          GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
          break;
          
        case KEY_CHANGE:
          GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
          break;

        case AF_DATA_CONFIRM_CMD:
          afDataConfirm = (afDataConfirm_t *)MSGpkt;
          sentEP = afDataConfirm->endpoint;
          sentStatus = afDataConfirm->hdr.status;
          sentTransID = afDataConfirm->transID;
          (void)sentEP;
          (void)sentTransID;

          if ( sentStatus != ZSuccess )
          {
            // The data wasn't delivered -- Do something
          }
          break;

        case AF_INCOMING_MSG_CMD:
          GenericApp_MessageMSGCB( MSGpkt );
          break;

        case ZDO_STATE_CHANGE:
          GenericApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
          if ( (GenericApp_NwkState == DEV_ZB_COORD)
              || (GenericApp_NwkState == DEV_ROUTER)
              || (GenericApp_NwkState == DEV_END_DEVICE) )
          {
          }
          if(GenericApp_NwkState == DEV_ZB_COORD)
          {
            keyChange_t *msgPtr;
            msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );
            if ( msgPtr )
            {
              msgPtr->hdr.event = KEY_CHANGE;//填写相关消息值,这里为按键状态改变
              msgPtr->keys=3;//消息内容为3
                
              osal_msg_send( GenericApp_TaskID, (uint8 *)msgPtr );//把该消息发生给消息队列,在消息队列里面设置任务事件
             }
            LS164_BYTE(12);//如果为协调器,数码管显示C
          }else if(GenericApp_NwkState == DEV_ROUTER)
          {
            LS164_BYTE(13);//如果为路由器,数码管显示R
          }else if(GenericApp_NwkState == DEV_END_DEVICE)
          {
            LS164_BYTE(14);//如果为终端,数码管显示E
          }
          break;

        default:
          break;
      }

      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );

      // Next
      MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
    }

    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }

  // Send a message out - This event is generated by a timer
  //  (setup in GenericApp_Init()).
  if ( events & GENERICAPP_SEND_MSG_EVT )
  {
    // Send "the" message
    GenericApp_SendTheMessage();

    // Setup to send message again
    osal_start_timerEx( GenericApp_TaskID,
                        GENERICAPP_SEND_MSG_EVT,
                      GENERICAPP_SEND_MSG_TIMEOUT );

    // return unprocessed events
    return (events ^ GENERICAPP_SEND_MSG_EVT);
  }

  // Discard unknown events
  return 0;
}

在这段代码中,任务号为events作为参数传入任务处理函数中,任务处理函数一共判断了2个事件,分别为SYS_EVENT_MSG和GENERICAPP_SEND_MSG_EVT其宏定义如下:
#define SYS_EVENT_MSG 0x8000
#define GENERICAPP_SEND_MSG_EVT 0x0001
在此对为什么使用独热码做解释:
独热码的使用方便系统提取事件,其中events & SYS_EVENT_MSG用于提取事件,events ^ SYS_EVENT_MSG用于清除事件.例如系统同时产生了上述2种事件,那么events为0x8001,如果SYS_EVENT_MSG事件未处理则返回0x8000

故如果有一个用户自定义事件,那么可以仿照此例程写出对应事件处理函数

  • 例如,我向自定义一个GENERICAPP_MY_EVT事件,首先应该去GenericApp.h去定义一个宏#define GENERICAPP_MY_EVT 0x0002,之后在事件处理函数中添加如下代码
if(events & GENERICAPP_MY_EVT)
  {
    if(0 == P1_1)//如果按钮3按下
    {
      LS164_BYTE(3);
      GenericApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
      GenericApp_DstAddr.addr.shortAddr = 0x0000;//指定接收方IP
      GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT;//指定接收方端口,默认为10
      
      char theMessageData[] = "8";
      AF_DataRequest( &GenericApp_DstAddr, &GenericApp_epDesc,
                       GENERICAPP_CLUSTERID,
                       1,//(byte)osal_strlen( theMessageData ) + 1
                       (byte *)&theMessageData,
                       &GenericApp_TransID,
                       AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
       P1SEL &= 0xfe;//设置Port1_0端口为普通IO
       P1DIR |= 0x01;//设置Port1_0端口为输出
       P1_0 ^= 1;//灯翻转
    }else if(0 == P2_0)//如果按钮4按下
    {
      LS164_BYTE(4);
    }else if(0 == P0_5)//如果按钮5按下
    {
      LS164_BYTE(5);
    }
    return(events ^ GENERICAPP_MY_EVT);
  }

3.3任务触发

首先得了解两个结构体:

typedef struct
{
  void   *next;//指向下一个消息
  uint16 len;len//为消息的长度
  byte   dest_id;//指向当前的任务ID,分配消息时,初始化为TASK_NO_TASK
} osal_msg_hdr_t;//消息头结构体
typedef struct
{
  uint8  event;
  uint8  status;
} osal_event_hdr_t;//事件结构体

其次几个函数:

  • uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )//发送消息到指定任务(destination_task为0~8号任务),将消息放入消息队列,并把任务的相应事件标志置位。
uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )
{
  //参数有效性检查
  if ( msg_ptr == NULL )
    return ( INVALID_MSG_POINTER );

  if ( destination_task >= tasksCnt )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_TASK );
  }

  // Check the message header
  if ( OSAL_MSG_NEXT( msg_ptr ) != NULL ||
       OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_MSG_POINTER );
  }

  OSAL_MSG_ID( msg_ptr ) = destination_task;//将当前消息的任务指定为入口参数

  // queue message
  osal_msg_enqueue( &osal_qHead, msg_ptr );//将此消息插入到全局消息队列的尾部

  // Signal the task that a message is waiting
  osal_set_event( destination_task, SYS_EVENT_MSG );//给此任务发起一个系统消息事件

  return ( SUCCESS );
}

其中有2个宏定义
#define OSAL_MSG_ID(msg_ptr) ((osal_msg_hdr_t *) (msg_ptr) - 1)->dest_id //获得msg_ptr所指向的任务(消息分配时,初始化为TASK_NO_TASK)
#define OSAL_MSG_NEXT(msg_ptr) ((osal_msg_hdr_t *) (msg_ptr) - 1)->next //获得msg_ptr所指向的下一个消息
注意看此函数最后一句,该函数触发的事件为SYS_EVENT_MSG类型,如果要触发自定义事件,得使用必须要用osal_start_timerEx()函数来触发

  • uint8 osal_set_event( uint8 task_id, uint16 event_flag )
uint8 osal_set_event( uint8 task_id, uint16 event_flag )
{
  if ( task_id < tasksCnt )
  {
    halIntState_t   intState;
    HAL_ENTER_CRITICAL_SECTION(intState);    // Hold off interrupts
    tasksEvents[task_id] |= event_flag;  // 给当前的task赋予事件值,以便于在OSAL大循环中可以处理
    HAL_EXIT_CRITICAL_SECTION(intState);     // Release interrupts
    return ( SUCCESS );
  }
   else
  {
    return ( INVALID_TASK );
  }
}
  • uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )
uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )
{
  halIntState_t intState;
  osalTimerRec_t *newTimer;

  HAL_ENTER_CRITICAL_SECTION( intState );  // Hold off interrupts.

  // Add timer
  newTimer = osalAddTimer( taskID, event_id, timeout_value );

  HAL_EXIT_CRITICAL_SECTION( intState );   // Re-enable interrupts.

  return ( (newTimer != NULL) ? SUCCESS : NO_TIMER_AVAIL );
}
  • 添加消息
keyChange_t *msgPtr;
msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );//为消息结构体分配内存
if ( msgPtr )
{
  msgPtr->hdr.event = KEY_CHANGE;//填写相关消息值,这里为按键状态改变
  msgPtr->keys=3;//消息内容为3
                
  osal_msg_send( GenericApp_TaskID, (uint8 *)msgPtr );//把该消息发生给 消息队列,在消息队列里面设置任务事件
}         
osal_start_timerEx(GenericApp_TaskID,GENERICAPP_MY_EVT,3000);
  • 解析消息
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );//从消息队列中取出引发系统事件的消息
while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )
      {
        case ZDO_CB_MSG:
          GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
          break;
          
        case KEY_CHANGE:
          GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );//消息处理函数,需要自己编写
          break;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容