BLE协议栈介绍
- BLE是什么
BLE,全称BluetoothLowEnergy,即低功耗蓝牙,目前所学的BLE是蓝牙4.0标准中的一个子集。
蓝牙4.0标准中一般分为两个部分,一个是经典蓝牙模块(又包括传统蓝牙模块和高速蓝牙模块),另外一个就是BLE(低功耗蓝牙模块),从名字上来看就知道BLE主打的就是一个低功耗、高性价比的蓝牙“青春版”,因此其最大传输速率也就只有4~5KB/S,所以BLE只能适用于传输简单数据,想用来传输音视频就别想了(除非你想听和看全损)。 - BLE协议栈是什么
在蓝牙4.0标准中的BLE只是一个标准,一个存在于书面上的协议规范,而BLE协议栈就是将书面上的协议规范采用代码的方式所呈现。因此蓝牙组织SIG只负责提出规范,至于如何实现就是每个芯片厂商所要解决的问题,因此BLE协议栈也可以理解为是芯片厂商提供的预先编译好的源码或是代码库。
目前我们学习BLE所用的MCU模块为CC2540,是一款由TI(美国德州仪器半导体公司)所提供的BLE芯片,因此我们所学的BLE协议栈就是由TI所提供BLE协议栈。 - BLE协议栈的构成
协议栈的实现方式几乎都是采用分层的思想,BLE协议栈的构成如下图所示。
- 物理层(PHY)
物理层用于指定BLE所用的频段、调制解调方式和方法等。
TI的BLE协议栈中物理层指定运行在2.4GHz ISMband频段,GFSK 调制方式(高斯频移键控),40 频道 2MHz 的通道间隙(其中包括3个固定的广播通道和37个自适应自动调频数据通道)。 - 链路层(LL)
链路层是整个BLE协议栈的核心,也是BLE协议栈的难点和重点。链路层要做的事情非常多,比如具体选择哪程度个射频通道进行通信,怎么识别空中数据包,具体在哪个时间点把数据包发送出去,怎么保证数据的完整性,ACK如何接收,如何进行重传,以及如何对链路进行管理和控制等等。链路层只负责把数据发出去或者收回来,对数据进行怎样的解析则交给上面的GAP或者ATT。
TI BLE协议栈中的链路层主要是控制芯片工作在 standby(准备)、advertising(广播)、scanning(监听/扫描),initiating(发起连接)、connected(已连接)这五个状态中的一种。五种状态的切换描述为:advertising(广播)不需要连接就可以发送数据(告诉所有人,我来了),scanning(监听/扫描)来自广播的数据,initiator(发起人)将携带 connectionrequest(连接请求)来相应广播者,如果 advertiser(广播者)同意该请求,那么广播这和发起者都会进入已连接状态,发起连接的设备变为 master(主机),接收连接请求的设备变为 slave(从机)。 - 控制接口层(HCI)
控制接口层可以理解为一个通信层,向 host (主机)和 controller(控制) 提供一个标准化的接口。该层可以由软件 api 实现或者使用硬件接口 uart、spi、usb 来控制。 - 自适应协议层(L2CAP)
L2CAP对LL进行了一次简单封装,LL只关心传输的数据本身,L2CAP就要区分是加密通道还是普通通道,同时还要对连接间隔进行管理。相当于快递,将数据打包,可以让客户点对点的通信。 - 安全管理层(SM)
安全服务层,提供配对和密钥的分发,实现安全连接和数据交换。 - 属性协议层(ATT)
简单来说,ATT层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。BLE协议栈中,开发者接触最多的就是ATT。BLE引入了attribute概念,用来描述一条一条的数据。Attribute除了定义数据,同时定义该数据可以使用的ATT命令,因此这一层被称为ATT层。
在TI的BLE协议栈中,允许设备向另外一个设备展示一块特定的数据,称之为“属性”,在 ATT 环境中,展示“属性”的设备称为服务器,与之配对的设备称为客户端。链路层状态(主机和从机)与设备的 ATT 角色是相互独立的,也就是说,主机设备可以是 ATT 服务器,也可以是 ATT 客户端。从机也一样。 - 通用访问配置文件层(GAP)
GAP是对LL层payload(有效数据包)如何进行解析的两种方式中的一种,而且是最简单的那一种。GAP简单的对LL payload进行一些规范和定义,因此GAP能实现的功能极其有限。GAP目前主要用来进行广播,扫描和发起连接等。 - 通用属性配置文件层(GATT)
GATT用来规范attribute中的数据内容,并运用group(分组)的概念对attribute进行分类管理。没有GATT,BLE协议栈也能跑,但互联互通就会出问题,也正是因为有了GATT和各种各样的应用profile,BLE摆脱了ZigBee等无线协议的兼容性困境,成了出货量最大的2.4G无线通信产品。
GATT 是在 ATT 上面的一层结构,定义了使用 ATT 的服务框架,GATT 规定了配置文
件(鼎鼎有名的 profile)的结构,在 BLE 中,所有被 profile 或者服务用到的数据块都称为“特性characteristic”,两个建立连接的设备之间的所有数据通信都是通过 GATT 子程序处理,应用程序和 profile直接使用 GATT 层,在后面具体的代码中,我们会经常见到 GATT。
- 物理层(PHY)
以上各层,全部封装在 lib 库中,对外提供接口函数。
-
BLE协议栈中六种设备状态
- 待机状态(Standby):设备没有传输和发送数据,并且没有连接到任何设备。
- 广播状态(Advertiser):周期性广播状态。
- 扫描状态(Scanner):主动的寻找正在广播的设备。
- 发起连接状态(Initiator):主动向魔钩设备发起连接。
- 主设备(Master):作为主设备连接到其他设备。
- 从设备(Slave):作为从设备连接到其他设备。
设备的状态和连接过程可以参考下图:
-
BLE数据传输解析
- 数据发送
在BLE中,数据发送分为两种情况:
- GATT的client向service发送数据
从机向主机发送数据调用的是 GATT_Notification 函数实现,需要发送的数据填充到 value 中,然后数据长度填充到 len 中。
- GATT的service向client方式数据
发送可以调用 GATT_WriteCharValue 函数实现,该函教会调用协议栈里面与硬件相关的函数最终将数据通过天线发送出去,这里面涉及对射频模块的操作,例如:打开发射机,调整发射机的发送功率等内容,这些部分协议栈已经实现了,用户不需要自己写代码去实现,只需要掌握 GATT_WriteCharValue函数的使用方法即可。需要发送的数据填充到 value 中,然后数据长度填充到 len 中。
- 数据接收
与数据发送一样,BLE协议栈中的数据接收同样分为两种情况:
- 从机接收主机发送的数据
当从机接收到主机发来的数据后,从机会产生一个 GATTProfileCallback 调用,我们在这个 callback 中接收主机发送的数据。这个 callback 在从机初始化时向 Profile 注册。
- 主机接收从机发送的数据
从机通知主机后,主机需要调用simpleprofile_writeattrcb,读取从机的数据。
- 数据发送
-
BLE通信概述
在 BLE 里面,一个从机设备有多个Services,每个 Service 下面又会有多个 Characteristics,我们 BLE 通信时,其实是通过每个具体的Characteristic。可以把 Characteristic 理解为具体的端口,通过具体的端口将数据发送出去即可。那怎样区分丌同的 Services 和 Characteristics 呢,就是 UUID,在同一个从机设备中每个 Services 和Characteristics 都会有唯一的 UUID,在 CC254x 的程序里,UUID 由两个字节表示,例如 0xFFF1,在而智能机例如 Android 戒者 iOS 编程里,通常是完整的 128 位的 UUID。两者之间有公式可以转换。
主机程序通过 GATT_WriteCharValue 函数向从机发送数据,如下图,SimpleBLECentral 中的写characteristic 代码。是不是没有看到 uuid?这是因为有 uuid 对应的 handle,在 cc2540 编程是通过 handle来代替 uuid。
BLE实训实验——基于CC2540蓝牙台灯实验
- 所用的硬件
- CC2540核心板
- 液晶传感器底板
- 实现步骤
-
我们直接使用官方提供的SimpleBLEPeripheral从机例程进行修改,因为是要实现一个蓝牙台灯,那就是要使用手机来控制板载LED的亮灭,所以CC2540就需要充当一个从机的身份,因此我们直接在“…\projects\ble”目录下复制SimpleBLEPeripheral文件夹,并改名为SimpleBLEPeripheral_DeskLamp。需要注意的是,最好是要放置在原SimpleBLEPeripheral例程所在的目录下,因为“…\projects\ble”目录这里面是所有协议栈 demo 所在的位置,所有例程都必须在这个目录里编译,否则会因为找不到组件会出现大量错误。
-
修改原有程序
原工程下的APP中文件如下所示
我们要修改的就是是类似 simpleBLEPeripheral.c 这样的文件。
第一步:添加LED控制相关代码
既然是做一个蓝牙台灯,那必须要能够控制LED的亮灭,那对于LED的操作我们放置在SimpleBLEPeripheral_ProcessEvent函数中,标准操作应该是是放到任务函数对应的 init 里,不过只是控制一个LED的亮灭,而且放置在SimpleBLEPeripheral_ProcessEvent中可以完全控制GPIO。
GPIO 初始化比较简单,设置 PxDIR 具体端口的输入输出方向,置 1 是输出,清 0 是输入;然后设置PxSEL 端口的功能,置 1 是外设,清 0 是 GPIO。同时记得打开传感器电源。if ( events & SBP_START_DEVICE_EVT ) { //传感器电源控制 P2DIR |= 0x01; P2 |= 0x01; //初始化台灯控制所使用的gpio引脚,为什么放到这里初始化? //由于我们使用的GPIO可能会被系统占用,所以在系统运行后,强行将该GPIO设置为输出控制台灯 //这里使用P1_0,LED低电平点亮,因此初始化输出为高 P1DIR |= 0x01;//设置为输出 P1 &= ~0x01;//防止跳变,初始化为低 P1 |= 0x01; //始化为高,灭灯 // 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 ); }
第二步:接收数据并处理
当主机(也就是我们的手机)通过 GATT_WriteCharValue 函数向CC2540发送数据后,CC2540作为从机就需要告知用户层接收数据。
static void simpleProfileChangeCB( uint8 paramID )
{
uint8 newValue;
switch( paramID )
{
case SIMPLEPROFILE_CHAR1:
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue );
//这里是char1的数据接收,当主机通过writechar想uuid 为fff1发送的数据将在这里接收到
//我们定义一下简单的控制命令
//点亮台灯:0x01,熄灭台灯:0x00
//可使用btool或者lightblue,或者ble utility
if(newValue==0x01){//点亮台灯
P1_0=0;
}else if(newValue==0x00){//熄灭台灯
P1_0=1;
}
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
HalLcdWriteStringValue( "Char 1:", (uint16)(newValue), 10, HAL_LCD_LINE_3 );
#endif // (defined HAL_LCD) && (HAL_LCD == TRUE)
break;
当接收到数据后,协议栈会调用改函数,让用户接收 ble 数据。我们通过SimpleProfile_GetParameter 函数接收主机发送的数据,并将数据保存到 newValue 中我们约定 0x00 表示熄灭,0x01 表示点亮。
SimpleProfile_GetParameter( SIMPLEPROFILE_CHAR1, &newValue );
//这里是char1的数据接收,当主机通过writechar想uuid 为fff1发送的数据将在这里接收到
//我们定义一下简单的控制命令
//点亮台灯:0x01,熄灭台灯:0x00
//可使用btool或者lightblue,或者ble utility
if(newValue==0x01){//点亮台灯
P1_0=0;
}else if(newValue==0x00){//熄灭台灯
P1_0=1;
}
以上就是完成蓝牙台灯所需要修改的主要代码,接下来编译下载之后,CC2540将自动广播。
我们通过手机上的BLE调试软件可以找到我们的蓝牙台灯(同时与液晶底板上的地址比对看是否一致):
依次找到 uuid 为 0xFFE0 的 Service,然后在找到 UUID 为 0xFFF1 的 Characteristic
发送控制命令:
向 Characteristic 发送十六迚制数:0x00,熄灭 LED
向 Characteristic 发送十六迚制数:0x01,点亮 LED
日志记录: