本文基于协议栈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 );
}
}