CC254x--OSAL

OSAL运行原理

蓝牙协议栈PROFILE、所有的应用程序、驱动等都是围绕着OSAL组织运行的。OSAL(Operating System Abstraction Layer)操作系统抽象层,它不是一个真正的操作系统(它没有 Context Switch 上下文切换功能),但它巧妙地组织各任务,支持任务优先级,任务之间可以通过事件和消息来通信,为任务提供软定时器和动态内存分配。要避免的陷阱是,应用任务的单个函数运行时间不能太长 (如操作大批量数据的 Flash 写),否则它无法及时调度高优先级的 LL(Link Layer)任务而导致蓝牙通信中断。


OSAL 为每一个任务函数分配了一个 16 位的EVENT 事件,每一位代表一个事件,其中最高位代表的事件为 SYS_EVENT_MSG,这个事件被 OSAL 系统保留,其他的 15 位可以由用户定义,OSAL 在主循环里运行每次都会检查每个任务函数的是否有事件发生(事件置位),如果有事件发生,将通过 taskid 来调用发生事件的任务函数,并将发生的事件传递到该函数中去,由任务函数处理对应的事件。

为什么要将返回值设置到tasksEvents中,就是因为其本质是一个单任务循环,如果某个子任务时间执行过长,会影响更高优先级的任务的响应变慢,影响整体性能。因此如果一个任务执行比较长,宜进行分割,在返回值那里设置继续处理事件,然后返回大循环,看看有没有更高优先级的任务需要执行。


事件和任务对应关系

事件和任务的事件处理函数是如何关联起来的呢?
建立一个事件表,保存各个任务对应的事件。【uint16 *tasksEvents;】。tasksEvents为指向一个内存分配的事件数组的指针。比如:tasksEvents[0]为第0个任务的事件变量(short int)。

建立另一个函数表,保存各个任务事件处理函数的地址。【const pTaskEventHandlerFn tasksArr[] =】将会根据tasksArr[x]去执行对应的回调函数。

然后将这两张表建立某种对应关系,【void osalInitTasks( void ),初始化task_id和tasksArr[]的函数指针对应关系】【uint8 osal_set_event( uint8 task_id, uint16 event_flag ),设置事件发生】,蓝牙协议栈也会调用并设置任务,具体的实现已经被封装起来了。

当某一事件发生时则查找函数表找到对应的事件处理函数即可。【events = (tasksArr[idx])( idx, events );,调用idx变好的函数指针回调函数

//定义了一个函数指针
/*
 * Event handler function prototype
 */
typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event );

//这是一个数组,该数组的每一项都是一个函数指针,指向了事件处理函数
// The order in this table must be identical to the task initialization calls below in osalInitTask.
const pTaskEventHandlerFn tasksArr[] =
{
  LL_ProcessEvent,                                                  // task 0
  Hal_ProcessEvent,                                                 // task 1
  HCI_ProcessEvent,                                                 // task 2
#if defined ( OSAL_CBTIMER_NUM_TASKS )
  OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ),           // task 3
#endif
  L2CAP_ProcessEvent,                                               // task 4
  GAP_ProcessEvent,                                                 // task 5
  GATT_ProcessEvent,                                                // task 6
  SM_ProcessEvent,                                                  // task 7
  GAPRole_ProcessEvent,                                             // task 8
  GAPBondMgr_ProcessEvent,                                          // task 9
  GATTServApp_ProcessEvent,                                         // task 10
  SimpleBLEPeripheral_ProcessEvent                                  // task 11
};

//该变量保存了任务的总个数
const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );

//这是一个指针,指向了事件表的首地址,事件表实际上是一个内存分配的数组
uint16 *tasksEvents;



OSAL是一种事件驱动的轮询式操作系统。事件驱动是指发生事件后采取相应的事件处理方法,轮询指的是不断地查询是否有事件发生。


tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

在系统初始化时,将所有任务的事件初始化为0。通过taskEvents[idx]是否为0来判断是否有事件发生【if (tasksEvents[idx])】。如果有事件发生,则查找函数对应的事件处理函数对事件进行处理【events = (tasksArr[idx])( idx, events );】。

事件表使用数组来实现,数组的每一项对应任务的事件,每一位表示一个事件;函数表使用函数指针数据来实现,数组的每一项是一个函数指针,指向了事件处理函数。


OSAL提供的API

总体而言,大致可以分为10个方面:
1.消息管理
2.任务同步
3.时间管理
4.中断管理
5.任务管理
6.内存管理
7.电源管理
8.非易失性闪存管理
9.时钟管理

10.其他常用


事件

OSAL为每个任务函数分配了一个16位的事件变量,每一位代表一个事件。最高位0x8000保留为系统事件SYS_ENENT_MSG。其余的15位留给用户自定义需要的事件。通常事件由定时器启动,比如2s后我要点亮LED1,这就需要发送一个点亮LED1的事件,然后等待,当2s后接收到点亮LED1事件的时候调用HAL层开关LED1的函数开启LED1。


消息

MAG是比EVENT事件更具体并且可以携带数据的一种通信方式。而且MSG的标记是按数值,而不是位。比如0x02和0x03是两个不同的消息,但是对于事件0x03则是0x01和0x02事件的组合。MSG收发使用osal_mag_send()和osal_msg_receive();当调用osal_msg_send()发送一个MSG的同时会在EVENT列表中触发一个message ready event。为了降低消息传递的开支,通常传递指向消息的指针。


消息与事件的区别

讲解消息队列之前需要讲解一下消息与事件的区别。

事件是驱动任务去执行某些操作的条件,当系统中产生了一个事件,OSAL 将这个事件传递给相应的任务后,任务才能执行一个相应的操作(调用事件处理函数去处理)。

通常某些事件发生时,又伴随着一些附加信息的产生,例如:主机 GATT 接收到数据后,会产生 GATT_MSG_EVENT 消息,但是任务的事件处理函数在处理这个事件的时候,还需要得到所收到的数据。
因此,这就需要将事件和数据封装成一个消息,将消息发送到消息队列osal_msg_send,然后在事件处理函数中就可以使用 osal_msg_receive 从消息队列中得到该消息。这里需要说明一点,消息一般用于不同任务函数之间的数据传递,因为不同的任务具有各自的堆栈空间。在同一任务中使用全局函数或者用户事件完全能够胜任。当然消息也可以在同一个任务中传递数据。
EVENT用于同一任务函数传递命令,而MSG则用于不同的任务函数传递命令数据。

这里需要说明一点,消息一般用于不同任务函数之间的数据传递,因为不同的任务具有各自的堆栈空间。在同一任务中使用全局函数或者用户事件完全能够胜任。当然消息也可以在同一个任务中传递数据。

事件处理


消息处理

OSAL 维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后调用消息处理函数进行相应的处理即可。

Osal_msg_allocate()为消息分配缓存空间,分配之后,可以填充消息,然后通过osal_msg_send( ) 将消息发送出去,然后任务函数中通过 osal_msg_reveive()函数接收属于 自己的消息,并处理,最后调用osal_msg_deallocate() 函数销毁由Osal_msg_allocate()分配的内存空间。

下面是按键的消息处理过程,按键的消息最终将会发送到第一次注册的任务中去。

这里需要说明一点,消息一般用于不同任务函数之间的数据传递,因为不同的任务具有各自的堆栈空间。在同一任务中使用全局函数或者用户事件完全能够胜任。当然消息也可以在同一个任务中传递数据。



事实上,它只是发了一个KEY_CHANGE事件,而键值是以MSG消息的形式发到系统的消息队列,而该消息也会带上目标taskId的标识。

如下代码可以从消息队列中得到一个消息:pMsg = osal_msg_receive( registeredKeysTaskID))


时间管理

时间管理API用于开启和关闭定时器,定时时间一般为毫秒级定时。

osal_start_reload_timer()和osal_start_timerEx()功能一样,但是本接口还多了一个功能:就是定时时间到后相应事件被执行,并重新加载定时器,也就是又重新设置了定时器,继续进行定时工作,除非调“osal_stop_timerEx()”接口,否则一直循环定时操作。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值