蓝牙4.0 BLE协议栈中OSAL详解

一、OSAL基础概念

蓝牙4.0 BLE协议栈的功能依靠函数实现,为更有效地管理这些函数库,引入了操作系统抽象层(OSAL)。BLE协议栈、profiles以及所有应用程序都构建在OSAL之上。对于非计算机专业的读者而言,尽管他们对操作系统知识的了解可能有限,但蓝牙4.0 BLE协议内嵌入的OSAL较为简单,通过几个小实验就能快速掌握其工作原理。

(一)OSAL常用术语

  1. 资源相关概念
    • 资源:任何被任务占用的实体,像变量、数组、结构体等,都可称为资源。
    • 共享资源:能被至少两个任务使用的资源即共享资源。为防止其被破坏,任务操作时需确保独占。
  2. 任务与多任务运行
    • 任务:任务类似于线程,是程序的执行过程。在任务执行期间,可认为CPU完全归其所用。设计任务时,应将复杂问题拆解为多个独立任务,赋予它们优先级,并为每个任务配备独立的CPU寄存器和堆栈空间,通常将任务设计成无限循环。
    • 多任务运行:实际上,同一时间点只有一个任务在运行。但CPU通过任务调度策略,让多个任务轮流执行,每个任务执行特定时长(如10ms),由于切换频繁,造成了多任务同时运行的假象。
  3. 内核与互斥机制
    • 内核:在多任务系统里,内核负责管理任务,涵盖为任务分配CPU时间、进行任务调度以及处理任务间通信等工作,其基本服务是任务切换。借助内核的任务切换功能,能简化应用系统的程序设计。
    • 互斥:多任务访问通信资源常用共享数据结构,如单片机系统中的全局变量、指针、缓冲区等。但要保证对共享数据结构的写操作唯一,避免数据不同步。保护共享资源常用方法有关中断、使用测试并置位指令(TAS指令)、禁止任务切换和使用信号量,在蓝牙4.0 BLE协议栈内嵌操作系统中,常采用关中断的方式。
  4. 消息队列:消息队列用于任务间传递消息,通常包含任务同步信息。为降低通信开销,一般传递消息指针。任务或中断服务程序将消息放入队列,其他任务可从中获取属于自己的消息。

(二)OSAL的功能

OSAL主要具备以下功能:任务注册、初始化和启动;实现任务间的同步与互斥;处理中断;进行存储器分配和管理;提供定时器功能 。

二、OSAL运行机理

(一)工作原理核心

在蓝牙4.0 BLE协议栈应用程序开发中,开发者只需专注于应用层程序开发。因一个应用程序可视为一个任务,所以需要OSAL来实现任务的切换、同步与互斥。OSAL是一种支持多任务运行的系统资源分配机制,但它与标准操作系统存在差异,虽实现了部分类似功能,却并非真正意义上的操作系统。
在这里插入图片描述

OSAL通过建立事件表和函数表来调度任务。事件表记录各任务对应的事件,函数表存储各任务事件处理函数的地址。当有事件发生时,OSAL通过tasksEvents指针访问事件表,找到对应事件后,调用tasksArr数组中相应的函数指针,执行事件处理函数。处理完后,继续查询事件表,查看是否有新事件,这是一种基于事件驱动的轮询式操作系统。
在这里插入图片描述

(二)关键变量解析

  1. taskID:用于保存任务的总个数,定义为uint8 taskID;,其中uint8typedef unsigned char uint8
  2. tasksEvents:是一个指针,指向事件表的首地址,定义为uint16 *tasksEvents;uint16typedef unsigned short uint16
  3. tasksArr:这是一个数组,数组中的每一项都是函数指针,类型为pTaskEventHandlerFn ,定义为typedef unsigned short (*pTaskEventHandlerFn) ( unsigned char task_id, unsigned short events )。每个函数指针都指向任务的事件处理函数。

(三)核心函数剖析

osal_start_system()函数是蓝牙4.0 BLE协议栈的关键:

void osal_start_system( void )
{
#if!defined ( ZBIT ) &&!defined ( UNIT_TEST )
  for(;;)
#endif
  {
    uint8 idx = 0;
    osalTimeUpdate();
    Hal_ProcessPoll();
    do {
      if (tasksEvents[idx])
        break;
    } while (++idx < tasksCnt);
    if (idx < tasksCnt)
    {
      uint16 events;
      halIntState_t intState;
      HAL_ENTER_CRITICAL_SECTION(intState);
      events = tasksEvents[idx];
      tasksEvents[idx] = 0;
      HAL_EXIT_CRITICAL_SECTION(intState);
      events = (tasksArr[idx])( idx, events );
      HAL_ENTER_CRITICAL_SECTION(intState);
      tasksEvents[idx] |= events;
      HAL_EXIT_CRITICAL_SECTION(intState);
    }
#if defined( POWER_SAVING )
    else
    {
      osal_pwrmgr_powerconserve();
    }
#endif
  }
}

为便于分析,去除条件编译指令和相关宏后,简化函数如下:

void osal_start_system( void )
{
  for(;;)
  {
    uint8 idx = 0;
    osalTimeUpdate();
    Hal_ProcessPoll();
    do {
      if (tasksEvents[idx])
        break;
    } while (++idx < tasksCnt);
    if (idx < tasksCnt)
    {
      uint16 events;
      events = tasksEvents[idx];
      events -= (tasksArr[idx])( idx, events );
      tasksEvents[idx] = events;
    }
  }
}

该函数不断循环执行:

  1. 定义变量idx,用于在事件表中索引。
  2. osalTimeUpdate()更新系统时间,Hal_ProcessPoll()检查硬件是否有事件发生,如串口数据接收、按键按下等。
  3. 通过do - while循环检查事件表,若tasksEvents[idx]不为0,则跳出循环,表明有事件发生。
  4. 若有事件,读取该事件,调用对应的事件处理函数处理,处理后将未处理的事件重新放回事件表。

蓝牙4.0 BLE协议栈用unsigned short型变量表示事件,其16个二进制位,每一位代表一个事件。系统初始化时,所有任务的事件都被初始化为0,通过检查tasksEvents[idx]是否为0来判断有无事件发生。

SimpleBLEPeripheral_ProcessEvent函数为例:

uint16 SimpleBLEPeripheral_ProcessEvent( uint8 task_id, uint16 events )
{
  if ( events & SYS_EVENT_MSG )
  {
    uint8 *pMsg;
    if ( (pMsg = osal_msg_receive( simpleBLEPeripheral_TaskID )) != NULL )
    {
      SimpleBLEPeripheral_ProcessOSALMsg( (osal_event_hdr_t *)pMsg );
      VOID osal_msg_deallocate( pMsg );
    }
    return (events ^ SYS_EVENT_MSG);
  }
  if ( events & SBP_START_DEVICE_EVT )
  {
    VOID GAPRoleStartDevice( &simpleBLEPeripheral_PeripheralCBs );
    osal_start_timerEx( simpleBLEPeripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );
    return (events ^ SBP_START_DEVICE_EVT);
  }
  if ( events & SBP_PERIODIC_EVT )
  {
    if ( SBP_PERIODIC_EVT_PERIOD )
    {
      osal_start_timerEx( simpleBLEPeripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD );
      performPeriodicTask();
    }
    return (events ^ SBP_PERIODIC_EVT);
  }
#if defined ( PLUS_BROADCASTER )
  if ( events & SBP_ADV_IN_CONNECTION_EVT )
  {
    uint8 turnAdv = TRUE;
    GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof(uint8), &turnAdv );
    return (events ^ SBP_ADV_IN_CONNECTION_EVT);
  }
#endif
  return 0;
}

该函数首先判断事件类型:

  1. 若为SYS_EVENT_MSG,从消息队列接收消息,处理后释放消息内存空间,并通过异或运算返回未处理的事件标志。
  2. 若为SBP_START_DEVICE_EVT,启动设备并设置定时器,同样通过异或运算返回未处理事件标志。
  3. 若为SBP_PERIODIC_EVT,根据条件设置定时器并执行周期性任务,再返回未处理事件标志。
  4. 若有连接状态广播事件(SBP_ADV_IN_CONNECTION_EVT,需PLUS_BROADCASTER宏定义支持),设置设备广播参数后返回未处理事件标志。若都不满足,则返回0。

OSAL运行机理可总结为:持续查看事件表判断是否有事件发生,若有则查找函数表,调用对应事件处理函数;事件表以数组实现,数组每项对应一个任务的事件,每一位表示一个事件;函数表由函数指针数组构成,数组每项指向事件处理函数。

三、OSAL消息队列

(一)消息与事件的区别

事件是驱动任务执行操作的条件,系统产生事件后,OSAL将其传递给相应任务,任务调用事件处理函数进行处理。某些事件发生时会伴随附加信息,如天线接收数据,这些事件和数据会封装成消息放入消息队列。在事件处理函数中,可使用osal_msg_receive从队列获取消息并处理数据。

pMsg = osal_msg_receive( simpleBLEPeripheral_TaskID );

(二)消息队列结构

OSAL维护着一个消息队列,每个消息都包含一个消息头osal_msg_hdr_t和自定义消息。osal_msg_hdr_t结构体定义如下:

typedef struct
{
  void        *next;
  uint16      len;
  uint8       dest_id;
} osal_msg_hdr_t;

任务接收到事件后,从消息队列获取属于自己的消息,调用消息处理函数进行处理。
在这里插入图片描述

四、OSAL添加新任务

在蓝牙4.0 BLE协议栈应用程序开发中,以SimpleBLEPeripheral工程为例添加新任务:打开OSAL中的SimpleBLEPeripheral.c文件,可找到tasksArr[]数组和osalInitTasks()函数。tasksArr[]存放所有任务的事件处理函数地址,osalInitTasks()负责所有任务的初始化并分配ID号。

添加新任务需编写两个函数:新任务的事件处理函数和初始化函数。将新任务的初始化函数添加到osalInitTasks()函数最后,同时将新任务的事件处理函数地址添加到tasksArr[]数组中。

const pTaskEventHandlerFn tasksArr[] = 
{
  LL_ProcessEvent,         // task 0  
  Hal_ProcessEvent,        // task 1
  HCI_ProcessEvent,        // task 2
#if defined ( OSAL_CBTIMER_NUM_TASKS )
  OsalCBTimer_ProcessEvent,  // task 3
#endif
  L2CAP_ProcessEvent,      // task 4
  GATT_ProcessEvent,       // task 5
  SM_ProcessEvent,         // task 6
  GAPRole_ProcessEvent,    // task 7
  GAPBondMgr_ProcessEvent, // task 8
  GATTServApp_ProcessEvent,// task 9
  GATTAttribute_ProcessEvent, // task 10
  SimpleBLEPeripheral_ProcessEvent  // task 11
};
{
  uint8 taskID = 0;
  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
  Hal_Init( taskID++ );
  HCI_Init( taskID++ );
#if defined ( OSAL_CBTIMER_NUM_TASKS )
  osal_cbtimerInit( taskID++ );
  taskCBs = OSAL_CBTIMER_TASK_CBS;
#endif
  L2CAP_Init( taskID++ );
  GATT_Init( taskID++ );
  SM_Init( taskID++ );
  GAPRole_Init( taskID++ );
  GAPBondMgr_Init( taskID++ );
  GATTServApp_Init( taskID++ );
  SimpleBLEPeripheral_Init( taskID );
  // 将SimpleBLEPeripheral_Init()函数添加到数组的末尾,将SimpleBLEPeripheral_ProcessEvent函数添加到osalInitTasks()函数初始化函数中。
}

添加新任务时要注意:tasksArr[]数组中事件处理函数的排列顺序应与osalInitTasks()函数中任务初始化函数的调用顺序一致,确保任务的事件处理函数能接收正确的任务ID;为保存osalInitTasks()函数分配的任务ID,需为每个任务定义全局变量,如SimpleBLEPeripheral.c中的SimpleBLEPeripheral_TaskID,并在SimpleBLEPeripheral_Init函数中赋值。

五、OSAL应用编程接口

蓝牙4.0 BLE协议栈内嵌的操作系统支持多任务运行,任务间同步、互斥等操作需要相应API支持。OSAL提供的API可使协议栈独立于特定操作系统、内核或任务环境进行编写。

OSAL提供了8个方面的API,包括消息管理、任务同步、时间管理、中断管理、任务管理、内存管理、电源管理和非易失性内存管理。这里选取部分典型API介绍:

(一)消息管理API

用于处理任务间消息交换:

  • osal_msg_allocate( uint16 len ):函数原型为uint8 *osal_msg_allocate( uint16 len ),功能是为消息分配缓存空间。
  • osal_msg_deallocate( uint8 *msg_ptr ):原型是void osal_msg_deallocate( uint8 *msg_ptr ),用于释放消息的缓存空间。
  • osal_msg_send( uint8 destination_task, uint8 *msg_ptr ):函数原型uint8 osal_msg_send(uint8 destination_task, uint8 *msg_ptr ),可将一个任务的消息发送到消息队列。
  • osal_msg_receive( uint8 task_id ):原型为uint8 *osal_msg_receive(uint8 task_id ),用于一个任务从消息队列接收属于自己的消息。

(二)任务同步API

osal_set_event( ):函数原型uint8 osal_set_event(uint8 task_id, uint16 event_flag ),用于运行一个任务设置某一事件,实现任务间的同步。

(三)时间管理API

用于开启和关闭定时器:

  • osal_start_timerEx( ):函数原型uint8 osal_start_timerEx( uint8 task_id, uint16 event_id, uint16 timeout_value ),设置定时时间,时间到后相应事件被设置。
  • osal_stop_timerEx( ):原型uint8 osal_stop_timerEx( uint8 task_id, uint16 event_id ),用于停止已经启动的定时器。

(四)中断管理API

主要用于控制中断的开启与关闭,一般较少使用,如需使用可参考相关文档。

(五)任务管理API

对OSAL进行初始化和启动:

  • osal_init_system( ):函数原型uint8 osal_init_system( void ),是第一个被调用的OSAL函数,用于初始化OSAL。
  • osal_start_system( ):原型void osal_start_system( void ),启动所有任务事件,有事件发生时调用相应处理函数,处理后继续检测事件。若开启节能模式,无事件时使处理器进入休眠模式以降低功耗。

(六)内存管理API

用于在堆上分配和释放缓冲区:

  • osal_mem_alloc( uint16 size ):函数原型void *osal_mem_alloc( uint16 size ),在堆上分配指定大小的缓冲区。
  • osal_mem_free( void *ptr ):原型void osal_mem_free( void *ptr ),释放osal_mem_alloc()分配的缓冲区,这两个函数需成对使用,防止内存泄露。

(七)电源管理API

主要用于电池供电的蓝牙4.0 BLE设备节点,这里暂不讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值