CC2540低功耗的内幕
一、概述
1、BLE蓝牙协议栈结构
附图6 BLE蓝牙协议栈结构图
分为两部分:控制器和主机。对于4.0以前的蓝牙,这两部分是分开的。所有profile(姑且称为剧本吧,用来定义设备或组件的角色)和应用都建构在GAP或GATT之上。下面由结构图的底层组件开始介绍。
· PHY层,工作车间,1Mbps自适应跳频GFSK(高斯频移键控),运行在免证的2.4GHz
· LL层为RF控制器,控制室,控制设备处于准备(standby)、广播、监听/扫描(scan)、初始化、连接,这五种状态中一种。五种状态切换描述为:未连接时,设备广播信息(向周围邻居讲“我来了”),另外一个设备一直监听或按需扫描(看看有没有街坊邻居家常里短可聊,打招呼“哈,你来啦”),两个设备连接初始化(搬几把椅子到院子),设备连接上了(开聊)。发起聊天的设备为主设备,接受聊天的设备为从设备,同一次聊天只能有一个意见领袖,即主设备和从设备不能切换。
· HCI层,为接口层,通信部,向上为主机提供软件应用程序接口(API),对外为外部硬件控制接口,可以通过串口、SPI、USB来实现设备控制。
· L2CAP层,物流部,行李打包盒拆封处,提供数据封装服务
· SM层,保卫处,提供配对和密匙分发,实现安全连接和数据交换
· ATT层,库房,负责数据检索
· GATT层,出纳/库房前台,出纳负责处理向上与应用打交道,而库房前台负责向下把检索任务子进程交给ATT库房去做,其关键工作是把为检索工作提供合适的profile结构,而profile由检索关键词(characteristics)组成。
· GAP层,秘书处,对上级,提供应用程序接口,对下级,管理各级职能部门,尤其是指示LL层控制室五种状态切换,指导保卫处做好机要工作。
TI的这款CC2540器件可以单芯片实现BLE蓝牙协议栈结构图的所有组件,包括应用程序。
2、任务调度---OSAL操作系统抽象层
正如一个公司为了实现扩大产能和产品多样化,建立了多个办公室和工厂一样,蓝牙为了实现同多个设备相连,或实现多功能,也实现了功能扩充,这就产生了调度问题。因为,虽然软件和协议栈可扩充,但终究最底层的执行部门只有一个。
为了实现多事件和多任务切换,需要把事件和任务对应的应用,以及其相关的提供支撑“办公室”和“工厂”打包起来,并起一个名字OSAL操作系统抽象层,类似于集团公司以下的子公司。
3、设备改造---HAL硬件抽象层
如果实现软件和硬件的低耦合,使软件不经改动或很少改动即可应用在另外的硬件上,这样就方便硬件改造、升级、迁移后,软件的移植。HAL硬件抽象层正是用来抽象各种硬件的资源,告知给软件。其作用类似于嵌入式系统设备驱动的定义硬件资源的h头文件。其角色类似于现代工厂的设备管理部。
4、BLE低功耗蓝牙系统架构
附图7 BLE低功耗蓝牙系统架构图,图中的Task用附图6BLE蓝牙协议栈结构图来描述
BLE低功耗蓝牙软件有2个主要组成: OSAL操作系统抽象层和 HAL硬件抽象层,多个Task任务和事件在OSAL管理下工作,而每个任务和事件又包括3个组成:BLE 协议栈,profiles和应用程序。 附图7同样可以用现代工厂模式来类比,如附图8。
附图8 BLE低功耗蓝牙软件5个主要组成用现代工厂架构来类比。
有了上面的简介,下面对OSAL、HAL和BLE进行稍微深入的介绍。
二、OSAL操作系统抽象层
1、软件功能由OSAL管理下的事件来实现
OSAL作为调度核心,BLE协议栈、profile定义、所有的应用都围绕它来实现。OSAL不是传统大家使用的操作系统,而是一个允许软件建立和执行事件的循环。
软件功能是由任务事件来实现的,创建一个任务事件需要以下工作:
· 创建task identifier任务ID;
· 编写任务初始化(task initialization routine)进程,并需要添加到OSAL初始化进程中,这就是说系统启动后不能动态添加功能;
· 编写任务处理程序;
· 如有需要提供消息服务。
BLE协议栈的各层都是以OSAL任务方式实现,由于LL控制室的时间要求最为迫切,所以其任务优先级最高。为了实现任务管理,OSAL通过消息处理(messageprocess),存储管理,计时器定时等附加服务实现。
2、系统启动流程
为了使用OSAL,在main函数的最后要启动一个名叫osal_start_system的进程,该进程会调用由特定应用决定的启动函数osalInitTasks(来启动系统)。osalInitTasks逐个调用BLE协议栈各层的启动进程来初始化协议栈。随后,设置一个任务的8bit任务ID(task ID),跳入循环等待执行任务,系统启动完成。
3、任务事件与事件处理
3.1 进程优先级和任务ID
· 任务优先级决定于任务ID,任务ID越小,优先级越高
· BLE协议栈各层的任务优先级比应用程序的高
· 初始化协议栈后,越早调入的任务,任务ID越高,优先级越低,即系统倾向于处理新到的任务
3.2 事件变量和旗语
每个事件任务由对应的16bit事件变量来标示,事件状态由旗号(taskflag)来标示。如果事件处理程序已经完成,但其旗号并没有移除,OSAL会认为事情还没有完成而继续在该程序中不返回。比如,在SimpleBLEPeripheral实例工程中,当事件START_DEVICE_EVT发生,其处理函数SimpleBLEPeripheral_ProcessEvent就运行,结束后返回16bit事件变量,并清除旗语SBP_START_DEVICE_EVT。
3.3 事件处理表单
每当OSAL事件检测到了有任务事件,其相应的处理进程将被添加到由处理进程指针构成的事件处理表单中,该表单名叫taskArr(taskarray)。taskArr中各个事件进程的顺序和osalInitTasks初始化函数中任务ID的顺序是对应的。
3.4 事件调度的方法
有两种,最简单的方法是使用osal_set_event函数(函数原型在OSAL.h文件中),在这个函数中,用户可以像定义函数参数一样设置任务ID和事件旗语。第二种方法是使用osal_start_timerEx函数(函数原型在OSAL_Timers.h文件中),使用方法同osal_set_event函数,而第三个以毫秒为单位的参数osal_start_timerEx则指示该事件处理必须要在这个限定时间内,通过定时器来为事件处理计时。
4、存储管理
类似于Linux嵌入式系统内存分配C函数mem_alloc,OSAL利用osal_mem_alloc提供基本的存储管理,但osal_mem_alloc只有一个用于定义byte数的参数。对应的内存释放函数为osal_mem_free。
5、进程间通信—通过消息机制实现
不同的子系统通过OSAL的消息机制通信。消息即为数据,数据种类和长度都不限定。
消息收发过程描述如下:
接收信息,调用函数osal_msg_allocate创建消息占用内存空间(已经包含了osal_mem_alloc函数功能),需要为该函数指定空间大小,该函数返回内存空间地址指针,利用该指针就可把所需数据拷贝到该空间。
发送数据,调用函数osal_msg_send,需为该函数指定发送目标任务,OSAL通过旗语SYS_EVENT_MSG告知目标任务,目标任务的处理函数调用osal_msg_receive来接收发来的数据。建议每个OSAL任务都有一个消息处理函数,每当任务收到一个消息后,通过消息的种类来确定需要本任务做相应处理。消息接收并处理完成,调用函数osal_msg_deallocate来释放内存(已经包含了osal_mem_free函数功能)。
三、硬件抽象层HAL
当新的硬件平台做好后,只需修改HAL,而不需修改HAL之上的协议栈的其他组件和应用程序。
四、BLE低功耗蓝牙协议栈
1、BLE库文件
TI蓝牙协议栈是以单独一个库文件提供的,并没有提供源代码,因此不做深入说明。对于TI的BLE实例应用,这个单独库文件已经够用,列出了所有的库文件。
附图9 BLE库文件
由于GAP和GATT与用户程序直接交互,因此下文对库文件中GAP和GATT一一讲解。
2、GAP秘书处
2.1 角色(即服务,功能)
GAP运行在如下四种角色的一种:
· Broadcaster 广播员—我在,但只可远观,不可连接。
· Observer 观察员—看看谁在,但我只远观,不连接。
· Peripheral 外设(从机)—我在,谁要我就跟谁走,协议栈单层连接。
· Central 核心(主机)—看看谁在,并且愿意跟我走我就带她/他走,协议栈单层或多层连接,目前最多支持3个同时连接。
虽然指标显示BLE可以同时扮演多个角色,但是在TI提供的BLE实例应用中缺省只支持外设角色。每一种角色都由一个剧本(roleprofile)来定义。
2.2 连接
主从机连接过程
一个典型的低功耗蓝牙系统同时包含外设和核心(主机),两者的连接过程如下:
外设角色向外发送自己的信息(设备地址、名字等),主机收到外设广播信息后,发送扫描请求(scanrequest)给外设,外设响应主机的请求,连接建立完成。
连接参数
主要有通信间隙(connectioninterval)、外设鄙视(slavelatency)、最大耐心等待时间(supervisiontimeout)等,下面简单说明。
· 通信间隙—蓝牙通信是间断的、跳频的,每次连接都可能选择不同的子频带。跳频的好处是避免频道拥塞,间断连接的好处是节省功耗,通信间隙就是指两次连接之间的时间间隔。这个间隔以1.25ms为基本单位,最小6单位最大3200单位,间隙越小通信越及时,间隙越大功耗越低。
· 外设鄙视—外设与主机建立连接以后,没事的时候主机总会定期发送问候信息到外设,外设懒得搭理,这些主机发送的信息就浮云般飘过。可以忽略的连接事件个数从0到499个,最多不超过32秒。有效连接间隙= 连接间隙x (1+ 外设鄙视).
· 最大耐心等待时间—指的是为了创建一个连接,主机允许的最大等候时间,在这个时间内,不停的尝试连接。范围是10个~3200个通信间隙基本单位(1.25ms)。
以上三参数大小设置优劣是显而易见的,在此也飘过。连接参数的设置请参看后文“5.1GAP外设剧本”小节。
连接异常处理
举例说明连接异常,如主机采用从机并不舒坦的参数来请求连接,有如主从机已经连接了,但从机有想法了,要改参数条约。通过“连接参数更新请求(ConnectionParameter Update Request)”来解决问题,交由L2CAP“收发室物流处”处理。连接参数上文已经说了,不再叙述。
加密处理
利用配对实现,利用密匙来加密授权连接。典型的过程是:外设向主机请求口令一个(passkey)以便进行配对,待主机发送了正确的口令之后,连接通信通过主从机互换密码来校验。由于蓝牙通信是间断通信,如果一个应用需要经常通信,而每次通信都要重新申请连接,那将是劳神费力的,为此GAP安全卫士(SM,security profile)提供了一种长期签证(long-termset of keys),叫做绑定(bonding),这样每次建立连接通关流程就简便快捷了。
3、出纳GATT
GATT负责两个设备间通信的数据交互。共有两种角色:出纳员(GATTClient)和银行(GATTServer),银行提供资金,出纳从银行存取款。银行可以同时面对多个出纳员。这两种角色和主从机等角色是无关的。
GATT把工作拆分成几部分来实现:读关键词(CharacteristicValue)和描述符(CharacteristicDescriptor),用来去库房查找提取数据。写读关键词和描述符。
GATT银行(GATTServer)的业务部门(API)主要提供两个主要的功能:一是服务功能,注册或销毁服务(serviceattribute),并作为回调函数(callbackfunction);二是管理功能,添加或删除GATT银行业务。
一个角色定义的剧本可以同时定义多个角色,每个角色的服务、关键词、关键值、描述符(service,characteristic, characteristic value and descriptors)都以句柄(attributes)形式保存在角色提供的服务上。所有的服务都是一个gattAttribute_t类型的array,在文件gatt.h.中定义。
4、调用GAP和GATT的一般过程
· API调用
· 协议栈响应并返回
· 协议栈发送一个OSAL消息(数据)去调用相应任务事件
· 调用任务去接收和处理消息
· 消息清除
以设备初始化为GAP外设角色来举例说明,外设角色由其剧本(GAPperipheral role profile)来决定,实例程序在文件peripheral.c内。
· 调用API函数GAP_DeviceInit。
· GAP检查了一下说,好,可以初始化,返回值为SUCCESS (0x00),并通知BLE干活。
· BLE协议栈发送OSAL消息给外设角色剧本(peripheral roleprofile),消息内容包括要干什么(eventvalue)GAP_MSG_EVENT和指标是什么(opcodevalue,参数)。
· 角色剧本的服务任务就收到了事件请求SYS_EVENT_MSG,表示有消息来了。
· 角色剧本接收消息,并拆看到底是什么事,接着把消息数据转换(cast)成具体要干事情,并完成相应的工作(这里为gapDeviceInitDoneEvent_t)。
· 角色剧本清除消息并返回。
再举一个例子:GATT客户端设备想从GATT服务器端读取数据,即GATT出纳想从GATT银行那边取点钱出来
· 应用程序调用GATT子进程API函数GATT_ReadCharValue,传递的参数为连接句柄、关键词句柄和自身任务的ID。
· GATT答应了这个请求,返回值为SUCCESS (0x00),向下告知BLE有活干了。
· BLE协议栈在下次建立蓝牙连接时,发送取钱的指令给银行,当银行说好,我们正好有柜员没事在干剪指甲,于是把钱取出来交给了BLE。
· BLE接着就把取到的钱包成消息(OSAL message),通过出纳GATT返回给了应用程序。消息内包含GATT_MSG_EVENT和修改了的ATT_READ_RSP。
· 应用程序接收到了从OSAL来的SYS_EVENT_MSG事件,表示钱可能到了
· 应用程序接收消息,拆包检查,并把需要的钱拿走。
· 最后,应用程序把包装袋销毁,没事了,返回。
5、GAP角色剧本profiles
在TI的BLE实例应用中提供了3中GAP角色剧本,保卫处角色,和几种GATT出纳/库管示例程序服务角色。
5.1 GAP外设剧本
其API函数在peripheral.h中定义,包括:
· GAPROLE_ADVERT_ENABLED—广播使能。
· GAPROLE_ADVERT_DATA—包含在广播里的信息。
· GAPROLE_SCAN_RSP_DATA—外设用于回复主机扫描请求的信息。
· GAPROLE_ADVERT_OFF_TIME—表示外设关闭广播持续时间,该值为零表示无限期关闭广播直到下一次广播使能信号到来。
· GAPROLE_PARAM_UPDATE_ENABLE—使能自动更新连接参数,可以让外设连接失败时自动调整连接参数以便重新连接。
· GAPROLE_MIN_CONN_INTERVAL—设置最小连接间隙,缺省值为80个单位(每单位1.25ms)。
· GAPROLE_MIN_CONN_INTERVAL—设置最大连接间隙,缺省值为3200个单位。
· GAPROLE_SLAVE_LATENCY—外设鄙视参数,缺省为零。
· GAPROLE_TIMEOUT_MULTIPLIER--最大耐心等待时间,缺省为1000个单位。
GAPRole_StartDevice函数用来初始化GAP外设角色,其唯一的参数是gapRolesCBs_t,这个参数是一个包含两个函数指针的结构体,这两函数是pfnStateChange和pfnRssiRead,前者标示状态,后者标示RSSI已经被读走了。
5.2 多角色同时扮演
前文5.1中设备配置为外设,这里以设备同时为外设和广播员两种角色。方法为:去除前文外设的定义剧本peripheral.c和peripheral.h,添加新的剧本peripheralBroadcaster.c和peripheralBroadcaster.h;定义处理器值(preprocessorvalue)PLUS_BROADCASTER。
5.3 GAP主机剧本
与外设剧本相似,主机剧本的API函数在central.h中定义,包括GAPCentralRole_GetParameter和GAPCentralRole_SetParameter以及其他。如GAPROLE_PARAM_UPDATE_ENABLE连接参数自动更新使能的功能,跟外设角色的一样。
GAPCentralRole_StartDevice函数用来初始化GAP主机角色,其唯一的参数是gapCentralRolesCBs_t,,这个参数是一个包含两个函数指针的结构体,这两函数是eventCB和rssiCB,每次GAP时间发生,前者都会被调用,后者标示RSSI已经被读走。
5.4 GAP绑定管理器剧本
用于保持长期的连接。同时支持外设配置和主机配置。当建立了配对连接后,如果绑定使能,绑定管理器就维护这个连接。主要参数有:GAPBOND_PAIRING_MODE,GAPBOND_MITM_PROTECTION,GAPBOND_IO_CAPABILITIES,GAPBOND_IO_CAP_DISPLAY_ONLY,GAPBOND_BONDING_ENABLED
GAPBondMgr_Register函数用来初始化GAP主机角色,其唯一的参数是gapBondCBs_t,,这个参数是一个包含两个函数指针的结构体,这两函数是pairStateCB和passcodeCB,前者返回状态,后者用于配对时产生6为数字口令(passcode)。
5.5怎样编写一个剧本来创建(定义)新的角色(功能、服务)
以SimpleGATT Profile为剧本名称,包含两个文件simpleGATTProfile.c和simpleGATTProfile.h。包含如下主要API函数:
· SimpleProfile_AddService—用于初始化的进程,作用是添加服务句柄(serviceattributes)到句柄组(attributetable)内,寄存器读取和回写。
· SimpleProfile_SetParameter—设置剧本(profile)关键词(characteristics)
· SimpleProfile_GetParameter—获取获取设置剧本关键词
· SimpleProfile_RegisterAppCBs
· simpleProfile_ReadAttrCB
· simpleProfile_WriteAttrCB
· simpleProfile_HandleConnStatusCB
这个实例剧本共有5个关键词:
· SIMPLEPROFILE_CHAR1
· …
· SIMPLEPROFILE_CHAR5
(注:参考网络和TI官方文档 http://blog.csdn.net/ooakk/article/details/7302425)