1 概述
OSAL 操作系统抽象层 (Operating System Abstraction Layer),一种类多任务运行的系统资源分配机制,并不是真正意义上的操作调度系统,但是上层抽象出的API接口对应用开发者比较友好,而且占用资源较少,适用于资源极其有限的硬件平台
2 框架
OSAL提供调度、内存管理和消息传递功能;HAL提供了对硬件层抽象的访问,将软件层与硬件层进行关联,方便移植
3 流程
事件可以由中断或其他任务中进行触发,被需要处理事件的任务获取执行;事件触发后可附带消息体进行数据传递交互
事件可以由中断或其他任务中触发
消息体进行数据传递交互
4 源码分析
4.1找到main函数
SimpleBLETest_Main.c
// 任何8051单片机c程序, 都是由 main 函数开始的,
// 我们拿到一份代码,首先需要找到main函数
int main(void)
{
/* Initialize hardware */
HAL_BOARD_INIT(); //初始化时钟稳定时钟等等
// Initialize board I/O
//冷启动,关闭了led灯与中断, 一边接下来的各种初始化不受干扰
InitBoard( OB_COLD );
/* Initialze the HAL driver */
HalDriverInit(); //各种驱动的初始化、如按键、lcd、adc、usb、uart等
/* Initialize NV system */
//snv 内部用于保存配对数据或你的用户自定义数据的一段flash,4kB空间
osal_snv_init();
/* Initialize LL */
/* Initialize the operating system */
//oasl 操作系统初始化, 包含内存分配、消息队列、定时器、电源管理和任务等
osal_init_system();
/* Enable interrupts */
HAL_ENABLE_INTERRUPTS();// 开启全局中断
// Final board initialization
InitBoard( OB_READY ); //设置标志标示系统初始化完毕
#if defined ( POWER_SAVING )
// 如果你使能了低功耗, 就启动低功耗模式,
osal_pwrmgr_device( PWRMGR_BATTERY );
#endif
/*
低功耗部分
1.如何总是在PM1
osal_pwrmgr_device( PWRMGR_ALWAYS_ON );
2.如何进入PM2
osal_pwrmgr_device( PWRMGR_BATTERY );在空闲的时候就会进入到PM2模式
3.如何进入PM3
存在连接就断开连接,存在广播就停掉广播,并确认自己创建的所有定时任务都已关闭,
则系统应该就会进入PM3模式,只能进行外部中断唤醒
*/
/* Start OSAL */
osal_start_system(); // No Return from here
/* osal 操作系统启动,实际上是一个大循环,只是检查相对应的标志位,
就指定相对应的任务,看到这里,同学们应该往哪里看呢?其实,这已经是尽头了?那么我们的应用程序是在哪里写的呢
其实是在上面的 上面的函数 osal_init_system 里就初始化了,现在回过头去看看
osal_init_system 这个函数内部就知道了
*/
return 0;
}
4.2 osalInitTasks进行任务初始化
在主函数 main() 中会调用osal_init_system,其中会实用osalInitTasks进行任务初始化,内部为任务各自维护的初始化函数,自定义新增的任务也可以在此函数内进行初始化
uint8 osal_init_system( void )
{
// Initialize the Memory Allocation System
osal_mem_init();//初始化内存分配系统
// Initialize the message queue
osal_qHead = NULL;//初始化消息队列
// Initialize the timers
osalTimerInit();//初始化定时器
// Initialize the Power Management System
osal_pwrmgr_init();//初始化电源管理系统
// Initialize the system tasks.
osalInitTasks();//初始化系统任务, 这一个任务初始花非常关键
// Setup efficient search for the first free block of heap.
osal_mem_kick();
return ( SUCCESS );
}
/*
可以看到在這個數組的定義中,每個成員都是任務的執行函數,按照任務的優先級排序,
並且在osalInitTasks中初始化的時候,我們可以看到每個任務都有一個對應的初始化函數?
瑏K且傳遞了一個taskID,此ID從0開始自增,這裏有一點非常重要,
初始化的順序和任務數組的定義順序是一樣的,
這就保證了我們給任務發生消息或事件時能夠準確的傳遞到相應的任務處理函數。
*/
void osalInitTasks( void )
{
uint8 taskID = 0;
// 分配任务事件空间,这里采用动态的方法来做,比较方便在tasksArr而代码修改少
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
/* LL Task */
LL_Init( taskID++ );
/* Hal Task */
Hal_Init( taskID++ );
/* HCI Task */
HCI_Init( taskID++ );
#if defined ( OSAL_CBTIMER_NUM_TASKS )
/* Callback Timer Tasks */
osal_CbTimerInit( taskID );
taskID += OSAL_CBTIMER_NUM_TASKS;
#endif
/* L2CAP Task */
L2CAP_Init( taskID++ );
/* GAP Task */
GAP_Init( taskID++ );
/* GATT Task */
GATT_Init( taskID++ );
/* SM Task */
SM_Init( taskID++ );
/* Profiles */
GAPRole_Init( taskID++ ); // 角色初始化
GAPBondMgr_Init( taskID++ );
GATTServApp_Init( taskID++ );
/* Application */
SimpleBLETest_Init( taskID ); //这个就是我们的应用程序初始化
}
4.1.2 tasksEvents和taskCnt
tasksCnt这个变量保存了当前的任务个数。
tasksEvent是指向数组的指针,此数组保存了当前任务的状态。
4.1.2 SimpleBLETEST_Init( taskID );追进去看
就是我们的应用程序的初始化了,可以想象一下,这已经实现了多任务,而一个任 务中又实现了最大 16 个事件的框架,在后面的不断学习当 中,除了在我们的应用程序中实 现任务初始化和任务处理函数,还有很多种 处理函数是以回调函数的形式出现的,这个后面的学习章节需要用到的时候在学习,可见,这个框架已经很完整也很用心良苦地设计了,我 们好好琢磨一 下,完全可以将这一开源的 OSAL 移植到其他的平台上来,并且努力一下, 移植也是比较简单的事情了。
void SimpleBLETest_Init( uint8 task_id )
{ //保存任务id到全局变量
SimpleBLETest_TaskID = task_id;
HalLcdWriteString ( "SimpleBLETest 1", HAL_LCD_LINE_1);
// Setup a delayed profile startup
/*
设置一个任务, 这么做的目的是按照多任务处理的方法来做
SimpleBLETest_ProcessEvent 就是处理 SBP_START_DEVICE_EVT
*/
osal_set_event( SimpleBLETest_TaskID, SBP_START_DEVICE_EVT );
}
// 这个是我们的应用程序的事件处理函数
uint16 SimpleBLETest_ProcessEvent( uint8 task_id, uint16 events )
{
VOID task_id; // OSAL required parameter that isn't used in this function
// SYS_EVENT_MSG 这是系统事件比如按键事件蓝牙读写事件处理,都会置这个事件
if ( events & SYS_EVENT_MSG )
{
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
// 这个是我们应用程序自定义的事件,SBP_START_DEVICE_EVT 的值被定义为 0x0001,
// 实际上我们可以定义 16个事件, 第一的时候是以位来定义的
//
if ( events & SBP_START_DEVICE_EVT )
{
while(1)
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON); // 点亮led1
// 返回这个, 告诉osal,这个实践你已经处理了
return ( events ^ SBP_START_DEVICE_EVT );
}
// Discard unknown events
return 0;
}
填充事件位
这个 SYS EVENT MSG 是系统事件的意思,知道这一个,对加深对OSAL 的理解比较好,我们还是来看一下比较好,反正有 source insight 这个好工具,代码各种看各种方便。 通过搜索,我们发现 SYS EVENT MSG 只在2 个地方被设置,并且这两个被调用的地方 均在以下文件
大家可以去看一看,实际上,都是OSAL系统调度的后根据需要调用的,比如按键按下后调用或者发送数据后的返回值回调。
最后任务与事件进行绑定
4.2 启动OSAL
osal_start_system:OSAL启动函数,在for循环中执行osal_run_system()
void osal_start_system( void )
{
for(;;) // Forever Loop
{
osal_run_system(); //osal运行系统
}
}
4.3 任务事件管理
根据tasksEvents来判断是否有事件,序号idx从0开始遍历,所以tasksArr函数指针数组中靠前的任务优先级较高;const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );与任务事件表长度对应
tasksCnt = 12
tasksEvents开辟的缓存中,为每个任务分配了两个字节(与任务偏移序号一一对应),每个bit代表一个事件(每个任务支持最大16个事件),非0则表示存在需要处理的事件
void osal_run_system( void )
{
uint8 idx = 0;
#ifndef HAL_BOARD_CC2538
osalTimeUpdate();
#endif
Hal_ProcessPoll(); //查询数据,比如串口数据,usb数据等
do { // 训环寻找是否有事件,有事件的话,就立马退出,app应用优先级最低
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break;
}
} while (++idx < tasksCnt);
if (idx < tasksCnt)// 找到了事件
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState); // 关闭中断
events = tasksEvents[idx]; //读取该任务的事件(事件可能不止1个)
tasksEvents[idx] = 0; // 清除时间记录,在执行任务处理函数期间有可置上新事件
HAL_EXIT_CRITICAL_SECTION(intState); // 开启中断
activeTaskID = idx;
events = (tasksArr[idx])( idx, events );// tasksArr 是任务处理函数指针
activeTaskID = TASK_NO_TASK;
HAL_ENTER_CRITICAL_SECTION(intState);// 关闭中断
tasksEvents[idx] |= events; // Add back unprocessed events to the current task.
HAL_EXIT_CRITICAL_SECTION(intState);// 开启中断
}
#if defined( POWER_SAVING )
else // Complete pass through all task events with no activity?
{ // 系统睡眠, 以便达到低功耗的目的
osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
}
#endif
/* Yield in case cooperative scheduling is being used. */
#if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0)
{
osal_task_yield();
}
#endif
}
循环寻找是否有事件,有事件的话,就立马退出,app 应用优先级最 低, 这个任 务其实在 OSAL_SimpleBLETest.c 有赋值。tasksEvents 是个指针,在函数 osalInitTasks 中开辟了与任务处理函 数个数一样多 的 uint16 型数据(16 位),起定义为:
第一个形参 task_id 是任务 id, 就是 1102 行中定义的 idx 是一个意思
以下两个函数是提供给我们调用的设置事件与清除事件的函数:
uint8 osal_set_event( uint8 task_id, uint16 event_flag )
uint8 osal_clear_event( uint8 task_id, uint16 event_flag )
![](https://img-blog.csdnimg.cn/direct/b1264eee96a74853a363465640483040.png)
4.3.1 事件处理函数
uint16 SimpleBLEPeripheral_ProcessEvent( uint8 task_id, uint16 events )
{
VOID task_id; // OSAL required parameter that isn't used in this function //参数非必要,去除警告
if ( events & SYS_EVENT_MSG ) //如果是全局的事件
{
uint8 *pMsg; //接收消息指针
if ( (pMsg = osal_msg_receive( simpleBLEPeripheral_TaskID )) != NULL ) //读取缓存数据
{
simpleBLEPeripheral_ProcessOSALMsg( (osal_event_hdr_t *)pMsg ); //处理数据
// Release the OSAL message
VOID osal_msg_deallocate( pMsg ); //释放数据缓存资源
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG); //异或,清除当前执行事件,返回未处理事件
}
if ( events & SBP_START_DEVICE_EVT ) //启动事件
{
// Start the Device
VOID GAPRole_StartDevice( &simpleBLEPeripheral_PeripheralCBs ); //设备启动
// Start Bond Manager
VOID GAPBondMgr_Register( &simpleBLEPeripheral_BondMgrCBs ); //注册绑定回调
// Set timer for first periodic event
osal_start_timerEx( simpleBLEPeripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD ); //启动周期事件
return ( events ^ SBP_START_DEVICE_EVT ); //清除当前执行事件,返回未处理事件
}
if ( events & SBP_PERIODIC_EVT ) //周期事件
{
// Restart timer
if ( SBP_PERIODIC_EVT_PERIOD ) //时间有效
{
osal_start_timerEx( simpleBLEPeripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD ); //每次生效后需要重新启动
}
// Perform periodic application task //定时处理的任务
performPeriodicTask();
return (events ^ SBP_PERIODIC_EVT); //清除当前执行事件,返回未处理事件
}
#if defined ( PLUS_BROADCASTER )
if ( events & SBP_ADV_IN_CONNECTION_EVT )
{
uint8 turnOnAdv = TRUE;
// Turn on advertising while in a connection
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &turnOnAdv );
return (events ^ SBP_ADV_IN_CONNECTION_EVT);
}
#endif // PLUS_BROADCASTER
// Discard unknown events
return 0; //无事件返回0
}
应用任务
在SimpleBle工程中自定义了两个事件
设备启动事件(SBP_START_DEVICE_EVT):用于蓝牙启动想要执行的操作(蓝牙状态回调);
周期性定时事件(SBP_PERIODIC_EVT):定时处理事件
// Simple BLE Peripheral Task Events
#define SBP_START_DEVICE_EVT 0x0001 //设备启动事件
#define SBP_PERIODIC_EVT 0x0002 //周期性定时事件
工程协议栈中定义了全局的事件 SYS_EVENT_MSG,固定为0x8000
4.3.2 消息收发
任务间需要有数据交互时,可以使用消息机制进行数据收发
系统使用了堆空间分配的方法,在TSK1任务一中开辟缓存存储需要发送的数据,TSK2任务二接收到消息处理完成后进行释放,通过指针地址的方式进行传递
截取一段按键消息发送的例子
消息的数据结构可以参考按键消息的数据结构进行修改,必须指定传递给的任务Id
uint8 OnBoard_SendKeys( uint8 keys, uint8 state )
{
keyChange_t *msgPtr;
if ( registeredKeysTaskID != NO_TASK_ID )
{
// Send the address to the task
msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) ); //分配消息缓存,数据结构可自定义
if ( msgPtr )
{
msgPtr->hdr.event = KEY_CHANGE;//消息类型
msgPtr->state = state;
msgPtr->keys = keys;
osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr ); //发送消息
}
return ( SUCCESS );
}
else
return ( FAILURE );
}
消息接收接口:uint8 *osal_msg_receive( uint8 task_id )匹配任务Id成功后返回接收到的消息缓存地址,数据调用完成后必须释放,否则会出现内存泄漏
if ( events & SYS_EVENT_MSG ) // 系统自带的全局事件
{
uint8 *pMsg;
if ( (pMsg = osal_msg_receive( simpleBLEPeripheral_TaskID )) != NULL )//读取缓存数据
{
simpleBLEPeripheral_ProcessOSALMsg( (osal_event_hdr_t *)pMsg );//处理数据
// Release the OSAL message
VOID osal_msg_deallocate( pMsg );//释放数据缓存资源
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);//清除当前执行事件,返回未处理事件
}
事件发送函数:uint8 osal_set_event( uint8 task_id, uint16 event_flag )可以自定义事件并调取该接口触发事件任务
消息数据传递机制:osal系统通过单链表的方式进行对消息缓存的增删管理
typedef struct
{
void *next;
#ifdef OSAL_PORT2TIRTOS
/* Limited OSAL port to TI-RTOS requires compatibility with ROM
* code compiled with USE_ICALL compile flag. */
uint32 reserved;
#endif /* OSAL_PORT2TIRTOS */
uint16 len;
uint8 dest_id;
} osal_msg_hdr_t;
#endif /* USE_ICALL */
4.4 如何向我们的应用程序中添加一个任务
TaskArr这个数组里存放了所有任务的事件处理函数的地址,在这里事件处理函数就代表了任务本身,也就是说事件处理函数标识了与其对应的任务。
要添加新任务,我们需要编写新任务的事件处理函数和初始化函数,然后将事件处理函数的地址加入此数组。然后在osalInitTasks中调用此任务的初始化函数。在此例中,我们此前提到过的SimpleBLEPeripheral_ProcessEvent这个函数被添加到了数组的末尾, SimpleBLEPeripheral_Init这个函数在osalInitTasks中被调用。
另外,为了保存任务初始化函数所接收的任务ID,我们需要给每一个任务定义一个全局变量来保存这个ID。在SimpleBLEPeripheral中SimpleBLEPeripheral.c中定义了一个全局变量SimpleBLEPeripheral_TaskID;并且在SimpleBLEPeripheral_Init函数中进行了赋值
4.5 总结
OSAL适用于资源有限的硬件平台,通过遍历事件缓存列表来调度到指定的任务中, 任务间通过可消息来进行通信,对其进行裁剪后可以方便移植到自己的其他应用平台中