由浅入深蓝牙40BLE协议栈开发攻略大全.docx
《由浅入深蓝牙40BLE协议栈开发攻略大全.docx》由会员分享,可在线阅读,更多相关《由浅入深蓝牙40BLE协议栈开发攻略大全.docx(39页珍藏版)》请在冰豆网上搜索。
由浅入深蓝牙40BLE协议栈开发攻略大全
本系列教程将结合TI推出的CC254xSoC系列,讲解从环境的搭建到蓝牙4.0协议栈的开发来深入学习蓝牙4.0的开发过程。
教程共分为六部分,本文为第五部分:
第五部分知识点:
第二十一节DHT11温湿度传感器
第二十二节蓝牙协议栈之从机通讯
第二十三节蓝牙协议栈主从一体之主机通讯
第二十四节OAD空中升级
第二十五节SBL串口升级
有关TI的CC254x芯片介绍,可点击下面链接查看:
主流蓝牙BLE控制芯片详解
(1):
TICC2540
同系列资料推荐:
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全
(1)
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全
(2)
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(3)
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(4)
有关本文的工具下载,大家可以到以下这个地址:
朱兆祺ForARM
第二十一节DHT11温湿度传感器
DHT11简介
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。
传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。
因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。
每个DHT11传感器都在极为精确的湿度校验室中进行校准。
校准系数以程序的形式存在OTP内存中,传感器内部在检测型号的处理过程中要调用这些校准系数。
单线制串行接口,使系统集成变得简易快捷。
超小的体积、极低的功耗,使其成为给类应用甚至最为苛刻的应用场合的最佳选择。
产品为4针单排引脚封装,连接方便。
技术参数
供电电压:
3.3~5.5VDC
输出:
单总线数字信号
测量范围:
湿度20-90%RH,温度0~50℃
测量精度:
湿度+-5%RH,温度+-2℃
分辨率:
湿度1%RH,温度1℃
互换性:
可完全互换,
长期稳定性:
<±1%RH/年
DHT11数字湿温度传感器采用单总线数据格式。
即,单个数据引脚端口完成输入输出双向传输。
其数据包由5Byte(40Bit)组成。
数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。
DHT11的数据格式为:
8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和。
其中校验和数据为前四个字节相加。
传感器数据输出的是未编码的二进制数据。
数据(湿度、温度、整数、小数)之间应该分开处理。
例如,某次从DHT11读到的数据如图所示:
协议栈DHT11测试
打开DHT11Example工程,我们在启动事件中对DHT11进行初始化。
如果初始化失败则说明没有接传感器。
然后在定时事件中定时的读取温湿度的值。
并将结果通过UART显示到PC端。
从其中可以看到当前的温度为29摄氏度,湿度为30%,往传感器器哈一口气可以看到温湿度都上升了。
第二十二节蓝牙协议栈之从机通讯
之前都是外围模块的驱动程序,这一节开始,我们进入蓝牙4.0协议栈的核心部分,从机通讯的程序设计。
接下来的章节是蓝牙4.0协议栈最为核心的程序设计部分。
前面的大都是外围器件的实验,这节我们介绍蓝牙通讯中从机的角色,从机的主要工作是对外广播,接受主机的连接,并且接受主机发送过来的数据。
这里介绍两个函数:
bStatus_tGAPRole_SetParameter(uint16param,uint8len,void*pValue);
这个函数主要是用来配置从机的一些参数,第一个参数表示需要配置哪个参数,例如我们需要时能从机广播,则需要这样调用:
uint8initial_advertising_enable=TRUE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED,sizeof(uint8),&initial_advertising_enable);
第二个函数是特征值改变时的回调函数,当主机给从机发送数据时,从机就会回调这个函数来告知应用层有数据送达。
staticvoidsimpleProfileChangeCB(uint8paramID);
在低功耗蓝牙中,数据的传输是通过特征值的读写来实现的。
BLE协议栈的GATT层用于应用程序在两个连接设备之间的数据通信的。
从GATT层的角度看,当设备连接后,将充当一下两种角色中的一个:
GATTClient——从GATT服务器读/写数据的设备。
GATTServer——包含客户端需要读/写的数据的设备。
重要的是要注意,GATTClient和Server的角色完全独立于BLE的链路层的slave和master的角色,或GAP层peripheral和central的角色。
一个slave可以是GATTClient或GATTServer,一个master同样可以是GATTClient或GATTServer。
一个GATTServer可以有多个完成一个特定的功能或特性GATTServer组成。
在SimpleBLEPeripheral应用程序中有三个GATT服务:
MandatoryGAPService:
这个服务包含设备和访问信息,比如设备名称、供应商和产品标识。
MandatoryGATTService:
这个服务包含有关服务UUID相关信息。
SimpleGATTProfileService——这个服务是一个示例配置文件,供测试和演示。
Profile简介
为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了Profile。
Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。
Bluetooth的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范,你可根据所需要的产品实现需要的Profile,不必给开发带来更大的开销。
这就是说当需要利用蓝牙提供数据传输功能时就必须建立对应的Profile,TI的BLE协议栈为我们提供了部分Profile,其中一部分是非标准的Profile。
其中非标准的有SimpleGATTProfile和SimpleKeysProfile,我们将通过对这两个Profile的介绍及实验来了解Profile的特性和使用。
每个Profile初始化其响应的服务和内部寄存器。
GATT服务器将整个服务加到属性表中,并为每个属性分配唯一的句柄。
GATTProfile用于存储和处理GATT服务器中的数据。
在下面的实验中需要用到的都是我们自己新建的Profile,即非标准的Profile。
其中主要要注意Profile、UUID、handle、CharacteristicValues。
SimpleGATTProfile及Btool的使用
SimpleGATTProfile中包含5个特征值,每一个的属性都不同:
SimpleGATTProfile特征值属性:
Btool是PC端工具,使用特定的HCI命令与CC2540通信,PC端需要通过串口或USB连接CC2540,CC2540使用HostTestRelease工程,硬件可以使用USBDongle(对应CC2540USB)或我们提供的USBDongle。
USBDongle连接从机
使用馒头科技有限公司的USBDongle,烧写HostTestRelease固件,连接电脑后就可以用Btool软件来连接从机设备。
将从机工程编译下载到开发板,连接串口到PC端,我们通过串口来观察设备的运行,运行后可以看到设备处于广播。
这是我们插入USBDongle到电脑,可以看到识别到一个串口插入,如图,这就是USBDongle用CDC的方式实现的串口。
打开Btool,按左图配置,可以看到右图的信息,这是说明Btool已经识别到了USBDongle。
Btool的界面可以分为4个区:
1.设备信息展示
2.历史记录
3.设备控制
4.连接信息
确保周围存在设备可发现,点击Discover/Connect标签的scan按钮,CC2540就会进行10s的扫描过程,在这期间可通过Cancle按钮停止扫描。
可以看到,我们周边有两个设备,其中一个就是我们的开发板,根据串口输出的信息我们知道我们设备的地址是0X7C669D9F6297,下面我们点击establish来连接我们的开发板。
连接后可以看到两边都同时显示了连接信息。
开发板输出连接:
Btool连接的设备信息:
特征值的读写
接下来我们用Btool对SimpleProfile进行使用操作。
刚刚我们已经列出了SimpleProfile中的各个特征值。
使用UUID读取特征值,CHAR1具有读写属性,这里对SimpleProfile的第一特征值CHAR1进行读取操作,UUID为0xfff1。
选择Read/Write选项页并选择ReadUsingCharacteristicUUID功能,在CharacteristicUUID选项填入f1:
ff(高字节在前),点击Read按钮。
读取特征值成功:
下面对此特征值进行写入操作,写入操作必须使用Handle值进行,而无法使用UUID来操作,那CHAR1的Handle值的什么呢?
其实刚刚在我们读取CHAR1的值的时候就已经获取到了它的Handle。
如图,CHAR1的Handle为0x0025。
CHAR1的Handle值:
下面我们通过这个Handle对CHAR1写入十进制的10,如图,我们写入成功了。
写入成功:
在SimpleBLEPeripheral设备的串口输出中可以看到设备提示CHAR1的值变为了10。
下面来验证我们是否成功的将CHAR1改为了10,按照刚刚读取CHAR1的步骤,重新读取CHAR1的值。
CHAR1的值改为了10:
第二十二节蓝牙协议栈之从机通讯(下)#e#
蓝牙点灯
上面我们已经能够成功的改写一个特征值,那我们是不是可以通过发送特定的值来控制一个灯的亮灭呢?
答案是肯定的。
下面我们来实现这个功能。
从机工程已经有5个特征值了,我们现在增加一个特征值来控制灯的亮灭。
那我们该如何来添加特征值呢?
特征值的管理是在profile中实现的。
所以我们需要对profile进行修改。
(1)修改simpleGATTProfile.h
在simpleGATTProfile.h中可以看到现在定义的5个特征值的标示符和UUID,我们添加一个1Byte的特征值来控制灯的亮灭。
因为simpleGATTProfile是共用的文件,为了不影响其它工程,我们使用一个宏来控制新增加的属性。
接下来我们需要修改simpleGATTProfile.c,这个文件需要修改的地方较多,下面我们一步一步来修改。
(2)添加UUID
(3)添加属性
(4)属性表
(5)属性设置操作
(6)属性获取操作
(7)属性读操作
(8)属性写操作
Profile的改造完成后,我们将这个宏打开,配置工程。
接着我们在staticvoidsimpleProfileChangeCB(uint8paramID)函数的switch中加入CHAR6的判断即可。
编译烧录后,按照我们前面说的在Btool中对FFF6的UUID进行读写操作即可实现对LED的控制。
第二十三节蓝牙协议栈之主机通讯
随着蓝牙4.0模块的大量使用,为了很多从未接触过蓝牙的工程师也能快速便捷地开发蓝牙项目或者使用蓝牙,主从一体、远控IO等等特性也成为蓝牙模块必备的条件。
其实,联合第二十一节和本节(第二十二节),我们就能将一个本无固件的裸片蓝牙,使其开发为具备主从一体功能的蓝牙模块。
这两节的内容,也是本连载篇的重点部分之一。
上一节我们对从机的工作流程有了一个整体的把握。
我们现在接着来看主机的工作流程。
主机的工作主要是扫描设备,对发现的设备发起连接,然后就是对特征值的读写操作了。
手动连接
从机的对外广播是在初始化的时候完成的,那主机的扫描是在哪里开始的呢?
阅读源码可以发现主机的操作都在按键处理中完成的。
主机通过五向按键中的五个按键实现不同的功能。
staticvoidsimpleBLECentral_HandleKeys(uint8shift,uint8keys)
{
(void)shift;//Intentionallyunreferencedparameter
if(keys&HAL_KEY_UP)//向上
{
//Startorstopdiscovery
if(simpleBLEState!
=BLE_STATE_CONNECTED)//如果没有连接,开始扫描
{
if(!
simpleBLEScanning)
{
simpleBLEScanning=TRUE;
simpleBLEScanRes=0;
LCD_WRITE_STRING(“Discovering.。
。
”,HAL_LCD_LINE_1);
LCD_WRITE_STRING(“”,HAL_LCD_LINE_2);
GAPCentralRole_StartDiscovery(DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST);
}
else
{
GAPCentralRole_CancelDiscovery();
}
}
elseif(simpleBLEState==BLE_STATE_CONNECTED&&//如果连接并且发现Handle进行读写操作
simpleBLECharHdl!
=0&&
simpleBLEProcedureInProgress==FALSE)
{
uint8status;
//Doareadorwriteaslongasnootherreadorwriteisinprogress
if(simpleBLEDoWrite)
{
//Doawrite
attWriteReq_treq;
req.handle=simpleBLECharHdl;
req.len=1;
req.value[0]=simpleBLECharVal;
req.sig=0;
req.cmd=0;
status=GATT_WriteCharValue(simpleBLEConnHandle,&req,simpleBLETaskId);
}
else
{
//Doaread
attReadReq_treq;
req.handle=simpleBLECharHdl;
status=GATT_ReadCharValue(simpleBLEConnHandle,&req,simpleBLETaskId);
}
if(status==SUCCESS)
{
simpleBLEProcedureInProgress=TRUE;
simpleBLEDoWrite=!
simpleBLEDoWrite;
}
}
}
if(keys&HAL_KEY_LEFT)//左
{
//Displaydiscoveryresults
if(!
simpleBLEScanning&&simpleBLEScanRes>0)//显示扫描到的设备
{
//Incrementindexofcurrentresult(withwraparound)
simpleBLEScanIdx++;
if(simpleBLEScanIdx>=simpleBLEScanRes)
{
simpleBLEScanIdx=0;
}
LCD_WRITE_STRING_VALUE(“Device”,simpleBLEScanIdx+1,
10,HAL_LCD_LINE_1);
LCD_WRITE_STRING(bdAddr2Str(simpleBLEDevList[simpleBLEScanIdx].addr),
HAL_LCD_LINE_2);
}
}
if(keys&HAL_KEY_RIGHT)//右
{
//Connectionupdate
if(simpleBLEState==BLE_STATE_CONNECTED)//如果连接,则更新连接
{
GAPCentralRole_UpdateLink(simpleBLEConnHandle,
DEFAULT_UPDATE_MIN_CONN_INTERVAL,
DEFAULT_UPDATE_MAX_CONN_INTERVAL,
DEFAULT_UPDATE_SLAVE_LATENCY,
DEFAULT_UPDATE_CONN_TIMEOUT);
}
}
if(keys&HAL_KEY_CENTER)//中间键
{
uint8addrType;
uint8*peerAddr;
//Connectordisconnect
if(simpleBLEState==BLE_STATE_IDLE)//空闲则连接
{
//ifthereisascanresult
if(simpleBLEScanRes>0)
{
//connecttocurrentdeviceinscanresult
peerAddr=simpleBLEDevList[simpleBLEScanIdx].addr;
addrType=simpleBLEDevList[simpleBLEScanIdx].addrType;
simpleBLEState=BLE_STATE_CONNECTING;
GAPCentralRole_EstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,
DEFAULT_LINK_WHITE_LIST,
addrType,peerAddr);
LCD_WRITE_STRING(“Connecting”,HAL_LCD_LINE_1);
LCD_WRITE_STRING(bdAddr2Str(peerAddr),HAL_LCD_LINE_2);
}
}
elseif(simpleBLEState==BLE_STATE_CONNECTING||//连接则断开连接
simpleBLEState==BLE_STATE_CONNECTED)
{
//disconnect
simpleBLEState=BLE_STATE_DISCONNECTING;
gStatus=GAPCentralRole_TerminateLink(simpleBLEConnHandle);
LCD_WRITE_STRING(“Disconnecting”,HAL_LCD_LINE_1);
}
}
if(keys&HAL_KEY_DOWN)//下
{
//StartorcancelRSSIpolling
if(simpleBLEState==BLE_STATE_CONNECTED)//连接则读取RSSi的值
{
if(!
simpleBLERssi)
{
simpleBLERssi=TRUE;
GAPCentralRole_StartRssi(simpleBLEConnHandle,DEFAULT_RSSI_PERIOD);
}
else
{
simpleBLERssi=FALSE;
GAPCentralRole_CancelRssi(simpleBLEConnHandle);
LCD_WRITE_STRING(“RSSICancelled”,HAL_LCD_LINE_1);
}
}
}
}
因为从机一直处于广播状态,所以秩序将上一节中的从机程序烧录进开发板即可,然后将主机程序烧录到另外一快开发板,通过五向按键来实现和从机的连接和读写功能。
(1)上电提示
从机上电提示:
主机上电提示:
(2)根据主机的按键功能,我们按“UP”键,开始搜索周边设备。
搜索完成后,可以看到,扫描到了一个设备。
(3)接着我们查看扫描到的设备地址,按左键。
可以看到扫描到的设备地址为0x7C669D9F638A。
这个地址正是我们的从机地址。
(4)按中间键连接从机,可以看到主机提示连接成功,从机也提示连接成功。
(5)接着我们开始读取从机的RSSI值,按下键。
(6)再次按下键,取消RSSI值的读取。
(7)对从机的CHAR1进行读写,再次按上键读取到CHAR1的值为1。
(8)接着按上键,对CHAR1写入0,同时看到从机提示CHAR1的值被修改为0。
主机写入成功:
从机提示CHAR1被改变:
上电自动连接
上一节中我们通过五向按键实现了主机连接从机的功能,这一节中们来实现主机上电后自动搜索连接从机。
要实现连接,从机必须处于广播状态,剩下的工作全部由主机完成,扫描、发起连接。
主机的状态也有回调函数,主机启动后,第一个状态是初始化,所以我们在初始化完成时开始扫描,
这样开机后主机就会开始扫描周边设备,接下来我们在扫描完成后对扫描到的设备发起连接。
将工程编译下载后通过串口助手观察主机