蓝牙基本概念(功能和代码片段)
蓝牙分类(按照应用和支持协议划分)
- 经典蓝牙模块(BT):- 泛指支持蓝牙协议在0以下的模块,一般用于数据量比较大的传输,如:语音、音乐等较高数据量传输。经典蓝牙模块可再细分为:传统蓝牙模块和高速蓝牙模块。传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的时期得到广泛支持。高速蓝牙模块在2009年推出,速率提高到约24Mbps,是传统蓝牙模块的八倍,可以轻松用于录像机至高清电视、PC至PMP、UMPC至打印机之间的资料传输
- 低功耗蓝牙模块(BLE):是指支持蓝牙协议0或更高的模块,也称为BLE模块,最大的特点是成本和功耗的降低,应用于实时性要求比较高的产品中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。
BLE分层:
BLE的状态以及基本连接过程:
链路层一共有5个状态,分别为就绪态(Standby),广播态(Advertsing),扫描态(Scanning),发起态(Initiating),连接态(Connection),而扫描态又分为主动扫描和被动扫描,连接态又分为主机和从机。
- 待机状态(standby):没有连接任何设备,没有传输和发送数据。
- 广播状态(Advertiser/advertising):周期性广播状态。
- 扫描状态(Scanner/scanning):主动寻找正在广播的设备。
- 发起连接状态(Initiator/initiating):主动发起连接。
- 连接状态(connected):已经连接。
BLE信道:
- BLE的物理信道一共40个
- BLE的这40个物理信道是分成了两组的,一组是Advertising信道(3个),一组是Data信道(37个)
BLE的广播、扫描和连接:
广播和扫描回应的数据格式
用户定义广播包和扫描回应包最多能有31个字节,每个包分为有效数据和无效数数两部分。
1. 有效数据部分,由若干个AD Structure单元组成
◆Length:表示DATA(AD Type和AD Data)的长度,不包含Length。
◆AD Type:表示广播内容的类型。
◆AD Data:表示广播的内容。
2. 无效数据部分,广播包必须有31字节,如果有效数据部分达不到31字节,剩下所有补0.
广播事件分类
- 通用广播:最常用的广播方式,可以被扫描,接收到连接请求时可以作为从设备进入一个连接。
- 定向广播:针对于快速建立连接的需求,定向广播会占满整个广播信道,数据净荷只包含广播者和发起者地址,发起者收到发给自己的定向广播后,会立即发送连接请求。
- 不可连接广播:广播数据,而不进入连接状态。
- 可发现广播:不可连接,但可以响应扫描。
BLE广播间隔:是指两次广播时间之间的最小时间间隔,一般取值范围在20ms-10.24s之间,链路层会在每次广播时间期间产生一个随机广播延时时间(0ms-10ms)
扫描事件
每次扫描,设备打开接收器去监听广播设备,这称为一个扫描事件,扫描事件有两个时间参数:扫描窗口和扫描间隔:
- 扫描窗口(scan window):一次扫描进行的时间宽度。
- 扫描间隔(scan interval):两个连续的扫描窗口的起始时间之间的时间差,包括扫描休息的时间和扫描进行的时间。
连接事件
- 一个连接事件是指主设备和从设备之间相互发送数据包的过程
- 所有的数据交换都是通过连接事件来完成
- 每个事件发生在某个数据通道(0-36)
- 一个连接中,主从设备依靠连接事件交换数据
- 设备连接后,无论有无数据收发,连接事件都在按照设置的连接参数周而复始的进行着,知道一方停止响应
- 主机与从机可在单次连接事件进行多次数据传输
BLE通信:
从GATT角度来看,当两个设备建立连接后,他们处于以下两种角色之一:
GATT服务器: 它是为GATT客户端提供数据服务的设备
GATT客户端: 它是从GATT服务器读写应用数据的设备
GATT包含若干个Profile,一个Profile包含若干个Services,一个Service包含若干个Characteristics,一个Characteristic包含Properties字段和若干个Descriptor(可选)。
GATT调用下层的ATT,ATT的attirbute在GATT中表现为Characteristic。
一个设备可以有多个服务,每一个服务可以包含多个特征值。为了方便操作,每个特征值都有他的属性,例如长度(size),权限(permission),值(value),描述(descriptor),比如:
蓝牙4.0版本推出了低功耗规范,引入了两个核心协议:ATT(Attribute Protocol)和GATT(Generic Attribute Protocol).这两个协议主要目标是BLE,但是也可以运行在传统蓝牙上(BR/EDR)。
属性协议:
决定了客户端如何获取和使用属性。
对属性的操作称为协议方法,包括:命令(Command),请求(Request),响应(Response),通知(Notification),指示(Indication)和确认(Confirmation),某些属性PDU还涉及授权签名方法。
属性PDU有六类:
属性PDU |
方向 |
触发响应 |
Command |
Client -> Server |
– |
Request |
Client -> Server |
Response |
Response |
Server -> Client |
– |
Notification |
Server -> Client |
– |
Indication |
Server -> Client |
Confirmation |
Confirmation |
Client -> Server |
– |
属性PDU格式如下:
字段 |
Opcode |
Parameter |
Authentication Signature |
长度 |
1 octet |
0 – (ATT_MTU-X) octets |
0 or 12 octets |
客户端->>服务器: 请求(Request)
服务器->>客户端: 响应(Response)
客户端->>服务器: 命令(Command)
请求和命令的区别在于:请求需要响应,而命令不需要。
服务器->>客户端: 指示
客户端->>服务器: 确认
服务器->>客户端: 通知
通知和指示的区别在于:通知不需要确认,而指示需要。
代码片段
温度大于22度,点亮LED灯,否则关闭LED。
服务器端读取温度值,当超过22度时,向服务器写数据,点亮LED。
方案一:
Thunderboard(master)+BG22(slave)
修改安卓端Thunderboard源码,加入如上判断。
//获取温度同时设置是否需要点亮LED标志alarm_state public void setTemperature(float temperature, int temperatureType) { if (isEnabled()) { textView.setText(String.format( temperatureType == ThunderBoardPreferences.TEMP_FAHRENHEIT ? getContext().getString(R.string.environment_temp_f) : getContext().getString(R.string.environment_temp_c), temperatureType == ThunderBoardPreferences.TEMP_FAHRENHEIT ? temperature * 1.8f + 32f : temperature));
if(temperatureType != ThunderBoardPreferences.TEMP_FAHRENHEIT ) {
if (temperature > 22.0){ alarm_state = 1; } else{ alarm_state = 0; } } } else { textView.setText(R.string.environment_not_initialized); } }
//对LED进行操作 static public void alarm_set_led(){ boolean submitted = BleUtils.writeCharacteristics( BleManager.gatt, ThunderBoardUuids.UUID_SERVICE_AUTOMATION_IO, ThunderBoardUuids.UUID_CHARACTERISTIC_DIGITAL, alarm_state, BluetoothGattCharacteristic.FORMAT_UINT8, 0 ); }
//调用函数,对LED进行写0或1 public void setHallStrength(float hallStrength) { if (isEnabled()) { textView.setText(getContext().getString(R.string.environment_hall_strength_measure, hallStrength)); } else { textView.setText(R.string.environment_not_initialized); }
hallStrengthMeter.setValue(hallStrength, isEnabled()); DemoEnvironmentTemperatureControl.alarm_set_led(); } |
方案二(补充):
一款国产蓝牙芯片(master)++BG22(slave)
原理同方案一类似,根据address选择连接的slave,读取温度值,超过22,写LED。
硬件平台:phyplus6212
软件平台:PHY62XX_SDK_2.1.0\example\ble_central\simpleBleCentral\
//指定要连接的蓝牙设备地址 void SimpleBLECentral_Init( uint8 task_id ) { ... simpleBlePeerTarget.addr[5]=0x84; simpleBlePeerTarget.addr[4]=0x2e; simpleBlePeerTarget.addr[3]=0x14; simpleBlePeerTarget.addr[2]=0x31; simpleBlePeerTarget.addr[1]=0xc3; simpleBlePeerTarget.addr[0]=0x4f; } //读取温度 bool rd_and_wr = FALSE; static void simpleBLECentral_ReadWriteNotifyTest(void) { ... if(rd_and_wr == FALSE) { pReqread = osal_mem_alloc(sizeof(attReadReq_t)); pReqread->handle = 0x23; bStatus_t status = GATT_ReadCharValue( simpleBLEConnHandle, pReqread, simpleBLETaskId ); osal_mem_free(pReqread); } ... } //温度判断 bool alarm_state = FALSE; static void simpleBLECentralProcessGATTMsg( gattMsgEvent_t *pMsg ) { ... curr_temp = pMsg->msg.readRsp.value[0] + ((pMsg->msg.readRsp.value[1]<<8)&0xFF00); LOG("current tempature:%d\n",curr_temp); if(curr_temp >2200) { alarm_state = TRUE; } else { alarm_state = FALSE; } ... } //点亮LED static void simpleBLECentral_ReadWriteNotifyTest(void) { ... if(rd_and_wr == TRUE) { pReq = osal_mem_alloc(sizeof(attWriteReq_t)); pReq->sig = 0; pReq->cmd = 0; pReq->handle = 0x35; pReq->len = 1; pReq->value[0] = alarm_state; bStatus_t status = GATT_WriteCharValue(simpleBLEConnHandle, pReq, simpleBLETaskId); osal_mem_free(pReq); } ... }
|
心得体会
总体感觉非常棒,希望类似活动能够一直搞下去。希望硬禾知名度不断扩大,更多方式盈利,给热爱电子的小伙伴带来更多好玩的东东。
微信交流群氛围很好,小伙伴们很热心,有很多大咖,知识面很广,遇到的问题也都是通过在群中沟通解决的。
虽然是一个简单的任务,但装环境,调试代码,解bug。只有参与了,才知其中滋味。
感谢硬禾,2021,一起加油!