CC2530/Zigbee协议栈之OSAL事件触发详解——保姆级别的入坑教程

一、OSAL任务与事件

1、任务

在OSAL里有着不同类型的任务,分别对应着zigbee协议栈各层的任务处理函数,它们有着不同的优先级,优先级高(数值小)的任务会先进行调度处理,每一个任务都会有一个任务号task_id。
👇tasksArr是一个函数指针数组,用于存放不同任务的处理函数(数组下标就是任务号)。

typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event );
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
  SerialApp_ProcessEvent//用户自定义任务处理函数
};

其中SerialApp_ProcessEvent为用户自定义任务,优先级最低

如:macEventLoop位于tasksArr[0],其下标(任务号)为0,故其优先级最高
而SerialApp_ProcessEvent位于函数指针数组的最后,故其优先级最低。

因为OSAL是以轮询的方式去处理任务的,当前面的任务都没有事件产生时,才会去处理SerialApp_ProcessEvent任务的事件

2、事件

OSAL 为每个任务函数分配了一个 16 位的事件变量,每一位代表一个事件,最高位 0x8000 保留为系统事件 SYS_EVENT_MSG。其余的 15 位留给用户自定义需要的事件。

系统事件是的优先级在同一任务的事件中是最高的,在任务事件处理函数里面可以直观地看出来

通常事件由定时器启动,比如两秒后我要点亮LED1,这就需要发送一个点亮 LED1 的事件,然后等待,当 2 秒后接收到点亮 LED1 事件的时候调用hal 层开关 LED1 的函数开启 LED1.

👇tasksEvents是一个指向数组的指针,此数组保存了当前任务的状态

uint16 *tasksEvents;

当tasksEvents某一位被置为1时,表示产生一个事件👇为任务号为task_id的任务产生一个事件

tasksEvents[task_id] |= event_flag;

tasksEvents通过task_id与tasksArr函数指针数组建立映射,调用相关的任务处理函数(tasksArr[task_id])( task_id, events )

在这里插入图片描述

默认情况下,OSAL最大任务数为9,最大事件数为16

//任务数量 由tasksArr 长度决定
const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );
//事件数量 16位2进制
uint16 *tasksEvents;

二、创建OSAL事件

创建OSAL事件的方法有2种:①定时器,②消息传递
(划重点)消息只能创建系统事件SYS_EVENT_MSG,往往用于不同任务函数之间的数据传递,在同一任务函数中使用全局函数或者自定义用户事件(定时器创建的事件)完全能够胜任
(划重点)不论是定时器还是消息传递,最终注入事件都是调用osal_set_event()完成。

1、定时器创建事件

①开启一个定时器,调用osal_start_timerEx函数

uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )

在timeout_value 时延后会添加一个任务号为taskID的事件event_id

//创建定时任务
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 );
}

②在osal_start_timerEx里面,会调用osalAddTimer函数。

//调用osalAddTimer
newTimer = osalAddTimer( taskID, event_id, timeout_value );
//函数定义
osalTimerRec_t * osalAddTimer( uint8 task_id, uint16 event_flag, uint16 timeout )

③在osalAddTimer里面,会先遍历定时器链表👇

//定时器链表头结点定义
osalTimerRec_t *timerHead;
//遍历定时器链表
newTimer = osalFindTimer( task_id, event_flag );

若存在该任务ID同事件的定时器,更新定时器超时时间。
若不存在,则创建一个新定时器。
然后将新创建的定时器添加到定时器链表中并返回定时器指针。

至此,定时器创建完成,接下来需要从定时器链表里面获取并创建事件

④在我们的main函数里面最后会调用osal_start_system启动轮询系统。

void osal_start_system( void )

⑤在轮询开始,调用了osalTimeUpdate函数。

void osalTimeUpdate( void )

该函数主要用于校准和更新系统时间,并调用osalTimerUpdate函数创建事件。

⑥在osalTimerUpdate里面,会遍历timerHead定时器链表,如果对应定时器的时延已到,则调用osal_set_event函数添加一个事件。

//timeout==0,把该定时器暂存在freeTimer 里面
if ( srchTimer->timeout == 0 || srchTimer->event_flag == 0 )
{
	   // Take out of list
	   if ( prevTimer == NULL )
	     timerHead = srchTimer->next;
	   else
	     prevTimer->next = srchTimer->next;
	
	   // Setup to free memory
	   freeTimer = srchTimer;
	
	   // Next
	   srchTimer = srchTimer->next;
}
...
...
//如果freeTimer 不为空,调用osal_set_event创建事件
if ( freeTimer )
{
  if ( freeTimer->timeout == 0 )
  {
    osal_set_event( freeTimer->task_id, freeTimer->event_flag );
  }
  osal_mem_free( freeTimer );
}

osal_set_event函数创建事件

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;  // 创建一个新事件
    HAL_EXIT_CRITICAL_SECTION(intState);     // Release interrupts
  }
   else
    return ( INVALID_TASK );

  return ( SUCCESS );
}

至此,定时器事件创建完成

2、消息传递

比如,我们需要创建一个KEY_CHANGE事件

在zigbee协议栈里面有3个默认的系统事件:
1、KEY_CHANGE 按键事件
2、AF_INCOMING_MSG_CMD 接收到AF的信息(别的zigbee节点发来的信息)
3、ZDO_STATE_CHANGE 网络状态发送变化

①调用OnBoard_SendKeys函数

byte OnBoard_SendKeys( byte keys, byte state )

在OnBoard_SendKeys里面会先调用osal_msg_allocate函数

msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );

②osal_msg_allocate函数,主要作用是给按键控制句柄开辟一块内存

uint8 * osal_msg_allocate( uint16 len )
//osal_msg_allocate初始化句柄
hdr = (osal_msg_hdr_t *) osal_mem_alloc( (short)(len + sizeof( osal_msg_hdr_t )) );
if ( hdr )
{
  hdr->next = NULL;
  hdr->len = len;
  hdr->dest_id = TASK_NO_TASK;
}

③随后,OnBoard_SendKeys函数会对句柄进行初始化

if ( msgPtr )
{
  msgPtr->hdr.event = KEY_CHANGE;
  msgPtr->state = state;
  msgPtr->keys = keys;

  osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
}

④调用osal_msg_send函数

uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )

在osal_msg_send函数里面👇

 // 把新创建的消息添加到消息队列
 osal_msg_enqueue( &osal_qHead, msg_ptr );

 // 调用osal_set_event函数创建事件
 osal_set_event( destination_task, SYS_EVENT_MSG );

OSAL维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后再调用消息处理函数进行相应的处理。
注意osal_set_event( destination_task, SYS_EVENT_MSG )的第二个参数是SYS_EVENT_MSG,故消息传递只能创建系统事件。

OSAL 系统消息保留给用于应用程序的 IDs 和 EVENTs,为 0xE0~0xFC
如果用户是在需要自定义用户消息,可以参考下列操作:
①在 xxxApp.h 中宏定义用户 MSG 消息,注意取值范围为 0xE0~0xFC 。

3个系统事件的id编号宏定义
#define AF_INCOMING_MSG_CMD 0x1A
#define ZDO_STATE_CHANGE 0xD1
#define KEY_CHANGE 0xC0

②使用 osal_msg_allocate 函数创建所要发送的消息的结构体空间,例
如 KEY_CHANGE 消息:

msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );

③填充消息结构体成员,例如 KEY_CHANGE

msgPtr->hdr.event = KEY_CHANGE;
msgPtr->state = state;
msgPtr->keys = keys;

④使用 osal_msg_send 函数想目标任务 ID 发送该消息

osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );

注意这里的registeredKeysTaskID(已注册的按键服务ID)我们需要在xxxApp_Init函数里面调用相应的注册服务函数RegisterForKeys给它分配一个任务号
在这里插入图片描述

在这里插入图片描述

⑤最后在目标任务的任务处理函数中的处理刚才填充消息
在这里插入图片描述

三、OSAL事件处理

来到了我们的最后一个部分——OSAL事件处理,在前面,我们详细地了解了OSAL事件产生的过程,也有提到OSAL是通过轮询去处理事件的,所谓的轮询其实就是一个while(1)死循环,在循环里面反复检测是否有事件发生,然后调用相应的任务事件处理函数对其进行处理。
在ZMain.c的main函数里面👇
在这里插入图片描述
通过osal_start_system函数进入操作系统的调度
在osal_start_system函数的最开始,我们可以看到一个for( ; ; ) // Forever Loop 死循环
在这里插入图片描述
在循环体里面,先调用osalTimeUpdate函数更新消息(在前面有讲过)
然后对任务事件表tasksEvents进行遍历,查看是否有未处理事件

    do {
      if (tasksEvents[idx])  // Task is highest priority that is ready.
      {
        break;
      }
    } while (++idx < tasksCnt);

tasksEvents[idx]不为0表示有任务号为idx的事件为处理,故break
任务事件表tasksEvents与任务处理函数指针数组tasksArr通过任务号idx映射起来,通过idx跳转到对应的任务处理函数(注意看下方注释‼)

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.设置为0,直接全部清除
      //退出临界区,恢复原来状态
      HAL_EXIT_CRITICAL_SECTION(intState);
      
      //调用相应任务处理函数
      //如:SerialApp_ProcessEvent
      events = (tasksArr[idx])( idx, events );//此处返回(events ^ 已处理事件),消除已完成事件的标志位

      HAL_ENTER_CRITICAL_SECTION(intState);
      //把没有处理的事件恢复,放回任务事件数组中,等待下一次轮询
      tasksEvents[idx] |= events;  // Add back unprocessed events to the current task.
      HAL_EXIT_CRITICAL_SECTION(intState);
    }

假如产生的是用户自定义的任务事件,则会跳转到SerialApp_ProcessEvent函数👇(注意看中文注释!!)

//负责处理传递给此任务的事件,判断由参数传递的事件类型,然后执行相应的事件处理函数
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
  (void)task_id;  // Intentionally unreferenced parameter
  
  //SYS_EVENT_MSG 0x8000 每个任务都有的强制事件
  if ( events & SYS_EVENT_MSG )
  {
    afIncomingMSGPacket_t *MSGpkt;//消息句柄
	//从消息队列接收未处理消息,一次只接收一个消息!
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SerialApp_TaskID )) )//从消息队列接收未处理消息
    {
      switch ( MSGpkt->hdr.event )
      {
        case KEY_CHANGE:
          //SerialApp_HandleKeys();
          break;
        case AF_INCOMING_MSG_CMD://接收到AF的信息 
          SerialApp_ProcessMSGCmd( MSGpkt );
          break;
          
        case ZDO_STATE_CHANGE://指示网络状态变化
          SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
          if ( (SampleApp_NwkState == DEV_ZB_COORD)//协调器
              || (SampleApp_NwkState == DEV_ROUTER)//路由器
              || (SampleApp_NwkState == DEV_END_DEVICE) )//终端
          {
              // Start sending the periodic message in a regular interval.
              HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
              
              if(SampleApp_NwkState != DEV_ZB_COORD)
                SerialApp_DeviceConnect();              
          }
          else
          {
            // Device is no longer in the network
          }
          break;
  
        default:
          break;         
      }

      osal_msg_deallocate( (uint8 *)MSGpkt );
    }

    return ( events ^ SYS_EVENT_MSG );
  }

  //用户自定义事件 SERIALAPP_SEND_EV  0x0001
  if ( events & SERIALAPP_SEND_EVT )
  {
    SerialApp_Send();
    return ( events ^ SERIALAPP_SEND_EVT );
  }
  //SERIALAPP_RESP_EVT   0x0002
  if ( events & SERIALAPP_RESP_EVT )
  {
    SerialApp_Resp();
    return ( events ^ SERIALAPP_RESP_EVT );
  }

  return ( 0 );  // Discard unknown events.
}

注意返回值全是(events ^ 已处理事件),用异或操作消除已处理事件标志位,对未处理事件标志位不做改变

至此,zigbee协议栈里OSAL的事件产生和处理过程已全部完成,如果上述流程有啥问题,欢迎探讨,我也是边学边写,难免会有理解上的错误~😊

  • 23
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值