OSAL之回调定时器相关分析

本文基于协议栈1.3.2阐述,请尊重原创

总论:

OSAL提供一种可以带回调函数功能的定时器,它的实现主要在Osal_ctimer.c与Osal_cbtimer.h两个文件中。回调定时器的是在OSAL的软件定时器基础上注册一个回调函数,当定时器时间到的时候,会执行事先注册的回调函数。相比与事件定时器的比较是:回调定时器用一个任务来处理所有回调定时器的相关事件,每个任务可以处理15个回调定时器的事件,事件标志events 的bit0 ~ bit14 是回调定时器的事件标志位,bit15是 SYS_EVENT_MSG 系统消息事件,不能用于回调定时器;当然你可以定义连续两个任务处理回调定时器的相关事件,那么可以处理多大30的回调定时器事件。

如图是一个任务用于处理回调定时器事件的实例。bit0 ~bit14是回调定时器对应的事件id,每一位是一个回调定时器的事件


技术细节解析

1、定义回调定时器的结构体

回调定时器的结构非常简单,只需要两个元素:一个是指向回调函数的函数指针;另一个则是要传递给回调函数的参数。如下:

// Callback Timer structure
typedef struct
{
  pfnCbTimer_t pfnCbTimer; // callback function to be called when timer expires
  uint8 *pData;            // data to be passed in to callback function
} cbTimer_t;

pfnCbTimer_t是定义的函数指针类型,带有一个参数,在定时器定时时间到了的时候会执行这个指针指向的函数:

typedef void (*pfnCbTimer_t)( uint8 *pData );

//几个常量宏的定义
//每个任务可有的最多的回调定时器
 #define NUM_CBTIMERS_PER_TASK          15
//总的回调定时器个数
#define NUM_CBTIMERS   ( OSAL_CBTIMER_NUM_TASKS * NUM_CBTIMERS_PER_TASK )
//其中OSAL_CBTIMER_NUM_TASKS是在预编译选项里面设定的,默认设为OSAL_CBTIMER_NUM_TASKS=1

// Find out event id using timer id
//根据定时器ID寻找事件ID,每个处理回调函数的任务可以处理15个回调事件。每个任务的15个回调定时器对应事件id的 bit0 ~ bit14 位,bit15对应SYS_EVENT_MSG系统消息事件,不能用于回调定时器用途,刚好这里取余把15给去掉了,所以遍历的时候不会遍历到定时器id为15的。
  #define EVENT_ID( timerId )            ( 0x0001 << ( ( timerId ) % NUM_CBTIMERS_PER_TASK ) )

// Find out task id using timer id
//根据定时器ID查找任务ID,注意下面用的是整除,上面宏用的是取余数。
#define TASK_ID( timerId )             ( ( ( timerId ) / NUM_CBTIMERS_PER_TASK ) + baseTaskID )

 baseTaskID是系统连续分配多个任务用于处理回调定时器,那么第一个任务的任务ID(task id)为baseTaskID;举个例子说,系统连续分配两个任务用来处理回调定时器事件,他们的task id分别为8,9,则baseTaskID的值就是8。

// Find out bank task id using task id
//根据任务ID寻找不同任务ID对应回调定时器的起始编号
#define BANK_TASK_ID( taskId )        ( ( baseTaskID - ( taskId ) ) * NUM_CBTIMERS )

这个宏定义也许是BLE Stack 1.3版本的一个bug。前面说过baseTaskID是系统连续分配给处理回调定时器多个任务的第一个任务的task id,而taskId则是对应的回调定时器的taks id,taskId应该会大于basetaskID,所以应该讲上面的宏定义改成:

#define BANK_TASK_ID( taskId ) ( ( (taskId)-baseTaskID ) * NUM_CBTIMERS )
其中的BANK_TASK_ID可以理解成不同任务对应回调定时器的起始编号。举例来说,系统分配task id分别为8、9的任务用于处理回调定时器事件,而每个任务又对应着15个回调定时器,给这些定时器从0~29编号,所以 BANK_TASK_ID(8) 表示taskid为8的任务对应着的回调定时器的起始编号为(8-8)*15=0,BANK_TASK_ID(9) 则表示task id为9的任务对应的回调定时器的起始编号为(9-8)*15=15。

//几个变量初始化

// Callback Timer base task id
回调定时器的基任务ID
uint16 baseTaskID = TASK_NO_TASK;
没有任务时,baseTaskID = 0xFF,在初始化回调定时器时它则会记录起始的task id。

// Callback Timers table.
#if ( NUM_CBTIMERS > 0 )
  cbTimer_t cbTimers[NUM_CBTIMERS];
#endif
//定义一个回调定时器的数组,这个数组的类型是cbTimer_t,每个数组元素对应一个回调定时器都管理着对应的回调函数指针与要传递的参数。

API解析

osal_CbTimerInit

void osal_CbTimerInit( uint8 taskId )
{
  if ( baseTaskID == TASK_NO_TASK )
  {
    // Only initialize the base task id
//初始化回调定时器的基址任务id, baseTaskID
    baseTaskID = taskId;

    // Initialize all timer structures
对所有回调定时器结构清零
    osal_memset( cbTimers, 0, sizeof( cbTimers ) );
  }
}

在OSAL_simpleBLECentral.c文件中调用osal_CbTimerInit()函数,从下面代码可以看到,如果OSAL_CBTIMER_NUM_TASKS =2 那么taskID将加2,也就是说有两个任务处理回调函数的事件,可以处理多大30个回调事件。但是工程里面默认是1,只能定义15个回调定时器,可以响应15个回调函数

#if defined ( OSAL_CBTIMER_NUM_TASKS )
  /* Callback Timer Tasks */
  osal_CbTimerInit( taskID );
  taskID += OSAL_CBTIMER_NUM_TASKS;
#endif

回调函数的事件执行函数osal_CbTimerProcessEvent

uint16 osal_CbTimerProcessEvent( uint8 taskId, uint16 events )
{

//判断有没收到系统消息
  if ( events & SYS_EVENT_MSG )
  {
    // Process OSAL messages

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

  if ( events )
  {
    uint8 i;
    uint16 event;

    // Process event timers
    //处理taskid回调定时器事件
    for ( i = 0; i < NUM_CBTIMERS_PER_TASK; i++ )
    {
        //处理逻辑是一旦发现事件集ID即events的一个回调事件则进入if结构,然后退出
      if ( ( events >> i ) & 0x0001 )
      {
        //获取taskId到期的回调定时器结构体,赋值给结构体指针
        cbTimer_t *pTimer = &cbTimers[BANK_TASK_ID( taskId )+i];

        // Found the first event
        //找到这次处理的回调定时器的事件id,带等下返回的时候从事件集events中消除这个事件id
        event =  0x0001 << i;

        // Timer expired, call the registered callback function
        //执行该回调定时器注册的回调函数
        pTimer->pfnCbTimer( pTimer->pData );

        //执行该回调定时器的回调函数后,清除回调函数以及它的参数
        // Mark entry as free
        pTimer->pfnCbTimer = NULL;

        // Null out data pointer
        pTimer->pData = NULL;

        // We only process one event at a time
        //每次进入执行一个事件
        break;
      }
    }

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

  // If reach here, the events are unknown
  // Discard or make more handlers
//执行到这里说明没有回调定时器事件发送
  return 0;
}

osal_CbTimerStart

osal_CbTimerStart() 启动一个回调定时器,只定时一次,该函数的功能本质上和启动一个osal软件定时器一样,只不过还要传递回调函数的地址和数据地址,但是他和软件定时器的区别是:

1、软件定时器个数是不限制的,而回调定时器是限制的。

2、启动一个软件定时器传递的是任务id和事件id和超时时间,而启动一个回调定时器传递的是回调函数的地址和数据地址,以及超时时间和记录定时器id的变量地址。

参数:

pfnCbTimer-要注册的回调函数指针

pData-要传递的数据参数

timeout-定时器的定时时间

pTimerId-找到回调定时器id,用该指针指向它

1、查找第一个可用的回调定时器

for ( i = 0; i < NUM_CBTIMERS; i++ )
  {
    if ( cbTimers[i].pfnCbTimer == NULL )
//为空说明该定时器可用
    {
      // Start the OSAL event timer first
启动该定时器的定时服务
      if ( osal_start_timerEx( TASK_ID( i ), EVENT_ID( i ), timeout ) == SUCCESS )
      {
        // Set up the callback timer
        //填充回调函数地址和参数
        cbTimers[i].pfnCbTimer = pfnCbTimer;
        cbTimers[i].pData = pData;

        if ( pTimerId != NULL )
        {
          // Caller is intreseted in the timer id
            //将该回调定时器的id赋值给pTimerId指针的内容
          *pTimerId = i;
        }

        return ( SUCCESS );
      }
    }
  }

启动定时器后,计时,然后时间超时,就会在任务事件轮询中获取回调函数的处理入口地址;

在入口地址数组中

#if defined ( OSAL_CBTIMER_NUM_TASKS )
  OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ),
#endif

这段代码的意思是如果OSAL_CBTIMER_NUM_TASKS =2,那么这里就有两个回调处理函数osal_CbTimerProcessEvent,处理函数是一样的,但是任务id不一样;所以处理的回调定时器的事件也不一样。

osal_CbTimerUpdate

Status_t osal_CbTimerUpdate( uint8 timerId, uint16 timeout )

参数:

timerId-回调定时器id

timeout-要更新的定时值

该函数是更新回调定时器id为timerId的定时器是否超时,如果没超时,那么用timeout更新它的超时时间。

 if ( timerId < NUM_CBTIMERS )
//定时器id不能超过总的回调定时器数
  {
    if ( cbTimers[timerId].pfnCbTimer != NULL )
//确保该定时器id已经启动
    {
      // Make sure the corresponding OSAL event timer is still running
//如果相应的定时器还在运行
      if ( osal_get_timeoutEx( TASK_ID( timerId ), EVENT_ID( timerId ) ) != 0 )
      {
        // Timer exists; update it
更行已存在的回调定时器的超时时间
        osal_start_timerEx( TASK_ID( timerId ), EVENT_ID( timerId ), timeout );

        return (  SUCCESS );
      }
    }
  }

osal_CbTimerStop

Status_t osal_CbTimerStop( uint8 timerId )

参数:timerId 是待取消的回调定时器的id

该函数实现的功能是停止一个回调定时器

// Look for the existing timer
  if ( timerId < NUM_CBTIMERS )
  {
    if ( cbTimers[timerId].pfnCbTimer != NULL )
    {
      // Timer exists; stop the OSAL event timer first
    //可以看到也只是停止了相应的定时器,然后回调地址和数据指针清空
      osal_stop_timerEx( TASK_ID( timerId ), EVENT_ID( timerId ) );

      // Mark entry as free
      cbTimers[timerId].pfnCbTimer = NULL;

      // Null out data pointer
      cbTimers[timerId].pData = NULL;

      return ( SUCCESS );
    }
  }
时间: 2024-08-29 10:06:57

OSAL之回调定时器相关分析的相关文章

关于 ajax 回调 定时器的用法

//前台的请求页面 <script type="text/javascript"> <!-- var xmlHttp; function createXMLHttpRequest(){     if(window.ActiveXObject){         xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");     }     else if(window.XMLHttpRequest){    

muduo网络库学习笔记(10):定时器的实现

传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好. 一.为什么选择timerfd 常见的定时函数有如下几种: sleep alarm usleep nanosleep clock_nanosleep getitimer / setitimer timer_create / timer_settime / timer_gettime / timer_delete timer

[Contiki系列论文之4]ContikiMAC RDC协议

目录 目录 摘要 介绍 ContikiMAC 1 ContikiMAC时序 2 包检测和快速睡眠 3锁相传输 实现 评估 1 微观基准 2 网络功率消耗 相关工作 总结 参考文献 摘要 为了降低系统功耗,低功耗无线设备必须尽可能地将无线电收发器关闭,但是为了接收来自邻居节点的通信消息,它必须被经常唤醒.本论文描述了ContikiMAC RDC(Radio-Duty-Cycling)机制,该机制通过使用一系列时序限制,从而达到既关闭收发器又能高效唤醒的作用.在ContikiMAC机制的作用下,参与

1、CC2541蓝牙4.0芯片中级教程——基于OSAL操作系统的运行流程了解+定时器和串口例程了解

本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 中级教程-OSAL操作系统(OSAL系统解基本套路) 中级教程-OSAL操作系统(进一步了解-OLED && 普通按键和5方向按键-中断!!!)这个系统驱动层和应用层不一样~ 中级教程-OSAL操作系统(ADC-光敏电阻) OSAL操作系统-实验16 串口波特率扩展 OSAL操作系统-实验1

MFC 定时器 SetTimer 如何使用回调函数

创建工程名TestCallBack 自定义回调函数   定义为全局函数 在TestCallBackDlg.h文件开头定义 #pragma once void CALLBACK EXPORT TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime); 在TestCallBackDlg.cpp文件末尾实现函数 void CALLBACK EXPORT TimerProc(HWND hwnd,UINT message,UINT iTimerID

Windows核心编程之创建可等待定时器及其APC回调

概述 创建可等待定时器是Windows内部线程同步的方式之一,本文简单讲述如何使用这一内核对象进行线程同步. 使用方法 创建对象: //创建事件内核对象,默认未触发状态 HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL); 设置对象属性: CreateWaitableTimer创建完成后内核对象处于未触发状态,需要使用API BOOL WINAPI SetWaitableTimer( __in HANDLE hTimer, __in con

boost库学习随记六:使用同步定时器、异步定时器、bind、成员函数回调处理、多线程的同步处理示例等

一.使用同步定时器 这个示例程序通过展示如何在一个定时器执行一个阻塞等待. [cpp] view plaincopy //makefile #---------------------------------------------------------- #makefile helloworld测试用例 # # # # #----------------------------------------------------------- ggg=g++ exe=asiotimer #所有的

使用类的成员函数作为API定时器的回调函数

有时候,我们在使用API定时器时,需要使用类的成员函数作为其回调函数,但是,编译器为了保护成员函数,编译是不能通过的.那么我们怎么才能使用类的成员函数作为API定时器的回调函数呢?我们可以嵌入一段汇编代码来绕过编译器的检查.举个例子: 假设我们一个类的成员函数定义为void CALLBACK EXPORT CTestDlg::TimerProc,那么我们在使用API定时器时如果直接这么写是编译通不过的: ::SetTimer(m_hWnd,1,1000,TimerProc); 使用嵌入一段汇编代

OSAL之时间管理,软件定时器链表管理

读源码写作,尊重原创: 本博文根据蓝牙4.0, 协议族版本是1.3.2 OSAL的时钟实现在OSAL_CockBLE.c与OSAL_Clock.h两个文件中.OSAL支持完整的UTC(世界统一时间),以2000年1月1日00:00:00为时间起点,可以精确到年.月.日.时.分.秒的时间值. 背景知识 // number of seconds since 0 hrs, 0 minutes, 0 seconds, on the 1st of January 2000 UTC存储自2000年1月1日开