【低功耗蓝牙】① 蓝牙广播数据格式分析

摘要

本文章主要讲解了蓝牙的发展史,蓝牙信号,蓝牙广播数据的格式。最后使用ESP32芯片MicroPython固件给出了蓝牙广播的具体代码,是蓝牙初学者很好的参考资料。

也可以参考下我在B站的蓝牙视频教程:

【ESP32教程】第二章: 低功耗蓝牙BLE相关概念及用法

如果您觉得这些资料有帮助,欢迎点击链接 让鹏老师恰口饭!

蓝牙发展史

蓝牙(Bluetooth)是一种无线通信协议,图标如下图所示:
蓝牙图标
蓝色的背景,中间像是一把白色的小剪刀,蓝牙的图标为什么是这样的呢?

这要从蓝牙的发展史说起!

1994-1997年之间,包括爱立信,英特尔,诺基亚在内的一些科技公司,想要制定一种短距离无线通信协议,用于各种电子设备之间通讯,取代当时的有线通信形式。
在这里插入图片描述
各个公司都推出了自己的通信协议,不同厂商的设备并不兼容。

在1997年的一次通信会议上,各个厂商希望制定一种统一的通信协议,使的他们的设备相互兼容。

来自英特尔的一位工程师 Jim Kardach 提议使用“Bluetooth”作为通信协议的名称。

他当时正在阅读有关维京人和哈拉尔国王的历史小说,由于哈拉尔国王以统一了因宗教战争和领土争议而分裂的挪威与丹麦而闻名于世,国王的成就与此次会议的的理念不谋而合,所以该工程师提议使用哈拉尔国王的绰号“Bluetooth”作为通信协议的名称。
在这里插入图片描述
传说哈拉尔国王有一个坏死的牙齿,变成的蓝色,所以世人就给他起了个绰号“Bluetooth”。 哈拉尔国王真名拼写“Harald”,蓝牙组织将国王的真名“Harald”和绰号“Bluetooth”的首字母拼合在一起,配以蓝牙的底图,就形成了蓝牙图标。
在这里插入图片描述

蓝牙技术演进史:

蓝牙技术演进史

蓝牙前言技术

目前蓝牙的最高版本是5.2,5.0以上新增的功能如下:

蓝牙5.0
引入 2Mbps 物理层,传输速率提升2倍
引入LongRang规范,传输距离提升4倍
蓝牙5.1
引入方向天线,实现高精度室内定位
蓝牙5.2
引入低功耗音频功能

低功耗蓝牙信道

蓝牙信道
蓝牙工作在2.4GHz频段,频率范围为 2402MHz – 2480 MHz,每 2MHz 一个信道,共40个信道,其中为3个广播信道,剩余的37为个数据信道。

蓝牙广播就是在这三个广播信道上,以一定的数据格式通过电磁波发射数据。

蓝牙广播数据格式

蓝牙广播包的最大长度是37个字节,其中设备地址占用了6个字节,只有31个字节是可用的。

31个可用的字节又按照一定的格式来组织,被分割为n个AD Structure。如下图所示:
蓝牙广播数据格式
每个AD Structure包含又包含三部分:

Length(1字节),AD Type(1字节),AD Data(n字节)

其中Length = AD Type 长度 + AD Data 长度

例如,如下广播数据:

0x04,0x09,0x41,0x42,0x43,0x03,0x19,0x80,0x01

按照蓝牙的广播数据格式可以解析为:
数据解析
通过上述方法,我们可以将蓝牙广播数据格式分割成若干个蓝牙广播结构体,但是先要知道每一个蓝牙广播结构体的含义,还必须知道广播结构体中AD Type 的含义。AD Type 由蓝牙组织联盟指定并发布,可以在蓝牙官方网站上下载相关文档,常用的AD Type及其含义如下:
常见的AD Type
那么现在,我们尝试分析下如下广播数据的含义:

0x05,0x09,0x31,0x32,0x33,0x34,0x02,0x0A,0x08,0x06,0xFF,0x41,0x50,0x50,0x4C,0x45

首先把他分割成一个个AD Structure,分割后如下图所示:

0x05,0x09,0x31,0x32,0x33,0x34
0x02,0x0A,0x08
0x06,0xFF,0x41,0x50,0x50,0x4C,0x45

一共可以分割成三个AD Structure:
第一个AD Structure的长度为0x05,类型为0x09(完成的设备名称),那么余下的数据含义就是完整的设备名称,0x31,0x32,0x33,0x34 按照UTF-8编码,就是1234。

第二个AD Structure的长度为0x02,类型为0x0A(发射功率),那么后面的数据0x08表示的就是发射功率。

第三个AD Structure 的长度为 0x06,类型为0xFF(厂商自定义数据),余下的数据0x41,0x50,0x50,0x4C,0x45就是厂商自定义的内容。

通过上面的示例,我想大部分读者应该都理解蓝牙广播数据格式了吧!接下来,我们就通过实践验证下上面的理论。

蓝牙广播实验

本实现使用的是ESP32芯片平台,Micro Python编程语言,uPyCraft开发环境,nRF Connect 手机 APP:
实践平台简介

开发环境的大家可以参考下我之前的这篇文章:自制教学用ESP32开发板【ESP32_Py_Board】① 开发环境搭建

我们在开发环境中键入如下代码,并运行:

from machine import Pin
from time import sleep_ms
import ubluetooth #导入BLE功能模块

ble = ubluetooth.BLE()  #创建BLE设备
ble.active(True)  #打开BLE

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')

打开手机APP(开启蓝牙和定位的权限),可以搜索到蓝牙名称为AB的设备,如下图所示:
在这里插入图片描述
上面的代码很简单,仅仅是导入蓝牙功能,创建BLE实体,打开BLE,设置BLE的广播参数。

广播参数主要设置了蓝牙的广播时间间隔为100mS,广播数据为0x02,0x01,0x06,0x03,0x09,0x41,0x42

特别地

ADType 0x01 表示的是设备标识,其含义如下:

数据位含义
Bit0LE 有限可发现模式
Bit1LE 普通可发现模式
Bit2不支持BR/EDR(经典蓝牙)
BIt3控制器端同时支持BR/EDR和LE
Bit4主机端同时自持BR/EDR和LE
Bit5-7保留
我们的设备只支持LE(低功耗蓝牙),不支持BR/EDR(经典蓝牙),一般我们都处于普通发现模式,所以我们只设置Bit1和Bit2,及0x06(b00000110)。

直播的时候,有家人问,能否将蓝牙设备名称改为中文呢?

当然是可以的,蓝牙名称采用的是UTF-8编码,理论上可以编码出任意国家的文字和字符。

python语言中,可以使用.encode("UTF-8")将字符串传换成UTF-8编码的数组,需要注意的是,一个汉字转换成UTF-8编码后,将占用三个字节的存储空间。

比如,要将蓝牙设备的名称改为,可以使用如下代码:

from machine import Pin
from time import sleep_ms
import ubluetooth #导入BLE功能模块

ble = ubluetooth.BLE()  #创建BLE设备
ble.active(True)  #打开BLE

#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x04\x09' + "中".encode("UTF-8"))

扫描到的蓝牙设备如下图:
在这里插入图片描述

蓝牙扫描请求和扫描响应

蓝牙设备除了可以主动的发射广播数据外,还可以接受其他设备的扫描请求,从而响应额外的数据,二者的区别如下:

广播是蓝牙从机设备主动发出的数据。

而扫描响应是, 当蓝牙主机收到从机的广播数据后,如果想要进一步了解从机的信息,可以向从机发送扫描请求,从机收到扫描请求后,向对应的主机回复扫描响应。
蓝牙扫描响应
扫描响应的数据格式和蓝牙广播的数据格式完全一样,其作用也基本一样,那为什么还要设置这么一个扫描响应数据呢?

我们前文中讲到,蓝牙的广播数据最多是31个字节,如果广播数据太多,这31个字节装不下时,我们就可以将一部分不太重要的数据放到扫描响应数据里面,来分担广播数据的工作。

蓝牙广播类型

按照蓝牙设备是否支持被链接,是否支持扫描响应,广播类型可以分为如下四类:

可连接的非定向广播: Connectable Undirected Event Type):

最常用的广播方式,可以被扫描,可以被连接。被连接后将进入连接状态。

可连接的定向广播:

主要用于快速连接的需求,其广播的数据中只包含自身地址和对方地址,对方收到发送给自己的定向广播后,会立即发起连接请求,达到快速建立连接的目的。定向广播在广播时会占满整个信道,造成信道拥堵,所以协议规定定向广播广播的最长时间不得超过1.28秒

不可连接的非定向广播:

仅仅发送广播数据,而不能被扫描或者连接。这也是唯一可用于只有发射机而没有接收机设备的广播类型。不可连接广播设备不会进入连接态。

(不可连接)可扫描的非定向广播(Scannable Undirected Event Type):

又称可发现广播,这种广播不能用于发起连接,但允许其他设备扫描该广播设备。这意味着该设备可以被发现,既可以发送广播数据,也可以响应扫描发送扫描回应数据,但不能建立连接。这是一种适用于广播数据的广播形式,动态数据可以包含于广播数据之中,而静态数据可以包含于扫描响应数据之中。
广播类型
如果有不清楚的地方,欢迎在下方留言!

下一篇:【低功耗蓝牙】② 蓝牙状态切换和事件处理

作者:我是鹏老师

  • 23
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
05-29
好的,如果您想要在STM32上实现蓝牙通信的话,您可以使用STM32提供的内置蓝牙模块,或者使用外部蓝牙模块(例如HC-05、HC-06等),并通过UART串口与STM32进行通信。 以下是一个使用STM32内置蓝牙模块(例如STM32WB55)进行蓝牙通信的示例代码: ```c #include "stm32wbxx.h" #include "stm32wbxx_hal.h" #define UART_TX_PIN GPIO_PIN_9 #define UART_RX_PIN GPIO_PIN_10 UART_HandleTypeDef huart1; void UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = UART_TX_PIN|UART_RX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 处理接收到的蓝牙数据 } } int main(void) { HAL_Init(); UART_Init(); while (1) { // 发送蓝牙数据 uint8_t data[] = {0x01, 0x02, 0x03}; HAL_UART_Transmit(&huart1, data, sizeof(data), 1000); // 等待接收蓝牙数据 HAL_UART_Receive_IT(&huart1, data, sizeof(data)); HAL_Delay(100); } } ``` 以上代码中,我们使用了STM32的UART串口与蓝牙模块进行通信,通过HAL_UART_Transmit()函数将数据发送到蓝牙模块,通过HAL_UART_Receive_IT()函数开启中断接收来自蓝牙模块的数据。 当然,具体的代码实现还需要根据您使用的蓝牙模块进行相应的调整。希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值