概述
CC2530/Zigbee协议栈之OSAL事件触发详解
- 一、OSAL任务与事件
- 1、任务
- 2、事件
- 二、创建OSAL事件
- 1、定时器创建事件
- 2、消息传递
- 三、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的事件产生和处理过程已全部完成,如果上述流程有啥问题,欢迎探讨,我也是边学边写,难免会有理解上的错误~????
最后
以上就是留胡子鼠标为你收集整理的CC2530/Zigbee协议栈之OSAL事件触发详解——保姆级别的入坑教程一、OSAL任务与事件二、创建OSAL事件三、OSAL事件处理的全部内容,希望文章能够帮你解决CC2530/Zigbee协议栈之OSAL事件触发详解——保姆级别的入坑教程一、OSAL任务与事件二、创建OSAL事件三、OSAL事件处理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复