[蓝牙 4.0 CC2541 开发] BLE架构与OSAL
本文主要介绍 TI BLE Stack v1.4.1,其只支持 Bluetooth 4.0 (蓝牙5.0 和 蓝牙Mesh 我会努力的(??_?)?) 按照德州仪器公司 CC2541 芯片作为示例,介绍蓝牙4.0以下几个内容
- OSAL:一种简单的嵌入式系统
- BLE Stack:低功耗蓝牙协议栈
- GAP通用访问协议 和 GATT通用属性协议
本次先介绍BLE架构 和 OSAL
BLE 4.0是什么
Bluetooth v4.0 使用两种通讯无线技术,这两种无线技术的速率是不同的 :基本速率/增强数据速率(BR/EDR)、蓝牙低功耗(BLE)。
BLE 就是我们所说的低功耗蓝牙,每次传输比较少的数据,发送后进入休眠,定时唤醒再发送;
而 BR/EDR 功耗比较高,它可以连续传输大包数据,满足更多的场景。
BLE 协议栈架构
一般 BLE 架构如图分为两个部分:控制器和主机。 为什么要分为两个部分?一个控制器一个主机?
这是由于向下兼容的旧版蓝牙结构,也就是蓝牙2.0、3.0这些老型号。
当时,也就是还没出iPhone的时候 😛,那时候的芯片的性能很弱,不能同时处理 2.4GHz 信号的调制解调,一遍处理封包解包。所以用了两个模块进行协同:控制器和主机。HCI、L2CAP就是用于这样的结构间沟通联系的部分。
但现在大部分的蓝牙芯片都已经集成了控制器和主机,不仅仅一个芯片处理调制解调信号、封包解包,还有另一个完整 MCU 工作。用 CC2541 举例,不仅可以发送无线信号,还可以运行应用。
- Physical Layer (PHY)层
指的是一个无线电层。 主要是一些 1Mbps 自适应高斯频移键控物理层面 - Link Layer (LL) :
这一层主要控制 射频天线 的状态 这种状态可以总结为五种:待命 Standby ; 广播 Advertisers;扫描 Scanning;发起 Initiating;连接 Connected。 - Host-Controller Interface (HCI):
主要是兼容旧设备,有蓝牙规范规定的控制器和主机之间通信的接口。旧设备是通过硬件串口链接,现在比如CC系列芯片是通过软件API的。 - L2CAP:
为了主机层,将控制器的通信封装,接受和发送。 - security manager SM :
配对和密钥分发,安全的交换数据;为其他层和其他设备通信的解密功能 - 通用访问配置文件层 GAP:
对接 应用层、配置层,处理怎么发现其他设备、怎么建立低功耗链接、什么时候介入安全层SM; - 属性协议 ATT :
允许什么数据公开给另一台设备;往哪些地方写入数据,那些数据可读 - 通用属性配置文件层 GATT
服务于 属性协议,如何使用 ATT。可以往 ATT 中写数据来通知另一个蓝牙设备,也就是传输什么数据。
OSAL:Operating System Abstraction Layer
其实是一个**操作系统,**就如同 UCOS、RTOS一样,这是一个由德州仪器深度定制的、专门针对51内核的操作系统
BLE 协议栈、配置文件、应用程序 都在 OSAL 的控制回路中构建。提供消息传递、堆栈管理、ji
主要是:
- 任务:各个协议层之间的定义
- 任务事件、事件进程:不同层相互之间的消息传递
- 堆管理;
- OSAL消息。
Task
任务的初始化
void osalInitTasks( void )
{
uint8 taskID = 0;
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
LL_Init( taskID++ );
Hal_Init( taskID++ );
HCI_Init( taskID++ );
L2CAP_Init( taskID++ );
GAP_Init( taskID++ );
GATT_Init( taskID++ );
SM_Init( taskID++ );
GAPRole_Init( taskID++ );
GAPBondMgr_Init( taskID++ );
GATTServApp_Init( taskID++ );
SimpleBLEPeripheral_Init( taskID );
每一个协议层都从 osalInitTasks() 中调用初始化例程。
当然,所有任务以物理射频层 LL 为最高优先级 : )
先分配内存池后,再按顺序为每一层分配 taskID ,8 位 taskID 值越高 优先级越低。如 SimpleBLEPeripheral_Init 即为最高数值 taskID 且最低优先级。
还定义了 tasksEvents 的大小,在 osal_run_system 系统运行中可以用户最多增加15个的子事件。 0x8000 被定义位系统消息
task event 和 event process
- 某任务 (task) 可以理解为某协议层
- 事件 (event) 可以理解为 层和层之间相互传递信息、相互激活函数的处理程序
一般物理层射频天线接到数据后怎么将接到的消息传递给应用层呢?或者 应用层 想要发送一段字符,怎么将指令一层层发送直到 LL层射频呢?
OSAL给出的解决办法是使用 任务事件
在初始化结束后,进入系统主循环,在系统循环中有一个关键的步骤是,轮询检查是否有任务层中有事件发生。如果有,就进入这个事件处理回调函数。↓ 这是框图。 主要讲一下代码
↓ 这是定义任务事件处理的数组,在 osal_run_system 函数中,轮询的时候会调用这个数组检查每一层的事件。
const pTaskEventHandlerFn tasksArr[] =
{
LL_ProcessEvent,
Hal_ProcessEvent,
HCI_ProcessEvent,
#if defined ( OSAL_CBTIMER_NUM_TASKS )
OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ),
#endif
L2CAP_ProcessEvent,
GAP_ProcessEvent,
GATT_ProcessEvent,
SM_ProcessEvent,
GAPRole_ProcessEvent,
GAPBondMgr_ProcessEvent,
GATTServApp_ProcessEvent,
SimpleBLEPeripheral_ProcessEvent
};
↓ 是 osal_run_system 的函数,在注释中详细解释
void osal_run_system( void )
{
uint8 idx = 0;
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);
activeTaskID = idx;
events = (tasksArr[idx])( idx, events );
activeTaskID = TASK_NO_TASK;
HAL_ENTER_CRITICAL_SECTION(intState);
tasksEvents[idx] |= events;
HAL_EXIT_CRITICAL_SECTION(intState);
}
}
各个任务层的事件处理函数 ProcessEvent 在 taskArr[] 指针数组中。
在初始化后 在 OSAL 无限循环中检查是否有任务事件,如果有任务事件则进入对应任务层的 processEvent 。
软件各个层可以在processEvent 中相互独立的为各自协议层设置 OSAL event。
↓ 可以用以下两个函数,在各自层中的子函数中调用,来激活对应的任务层事件。
uint8 osal_set_event( uint8 task_id, uint16 event_flag ) 可以往指定协议层中安排新的事件uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint32 timeout_value ) 类似于osal_set_event() 。第三个参数可以设定毫秒计时器超时后激活 event。
Message
OSAL 设置了 SYS_EVENT_MSG 作为任务层之间的接收消息的标志,会调用对应协议层事件处理程序Msg event process
已经有 task_event 为什么还需要 Message?
首先先确定:Message 是一个 task_event,是 OSAL 设定的专用于发送数据的任务事件。而其他是激活对应子程序的。
Msg 是可以将 任何类型数据、任意大小的数组指针发送给指定层。
↓ 发送消息 典型事例的是 OSAL发送按键信息
uint8 OnBoard_SendKeys( uint8 keys, uint8 state )
{
keyChange_t *msgPtr;
if ( registeredKeysTaskID != NO_TASK_ID )
{
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 );
}
↓接收消息 这是应用层的事件处理函数
uint16 SimpleBLEPeripheral_ProcessEvent( uint8 task_id, uint16 events )
{
VOID task_id;
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);
}
}
Heap Manager
对于系统的内存堆管理,类似于 C标准库 malloc realloc calloc
void *osal_mem_alloc( uint16 size ) 做动态内存管理,在任务初始化中就有用过void osal_mem_free(void *ptr) 用于释放分配的内存
CallBack 回调函数
除 Process Event 以外 还可以定义 回调函数 。如 simpleProfileChangeCB() 和 peripheralStateNotificationCB()
CB 只能在任务中做有限的处理,需要密集处理的话,发送 event 在 应用层 中处理。
结束了
接下来还有两篇介绍 多篇实战(? ?_?)?
就酱
|