BLE开发笔记——OSAL和协议栈启动流程
在进行协议栈开发之前,必须要了解协议栈中完成任务调度和资源管理的OSAL以及整个协议栈程序大体的启动流程,只有这样才能更好的开发协议栈程序。
OSAL-操作系统抽象层
BLE协议栈、配置文件和所有应用的创建都是围绕操作系统抽象层(OSAL)进行的。OSAL层不是传统意义上实际的操作系统,而是一个控制循环,它允许软件建立事件的执行。对于这一类型控制的软件的每一层,必须创建一个任务ID,也必须定义任务初始化子程序并把它添加到OSAL的初始化中,而且事件的处理程序也必须定义,可以选择定不定义消息处理程序。蓝牙栈的几个层都是OSAL任务,比如最高优先级的LL层任务(因为LL层有很严格的时间要求)。
除了任务管理,OSAL还提供了其它服务,如消息传递、存储管理和定时器,开发套件提供所有的OSAL代码。
OSAL的实现
为了方便硬件改造、升级以及软件的移植,必须实现软件和硬件的低耦合,使软件在改动很小的情况下可以应用到不同的硬件场合,这就是OSAL和HAL存在的意义。HAL是抽象各种硬件资源整合接口供OSAL所用的,BLE低功耗系统架构如下图所示:
OSAL实现任务调度,BLE协议栈、profiles和编写的应用都是围绕它实现的。软件的功能是由编写的任务事件实现的,可以按照以下步骤来创建任务:
- 创建task identifier任务ID;
- 编写任务初始化进程,并将其添加到OSAL初始化中。
- 编写任务处理程序。
- 如果需要的话还需编写消息服务。
系统运行流程
任务循环执行过程如下图所示:
为了使用OSAL,系统启动后,会先进行板级硬件初始化和系统任务的初始化( board init, tasks’s init() ),在任务初始化中会逐个调用BLE协议栈每一层的启动进程来初始化协议栈,完成初始化便进入main函数中名叫osal_start_system的进程,设置一个8位的任务ID,然后进入循环逐个执行任务及任务中的事件。
注意
- 任务优先级是由任务ID决定的,任务ID越小优先级越高。
- BLE协议栈各层的任务优先级比应用程序的高。
- 初始化协议栈以后,越早调入的任务,任务ID越高,优先级越低,即系统倾向于处理新到的任务。
每个事件任务由对应的16bit事件变量来标示,事件状态由taskflag来标示。如果事件处理程序已经完成,但taskflag并没有移除,OSAL会认为事情还没有完成而继续在该程序中不返回。比如,在SimpleBLEPeripheral实例工程中,当事件START_DEVICE_EVT发生,其处理函数SimpleBLEPeripheral_ProcessEvent就运行,结束后返回16bit事件变量,并清除SBP_START_DEVICE_EVT。
每当OSAL事件检测到了有任务事件,其相应的处理进程将被添加到由处理进程指针构成的事件处理表单中,该表单名叫taskArr(taskarray)。taskArr中各个事件进程的顺序和osalInitTasks初始化函数中任务ID的顺序是对应的。
触发事件的方法有三种:
- 最简单的方法是使用osal_set_event函数(函数原型在OSAL.h文件中),在这个函数中,用户可以像定义函数参数一样设置任务ID和事件旗语。
- 第二种方法是使用osal_start_timerEx函数(函数原型在OSAL_Timers.h文件中),使用方法同osal_set_event函数,而第三个以毫秒为单位的参数osal_start_timerEx则指示该事件处理必须要在这个限定时间内,通过定时器来为事件处理计时。
- 还有一种就是定时器实现事件的周期触发,使用osal_start_reload_timer函数。
类似于Linux嵌入式系统内存分配C函数mem_alloc,OSAL利用osal_mem_alloc提供基本的存储管理,但osal_mem_alloc只有一个用于定义byte数的参数。对应的内存释放函数为osal_mem_free。
不同的子系统通过OSAL的消息机制通信。消息即为数据,数据种类和长度都不限定。消息收发过程描述如下:
- 接收信息,调用函数osal_msg_allocate创建消息占用内存空间(已经包含了osal_mem_alloc函数功能),需要为该函数指定空间大小,该函数返回内存空间地址指针,利用该指针就可把所需数据拷贝到该空间。
- 发送数据,调用函数osal_msg_send,需为该函数指定发送目标任务,OSAL通过SYS_EVENT_MSG告知目标任务,目标任务的处理函数调用osal_msg_receive来接收发来的数据。建议每个OSAL任务都有一个消息处理函数,每当任务收到一个消息后,通过消息的种类来确定需要本任务做相应处理。消息接收并处理完成,调用函数osal_msg_deallocate来释放内存(已经包含了osal_mem_free函数功能)。
协议栈启动流程分析
这里以协议栈例程中的简单从机工程SimpleBLEPeripheral为例进行说明协议栈启动过程,平台为CC2541芯片。
如果协议栈是安装在C盘的话一般可以在目录C:\Texas Instruments\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\CC2541DB
中找到SimpleBLEPeripheral.eww,双击即可用IAR打开工程了。
要了解一个C语言工程,我们应该先找到main函数,这是C程序的一般入口,IAR工程文件的APP下可以找到SimpleBLEPeripheral_Main.c,其中可以看到main函数,内容如下:
Int main(void)
{
/* Initialize hardware */
HAL_BOARD_INIT(); // 硬件初始化
// Initialize board I/O
InitBoard( OB_COLD ); // 板级初始化
/* Initialze the HAL driver */
HalDriverInit(); // Hal驱动初始化
/* Initialize NV system */
osal_snv_init(); // Flash存储SNV初始化
/* Initialize LL */
/* Initialize the operating system */
osal_init_system(); // OSAL初始化
/* Enable interrupts */
HAL_ENABLE_INTERRUPTS(); // 使能总中断
// Final board initialization
InitBoard( OB_READY ); // 板级初始化
#if defined ( POWER_SAVING )
osal_pwrmgr_device( PWRMGR_BATTERY ); // 低功耗管理
#endif
/* Start OSAL */
osal_start_system(); // No Return from here 启动OSAL
return 0;
}
可以看到,进入main函数后,首先做了各种初始化,以及低功耗管理,最后启动了OSAL,进入**osal_start_system()**函数,函数代码如下:
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
osal_run_system();
}
}
根据上面代码,启动OSAL后,就会循环执行osal_run_system(),我们还得看一下这个函数的内容:
void osal_run_system( void )
{
uint8 idx = 0;
#ifndef HAL_BOARD_CC2538
osalTimeUpdate(); // 定时器更新
#endif
Hal_ProcessPoll(); // Hal层信息处理
do {
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];
tasksEvents[idx] = 0; // Clear the Events for this task. 清除事件标志
HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区
activeTaskID = idx;
events = (tasksArr[idx])( idx, events );// 执行事件处理函数
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
}
阅读代码可以明白OSAL循环检测每一个任务是否有事件发生,如果有则执行相应的任务并处理触发的事件,如果没有事件并且宏定义中开启了低功耗模式,那么就会进入低功耗模式。但是OSAL怎么检测任务是否有事件的呢?我们可以看到有一句代码:
events = (tasksArr[idx])( idx, events );
taskArr又是什么鬼?其实它是一个函数指针数组:
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
};
tasksArr中的成员是每一个任务事件处理函数,是按照优先级排列的,排列顺序与任务初始化中任务初始化顺序是一致的,这样就保证了触发事件或产生消息能够准确传递到相应的任务处理函数中,任务初始化代码如下:
void osalInitTasks( void )
{
uint8 taskID = 0;
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 */
SimpleBLEPeripheral_Init( taskID );
}
我们做BLE开发,主要是编写Application,而在SimpleBLEPeripheral工程中的应用程序则是SimpleBLEPeripheral,对应两个重要的函数就是任务初始化函数SimpleBLEPeripheral_Init和事件处理函数SimpleBLEPeripheral_ProcessEvent。
在SimpleBLEPeripheral_Init中主要是对GAP和GATT进行初始化配置,在最后会执行osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT )触发启动设备的事件,处理SBP_START_DEVICE_EVT事件的代码如下:
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 );
}
能够看到在执行上述代码的时候,设置定时触发事件SBP_PERIODIC_EVT,时间为SBP_PERIODIC_EVT_PERIOD,在事件处理函数中有对SBP_PERIODIC_EVT的处理,处理中又重新设置此事件的定时触发,也就实现了事件周期触发:
if ( event & 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
performPeriodicTassk();
return ( events ^ SBP_PERIODIC_EVT );
}
另外还有两个重要的回调函数:
- peripheralStateNotificationCB:这个函数主要是在设备的连接状态改变的时候由底层回调,可以在这个函数中查看设备状态以做出需要的处理。
- simpleProfileChangeCB:这个函数主要会在设备蓝牙射频接收到数据的时候底层回调用来及时获取接收到的数据。
做协议栈的开发主要是做自己需要的应用,可以在例程基础上添加自己需要的处理完成应用,也可以新建自己的应用,只要按照OSAL中任务的添加步骤添加即可,了解了以上的内容,就可以尝试开发了。