【转】TI蓝牙BLE 协议栈代码学习
BLE就是低功率蓝牙。要着重了解两种设备:
TI的蓝牙4.0BLE协议栈为BLE-CC254x-1.4.0,即现在的版本是1.4版本的。可以从TI官方下载或从附件中下载安装,默认是安装在C盘中。因为上一篇博文提到进行空中固件升级,当时没有安装在C盘下,死活生成不了bin文件,改在C盘下生成了。所以,我个人建议,还是默认安装吧,也占不了多大空间。
TI蓝牙4.0BLE协议栈的结构如下图所示:
由控制器和主机两部分构成,分层的思想很明晰。
主机包括物理层PHY、数据链路层LL和主机控制器接口HCI构成。物理层PHY是1Mbps自适应跳频的GFSK射频,工作于免许可证的2.4G频段。数据链路层LL用于控制设备的射频状态,使设备工作于Standby(准备)、Advertising(广播)、Scanning(扫描)、Initiating(初始化)、Connected(连接)五种状态中的一种。主机控制器接口HCI为主机和控制器之前提供标准的通信接口。
主机包括逻辑链路控制及自适应协议层L2CAP、安全管理层SM、属性协议层ATT、通用访问配置文件层GAP、通用属性配置文件层GATT构成。逻辑链路控制及自适应协议层L2CAP为上层提供数据封装服务,允许逻辑上的点对点通信。安全管理层SM配对和密钥分发服务,实现安全连接和数据交换。属性协议层ATT允许设备向其他设备展示一块特定的数据,这块数据称之为“属性”。通用属性配置文件层GATT定义了使用ATT的服务框架和配置文件(profile),BLE中所有数据的通信都要通过GATT层。通用访问配置文件层GAP提供设备通信的接口,负责处理访问模式和程序,包括设备发现、建立连接、终止连接、初始化安全和设备配置等。对于我们来说,直接接触的是GAP和GATT两个层。
最早接触这个项目的时候,听说CC2540/2541是51内核的SOC,当时我心想,毛毛雨啦,51的东东还不简单。等真接手了才发现,头大了,TI的工程师把协议栈封装和规划得都很好,不能不佩服。
先分析协议栈的流程吧,这里以TI的KeyFobDemo为例,该工程位于C:\Texas Instruments\BLE-CC254x-1.4.0\Projects\ble\KeyFob中。先看下工程的架构。对于我们开发来说,App和Profile两个文件夹中的内容是最主要的。
先从main()函数入手,打开App文件夹下的KeyFob_Main.c,找到main()函数:
int main(void)
{
/* Initialize hardware */
HAL_BOARD_INIT();//初始化硬件 // Initialize board I/O
InitBoard( OB_COLD );//初始化板卡IO /* Initialze the HAL driver */
HalDriverInit();//初始化HAL层驱动 /* Initialize NV system */
osal_snv_init();//初始化Flash /* 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 ;
}
上述代码,我加入了简单的中文注释,会发现有个很重要的东西——OSAL,Operation System Abstraction Layer,操作系统抽象层。OSAL还不是操作系统,但是实现了OS的很多功能。从前面的代码中我们可以看到,跟OSAL相关的有两个函数osal_init_system()和osal_start_system()(osal_pwrmgr_device()暂时先不去理会)。我们依次来看看。
在IAR环境中,可以在代码中osal_init_system()上单击鼠标右键,打开“Go to definition of osal_init_system”,
osal_init_system()在OSAL.c中,下面就是该函数的代码:
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(),我们进到这个函数里面。osalInitTasks()在OSAL_KeyfobDemo.c中。
void osalInitTasks( void )
{
uint8 taskID = ; tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, , (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 */
KeyFobApp_Init( taskID );
}
从每行代码,可以看到整个事件初始化的过程也是分层的。从链路层任务初始化(LL_Init)到硬件抽象层(Hal_Init)、主机控制器接口(HCI_Init)、逻辑链路控制及自适应协议层(L2CAP_Init)、GAP层(GAP_Init)、GATT层(GATT_Init)、安全管理层(SM_Init)。然后完成GAP层的配制(GAPRole_Init)、蓝牙绑定的管理初始化(GAPBondMgr_Init)及GATT层服务的初始化(GATTServApp_Init)。最后完成的是应用层的初始化(KeyFobApp_Init)。程序是一行一行地执行,各层的任务也是依次的完成初始化。
接下来我们再看main()函数中另一个跟OSAL相关的函数——osal_start_system(),也位于OSAL.c中。
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
osal_run_system();
}
}
一看这是个死循环,相当于单片机程序最后一行while(1);。这个函数最主要的部分还是osal_run_system(),找到它,也在OSAL.c中。
void osal_run_system( void )
{
uint8 idx = ; #ifndef HAL_BOARD_CC2538
osalTimeUpdate();
#endif Hal_ProcessPoll(); 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] = ; // 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
}
去掉条件编译部分,最核心的是一个do-while循环,一个if判断。
do-while循环:
do-while循环:
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] = ; // 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);
}
这部分代码应该是OSAL最核心最精髓的部分了。前面的循环中已经确定有事件发生了。HAL_ENTER_CRITICAL_SECTION(intState);和HAL_EXIT_CRITICAL_SECTION(intState);分别是关中断和使能中断,以防止在执行代码时被中断打断。将事件表tasksEvents[]中的事件赋给events,然后该事件清零。接下来events = (tasksArr[idx])( idx, events );就是去处理事件了,这里的tasksArr[]数组非常重要。下面的tasksEvents[idx] |= events;就是把没有响应的事件再放回到tasksEvents[]中。
我们来看看这个非常重要的数组taskArr[],它被定义在OSAL_KeyFobDemo.c中。
// 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
KeyFobApp_ProcessEvent // task 11
};
数组内的成员看起来很面熟。最上面一行的注释也写得很清楚,表中的顺序要和osalInitTask()中定义的一致。再来看这个数组的类型,是pTaskEventHandlerFn,这是个什么东西?pTaskEventHandlerFn不是东西,是
typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event );
这个定义是一个函数指针,看起着很头疼,很蛋疼。如果换一下:
typedef pTaskEventHandlerFn unsigned short (*)( unsigned char task_id, unsigned short event );
这样或许理解起来要好一些。拿KeyFobApp_ProcessEvent的声明来看,uint16 KeyFobApp_ProcessEvent( uint8 task_id, uint16 events ),这是符合pTaskEventHandlerFn的格式的,函数名就是指针,函数的地址。
tasksArr[]是一个函数指针数组,里面保存了所有事件处理函数的地址。当有事件发生时,就执行events = (tasksArr[idx])( idx, events );一句,就是对应的tasksArr[]里相应的那个事件的处理函数。
再看另一个数组,tasksEvents[]。tasksEvents[]声明为全局变量,是在osalInitTasks()中定义和初始化的:
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, , (sizeof( uint16 ) * tasksCnt));
这个数组的大小跟事件的数量是一致的,而且被osal_memset()初始化为0.
这样OSAL的运行机理基本清晰了,就是在do-while()循环中判断tasksEvents[]中哪个事件非0,即事件被触发了;然后在if中把该事件清0,执行tasksArr[]中定义好的事件处理函数,然后把没有执行的事件再放回到tasksEvents[]中。这也是为什么在osalInitTask()中进行初始化的时候,初始化的顺序要和tasksArr[]一致。
以上是我对OSAL的理解,因为C语言的基本不够瓷实,说得也很大白话。之所以敢这么大胆贴出来,也是请大家多批评指正,让我能得到提高。
【转】TI蓝牙BLE 协议栈代码学习的更多相关文章
- 深入浅出低功耗蓝牙(BLE)协议栈
深入浅出低功耗蓝牙(BLE)协议栈 BLE协议栈为什么要分层?怎么理解蓝牙"连接"?如果蓝牙协议只有ATT没有GATT会发生什么? 协议栈框架 一般而言,我们把某个协议的实现代码称 ...
- 深入浅出讲解低功耗蓝牙(BLE)协议栈
详解BLE连接建立过程https://www.cnblogs.com/iini/p/8972635.html 详解BLE 空中包格式—兼BLE Link layer协议解析https://www.cn ...
- 蓝牙BLE: 蓝牙(BLE)协议栈
蓝牙协议是通信协议的一种,一般而言,我们把某个协议的实现代码称为协议栈(protocol stack),BLE协议栈就是实现低功耗蓝牙协议的代码,理解和掌握BLE协议是实现BLE协议栈的前提.当前的蓝 ...
- TI CC2541 BLE协议栈蓝牙MAC 地址
在Flash中有一块只读区域,从地址0x780E开始,蓝牙的MAC以小端方式存放在里面. 在TI的Peripheral例程里面,添加一个特征值,只读属性,6字节长度(蓝牙MAC长度为48-bit,6字 ...
- 蓝牙 BLE 协议学习: 001-BLE协议栈整体架构
背景 在深入BLE协议帧之前,我们先看一下BLE协议栈整体架构. 转载自:<深入浅出低功耗蓝牙(BLE)协议栈> 架构 如上图所述,要实现一个BLE应用,首先需要一个支持BLE射频的芯片, ...
- TI BLE协议栈软件框架分析
看源代码的时候,一般都是从整个代码的入口处开始,TI BLE 协议栈源码也不例外.它的入口main()函数就是整个程序的入口,由系统上电时自动调用. 它主要做了以下几件事情: (一)底层硬件初始化配 ...
- 蓝牙(BLE)应用框架接口设计和应用开发——以TI CC2541为例
本文从功能需求的角度分析一般蓝牙BLE单芯片的应用框架(SDK Framework)的接口设计过程,并以TI CC2541为例说明BLE的应用开发方法. 一.应用框架(Framework) 我们熟知的 ...
- BLE协议栈及传统蓝牙协议栈对比图
1. BLE协议栈的层次图如下: 主机控制接口层: 为主机和控制器之间提供标准通信接口 逻辑链路控制及自适应协议层: 为上层提供数据封装服务 安全管理层: 定义配对和密钥分配方式,为协议栈其他层与另一 ...
- [蓝牙] 2、蓝牙BLE协议及架构浅析&&基于广播超时待机说广播事件
第一章 BLE基本概念了解 一.蓝牙4.0和BLE区别 蓝牙4.0是一种应用非常广泛.基于2.4G射频的低功耗无线通讯技术.蓝牙低功耗(Bluetooth Low Energy ),人们又常称之为 ...
随机推荐
- JavaScript 单例模式实现
Singleton模式指的是调用一个类,任何时候返回的都是同一个实例. 对于Node来说,模块文件可以看成是一个类.怎么保证每次执行这个模块文件,返回的都是同一个实例呢? 很容易想到,可以把实例放到顶 ...
- 查看ip地址信息和配置临时ip
查看ip地址信息:ifconfig –a 配置临时ip: ifconfig eth0 192.168.11.107
- event.getAction()&MotionEvent.ACTION_MASK的原因
看到下面代码中用了AND位运算是为了什么呢? public boolean onTouchEvent(MotionEvent event) { int action = event.getAction ...
- 将vim改造成C/C++开发环境(IDE) 2011
[参考资料]吴垠的“手把手教你把Vim改装成一个IDE编程环境”在Fedora下成功将Vim打造成适用于C/C++的IDE用Vim搭建C/C++开发环境 Ubuntu下vim+ctags的配置 ...
- Python 正则表达试
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦, ...
- alias
1.语法:alias[别名]=[指令名称] [root@rusky /]# alias pingm="ping 127.0.0.1" [root@rusky /]# pingmP ...
- html hack 列表
<!--[if !IE]><!--> 除IE外都可识别 <!--<![endif]--> <!--[if IE]> 所有的IE可识别 <![ ...
- MySql不支持事务解决
用的是一个绿色版的mysql数据库,发现不支持事务,在网络上搜集资料找到解决方案: 1.执行语句 SHOW ENGINES; 如果发现InnoDB全部显示为“YES”,说明该版本的数据库支持事务 2 ...
- Mysql中的 的 Cascade ,NO ACTION ,Restrict ,SET NULL
转载自:http://blog.163.com/inflexible_simple/blog/static/1676946842011616102543931/ 外键约束对子表的含义: 如果在父 ...
- js的一个稍微高级点的用法
通过问题来说明: 1.一个系统中,要创造很多对象,为每个对象分配一个唯一的ID 1: var createObj = (function(){ 2: var i = 0; 3: return func ...