一文搞清 BLE 蓝牙 UUID

描述

一文带你搞清楚蓝牙 UUID      ...... 矜辰所致

前言

在使用蓝牙的过程中,UUID 作为通用唯一识别码,是必须要清楚的,按理来说它其实只是属于一个简单的概念,但是由于蓝牙协议的层次分明,对于初学者来说很容易看得迷糊,理不清楚。所以本文就带大家详细认清蓝牙中的 UUID 。

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

一、 理论基础

简单过一遍理论基础,UUID(通用唯一识别码,Universally Unique Identifier) ,相当于蓝牙通信中的 “身份证”,通过标准化和自定义两种形式,确保蓝牙设备间能准确识别、通信和交互。

蓝牙协议通过 UUID 实现设备间的标准化通信,使用蓝牙对外提供服务的设备,需要有对应的服务功能,服务是蓝牙设备中功能划分的单元,每个服务都对应着一种特定的功能或数据传输需求。

例如,当一个蓝牙设备(如智能手环)向外界广播服务时,会携带对应的 UUID,其他设备(如手机)通过识别这些 UUID,就能知道该设备提供哪些功能(如心率监测、数据传输等),并建立针对性的连接。

蓝牙 UUID 用于唯一标识蓝牙服务(Service)、特征(Characteristic)和描述符(Descriptor)等
核心组件,在蓝牙官方文档中有图:

BLE

对于蓝牙这个 服务,特征值,描述符知识理论基础大家可以自行网上翻阅资料,

在我以前的博文 [ESP32-C3 学习测试 蓝牙 篇(三、认识蓝牙 GATT 协议)]也有过这方面的说明。

我们的手机连接上蓝牙设备,也可以看到 UUID :

BLE

二、UUID 格式

蓝牙 UUID 的标准格式为 128 位,通常表示为 32 个十六进制字符,以 8-4-4-4-12 的格式分组,共 36 个字符(包括 4 个连字符):

格式:8-4-4-4-12
示例:0000180D-0000-1000-8000-00805F9B34FB

2.1 标准 UUID(SIG 定义)

由蓝牙技术联盟(Bluetooth SIG)定义,用于常见服务。

标准的UUID为:0000xxxx-0000-1000-8000-00805F9B34FB

为了节省带宽,标准 UUID 通常使用 16 位或 32 位短格式,实际通信时自动扩展为 128 位。每一个蓝牙技术联盟定义的属性有一个唯一的16位 UUID,以代替上面的基本UUID的‘x’部分。

若 16 bit UUID为xxxx,那么 128 bit UUID 为 0000xxxx-0000-1000-8000-00805F9B34FB
若 32 bit UUID为xxxxxxxx,那么 128 bit UUID 为xxxxxxxx-0000-1000-8000-00805F9B34FB

比如下图中,第一个服务就是标准 UUID:

BLE

2.2 自定义 UUID

用于私有服务或厂商特定功能,需开发者自行生成,通常使用 UUID 随机生成器生成 128 位 UUID,确保全球唯一性。

比如下图中的最后一个 Service ,使用的就是自定义的 128 位 UUID :

BLE

一般应用来说,多见 16bit 或者128 bit UUID ,本文后面的讨论也皆在讨论这两种情况。

三、UUID 分类

那我们上面知道,服务有 UUID ,特征有 UUID ,描述符有 UUID,而且有时候服务UUID 和 特征值 UUID 会一样?这怎么区分开呢?

上面这个问题就是新手容易搞迷糊的地方,首先他们确实是共用 16-bit 或者 128 bit 编号的,而且确实存在ServiceCharacteristic UUID 一样的情况,但是!!!Service 和 Characteristic UUID 一样的情况只有在使用自定义的 128-bit UUID 的情况下才会发生!!16-bit 区间被 SIG 固定了用途。

上面说到 16-bit 区间被 SIG 固定了用途,我们平时蓝牙连接设备后,几乎大部分设备都能见到 16-bit UUID 为 0x1800 、0x1801 什么的,如下图:

BLE

其实这些都是蓝牙联盟规定好固定的:

  • 0x180x 开头 → 只能当 Service UUID
  • 0x2Axx 开头 → 只能当 Characteristic UUID
  • 0x29xx 开头 → 只能当 Descriptor UUID

只要记住这种规定,以后看到 UUID 就不会犯迷糊了,下面把常见的一些列出来,当做记录,也方便自己以后查阅。

3.1 0x180x 开头UUID(Service UUID)

0x180x 区间只能当 Service UUID , 但是不是说 Service UUID 只能用 0x180x 这里要搞清楚,因为我们可以自定义,也可以使用一些预留区域的值。

我们用表格记录一下:

通用类

16-bit服务英文名称中文场景
0x1800Generic Access必含,设备名、外观、连接参数
0x1801Generic Attribute必含,Service Changed
0x180ADevice Information厂商字符串/版本号
0x180FBattery Service电池电量,手环/键鼠必备

基本上做 BLE 从机,都得加上 0x1800 / 0x1801 / 0x180A / 0x180F 四件套。

传感器与健康(手环、手表、医疗)

16-bit服务英文名称中文场景
0x180DHeart Rate心率
0x1809Health Thermometer体温计
0x1810Blood Pressure血压计
0x1818Cycling Power骑行功率计
0x1816Cycling Speed & Cadence踏频/速度
0x1814Running Speed & Cadence跑步速度
0x1843Pulse Oximeter血氧仪
0x183EWeight Scale体重秤
0x183FLocation & Navigation自行车码表
0x181BEnvironmental Sensing温湿度/气压/光照
0x183BBinary Sensor门窗/烟雾开关量

做传感器需要用到 0x180D / 0x1809 / 0x1810 / 0x1843 / 0x183E / 0x181B 等上面这些。

HID & 音频 & OTA

16-bit服务英文名称中文场景
0x1812Human Interface Device键盘/鼠标/遥控
0x1813Scan Parameters扫描窗口参数(配合 HID)
0x1811Alert Notification Service来电/短信提醒
0x184ECommon AudioLE Audio 统一服务
0x181EBond Management删除配对键
0x181FImmediate Alert防丢器“响铃”
0x1825Object Transfer ServiceOTA/文件传输

做 BLE 键鼠/遥控 0x1812 必用。

手机/PC 常用后台服务

16-bit服务英文名称中文场景
0x1802Immediate Alert防丢响铃
0x1803Link Loss断开报警
0x1804Tx Power发射功率
0x1805Current Time Service同步系统时间
0x1806Reference Time Update网络校时
0x1807Next DST Change夏令时
0x1808Glucose血糖仪
0x1815Automation IO通用 GPIO 控制
0x1817Position Quality定位精度
0x1819Location & Speed位置速度
0x183AMedia Control媒体播放控制
0x183CMesh ProvisioningMesh 配网
0x183DMesh ProxyMesh 代理

3.2 0x2Axx 开头UUID(Characteristic UUID)

0x2Axx (0x2A00 – 0x2AFF) 区间只能当 Characteristic UUID , 同上面一样,反过来并不是必须用这个区间来做 Characteristic UUID。

通用与设备信息

UUID名称典型用途
0x2A00Device Name设备名称,Generic Access 必含
0x2A01Appearance外观图标(键盘/鼠标/温度计…)
0x2A04Peripheral Preferred Connection ParametersPPCP,广播里常用
0x2A05Service ChangedGeneric Attribute 必含,OTA 必备
0x2A19Battery Level电池百分比,0-100
0x2A23System ID8 字节,OUI+Vendor 自编
0x2A24Model Number String型号
0x2A25Serial Number String序列号
0x2A26Firmware Revision StringFW 版本
0x2A27Hardware Revision StringHW 版本
0x2A28Software Revision StringSW 版本
0x2A29Manufacturer Name String厂商名

传感器与运动类(手环/手表/温度计)

UUID名称(EN)中文说明 / 典型用途
0x2A37Heart Rate Measurement心率测量值,一次性通知(uint8 flags + 心率 + 能量等)
0x2A38Body Sensor Location身体传感器位置(0-6:胸、腕、指、手、耳、足、腰)
0x2A39Heart Rate Control Point心率控制点,写 0x01 可重置能量消耗累计
0x2A1CTemperature Measurement温度测量结果(float °C + 时间戳可选)
0x2A1DTemperature Type测温位置类型(腋下、体表、直肠等,枚举 0-9)
0x2A1EIntermediate Temperature中间温度值,连续测温时周期性通知
0x2A6EPressure气压/压力值(uint32,分辨率 0.1 Pa)
0x2A6FHumidity相对湿度(uint16,分辨率 0.01 %)
0x2A6DRainfall降雨量(uint16,分辨率 0.1 mm)
0x2A5BCSC Measurement骑行/步速综合测量(轮转速 + 曲柄转数 + 时间戳)
0x2A5CCSC FeatureCSC 支持特性位域(轮速、曲柄、多传感等)
0x2A5DSensor Location传感器安装位置(曲柄、轮毂、鞋、胸等 0-15)
0x2A63Cycling Power Measurement骑行功率实时测量(功率 W + 可选踏频、扭矩等)
0x2A64Cycling Power Vector功率矢量(各踏点扭矩、角度等数组,用于高阶骑行分析)
0x2A65Cycling Power Feature功率计功能位域(扭矩源、矢量、多链轮等)
0x2A6CElevation海拔高度(uint24,分辨率 0.1 m)
0x2A6ABattery Power State电池功率状态(位域:是否充电、电量充足、低电、临界等)

电池与电源

UUID名称(EN)中文说明 / 典型用途
0x2A19Battery Level电池剩余电量百分比(uint8,0–100)。最常用,单电池场景一条 Characteristic 即可。
0x2A1ABattery Power State电池“功率/状态”位域:充电中/放电中/充满/低电/临界/故障等;与 0x2A6A 同义,二选一。
0x2A1BBattery Group Info电池组信息:组内电池数量 + 每个电池的 0/1 状态位图;支持多电池设备一次性汇报。

HID 与 DFU(键鼠、OTA)

UUID名称(EN)中文说明 / 典型用途
0x2A22Boot Keyboard Input Report8 字节键盘输入(BIOS 阶段),位图+修饰键。
0x2A32Boot Mouse Input Report3 字节鼠标输入(按键+XY 位移)。
0x2A33Boot Mouse Output Report1 字节主机→鼠标输出(LED 同步)。
0x2A4AHID InformationHID 版本与特性标志(1 字节 country code + 2 字节 flags)。
0x2A4BHID Control Point主机写 0x00/0x01 让设备 suspend/resume;无响应,纯控制。
0x2A4CHID Protocol Mode切换 Boot Protocol(0x00) 与 Report Protocol(0x01)。
0x2A4DReport(HID)通用输入/输出/特征报告,日常键鼠数据就靠它。
0x2A4EReport MapHID 用途描述符,主机先读它才能解析后续 Report。
0x2AE9Object Action Control PointDFU 控制点:执行 Create/Execute/Verify/CRC/Abort 等动作,写命令+返回响应。
0x2AEBObject IDDFU 对象标识(uint32),例如镜像编号、Bank 号。
0x2AECObject Size(DFU)DFU 对象总字节数(uint32),升级前主机先读。

安全与配对

UUID名称(EN)中文说明 / 典型用途
0x2A3DSupported New Alert Category手机端支持的新增告警类型位图(邮件、短信、来电等 0-7 类)。
0x2A3EAlert Category ID单条告警的类别编号(1 字节枚举)。
0x2A3FAlert Category ID Bit Mask批量告警类别位掩码,每位对应 0x2A3E 的一个类别。
0x2A40Alert Level告警级别:无(0)、温和(1)、高(2),用于立即提醒或震动。
0x2A41Alert Notification Control Point写命令控制:使能/禁能某类告警、立即清除未读计数等。
0x2A42Unread Alert Status未读计数器:类别 ID + 未读条数(uint8)。
0x2A43New Alert新增告警:类别 ID + 字符串内容(UTF-8),一次性通知。
0x2A4FScan Refresh扫描刷新:写 0x00 通知外设“可重新开始广播”,用于减小广播窗口。
0x2A31Scan Interval Window主机建议的扫描间隔与窗口(各 2 字节,单位 0.625 ms),外设可采纳以节电。
0x2A55Bond Management Control Point绑定管理控制点:删除指定绑定、删除所有绑定、清除密钥等,写命令+返回结果。
0x2A56Bond Management Feature绑定管理特性位域:支持删除单个/全部/授权列表等能力标志。

3.2.1 键盘鼠标 UUID 说明

记录到这里,还想说明一个问题,我们发现有的 UUID 在 Service 里面之专门给某个东西的,在 Characteristic 里面也有专门给这个东西的,比如键盘鼠标,比如传感器,为什么 SIG 要在服务和特征里面“重复”用 UUID 标定?这里专门分一小节说明一下。

Service 的 0x1812 是 “功能集合” 名称 。
告诉客户端“本设备提供 HID 功能,下面可以放键盘、鼠标、消费级遥控器甚至电池报告。

Characteristic 0x2A22 / 0x2A32 / 0x2A33是 “数据槽” 名称 。
告诉客户端 “ 这个槽放的是 Boot 键盘输入报告,长度 8 字节,格式固定 ”。

名字里带“Keyboard/Mouse”只是告诉你“这条管道按 Boot 协议走固定格式”;
.
UUID 数字本身不带格式,格式由 Report Map Descriptor(UUID 0x2A4E)里那张 80~200 字节的“小字节码”定义。
.
真正决定“是键盘还是鼠标”的是 Report Map 描述符,不是 UUID 名字;
那里才写“Usage Page = Generic Desktop,Usage = Keyboard,Report Size = 8,Report Count = 6”。
这个在后期学习键盘鼠标报表的时候会遇到。

如果是键盘,手机端展开 0x1812 Human Interface Device 服务后:

HandleUUID角色手机 UI 常见文字
0x00330x2A4EReport Map“Report Map”
0x00360x2A4DReport“Input Report”
0x00390x2A4DReport“Output Report”
0x003C0x2A22Boot Keyboard Input Report“Boot KB In”
0x003F0x2A32Boot Keyboard Output Report“Boot KB Out”
0x00420x2A4AHID Information“HID Information”
0x00450x2A4CProtocol Mode“Protocol Mode”
0x00480x2A4BHID Control Point“Control Point”

→ Service 的 0x1812 只出现在最顶层,
→ 下面的 0x2A4D / 0x2A22 分配在这个 0x1812 的 Service 下面的不同 Characteristic 上,

手机端查看层次如下图所示:
BLE

还是要再次说明,真正告诉手机“这是键盘”的是 Report Map 里的描述符,不是 UUID。因为这里我们主要讨论 UUID ,就不展开讨论。

3.3 0x29xx 开头UUID(Descriptor UUID)

0x29xx 开头只能当 Descriptor UUID ,而且描述符 UUID 并不多。截至 Bluetooth v5.4,0x2900 ~ 0x290D 是当前已发布的标准描述符 UUID 。

下面的比上面的记录多了变量名,是根据WCH CH585 示例工程中的定义来列举的:

变量名(代码)UUID标准名称(EN)简单用途(含必选提示)
charExtPropsUUID0x2900Characteristic Extended Properties扩展属性位;可选
charUserDescUUID0x2901Characteristic User Description用户文字说明;可选
clientCharCfgUUID0x2902Client Characteristic Configuration开/关通知或指示;通知/指示特征必备
servCharCfgUUID0x2903Server Characteristic Configuration服务器端广播指示;极少用
charFormatUUID0x2904Characteristic Presentation Format数值格式与单位;可选
charAggFormatUUID0x2905Characteristic Aggregate Format组合多个格式;可选
validRangeUUID0x2906Valid Range合法最小最大值;可选
extReportRefUUID0x2907External Report Reference指向外部服务;HID 可选
reportRefUUID0x2908Report ReferenceReport ID 与类型;HID 可选

至于新增加的 0x2908 后面的描述符,大家可自行网上查找。

我们这里看一个示例架构,帮助大家理解一下上面的分类:

BLE

3.4 0xFFxx开头 UUID(Vendor Specific)

0xFF00 ~ 0xFFFF 区间为SIG 的公共预留池(Vendor-Specific 区间) 任何厂商都可以临时借用。

这个区间 SIG 作为公共预留区间,一般用作示例测试参考,属于临时方案,想要作为产品,要么去 SIG 申请正式分配,要么直接用 128-bit 自建 UUID。

额外说明。使用 16bit UUID 那么久必须保证同一份规范表里 Service、Characteristic 不能重复。这是官方文档规定好的。

CH585 例程把 0xFFE0 当 Service,再把 0xFFE1-0xFFE5 当 Characteristic,作为示例演示使用,如下图:

BLE

当然,除了 0xFF00 ~ 0xFFFF 区间,如果想用其他数值,比如0xCCC0、0xAAA1 这种,他们并不在 SIG 已经划出用途的 “ 固定区间 ”里,能不能用?会怎么样?

首先,单协议角度看,拿 0xCCC0 / 0xAAA1 当 Service 或 Characteristic 用,没什么问题,芯片例程、调试 Demo 里这么干只要自己知道自己的设定,也能够调试解析。

但是仅限于例程,产品不建议,它们仍是 SIG 的公共编号池资源,只是目前还没轮到给 0xCCC0 分配名字而已,指不定哪天 SIG 把 0xCCC0 分配成“某官方服务”,你就得跟着改,否则后续过认证、做 BQB 测试就会出问题。

做产品最规范的途径只有两条:

  • 去 SIG 花钱申请正式分配(16/32-bit);
  • 直接用 128-bit 自建 UUID。

所以 0xCCC0、0xAAA1 这种临时使用可以,本质上和 0xFF00 ~ 0xFFFF 的公共预留区差不多,属于 SIG 公共编号区,目前没人使用,但是随时可能有用。

还要记住,整个 16-bit 空间都由 Bluetooth SIG 的官方文档 《16-bit UUID Numbers Document》统一管理, 所有的 16-bit 空间 !!! 所有的 16-bit 空间 !!!

经过上面的说明相信大家以后在看到蓝牙设备的 UUID ,能够清楚的识别是什么类型。

四、WCH 蓝牙示例中 UUID 对应体现

最后一小节我们来简单看看一下在 WCH 蓝牙芯片例程中与 UUID 相关的地方。

我们打开从机工程:

BLE

详细的从机例程会有对应的从机示例解析博文(博主还没写= =! 等写了以后放上链接,大家可以自己查看网上其他资料~)

我们要看的地方是static gattAttribute_t simpleProfileAttrTbl[] 这个数组,因为他直接和我们手机连接上设备看到的框架图对应起来。

4.1 0x28XX 开头UUID

在第一栏我们就能看到一个名为primaryServiceUUID 的东西,但是跳转过去,我们是找不到这个定义的,因为他是一个指针,指向一个 UUID 的地址,但是这个地址是一个 Flash 的固定地址,就等于说,CH585 示例种这个 UUID 数据是烧录完后就会保存在固定地址的一个数据。

既然官方都定义为 UUID ,而且固定起来了,说明他肯定是标准的 UUID ,于是上网查了一下:

0x28xx 一共只有 4 个(SIG 已全部分配完毕),它们是 GATT 的“骨架”UUID,永远不参与用户数据,只用来告诉客户端我这条属性是干什么的:这里是服务头 / 特征头 / 引用头。

16-bit名字在属性表里的作用
0x2800Primary Service“一个主服务开始了”
0x2801Secondary Service“一个次级服务开始了” (极少用)
0x2802Include“这里包含另一个服务” (跨服务引用)
0x2803Characteristic“后面跟的是一条特征”

在示例代码中的体现:

BLE

在有些地方会有如下定义:

/**
 * GATT Declarations
 */
#define GATT_PRIMARY_SERVICE_UUID       0x2800 // Primary Service
#define GATT_SECONDARY_SERVICE_UUID     0x2801 // Secondary Service
#define GATT_INCLUDE_UUID               0x2802 // Include
#define GATT_CHARACTER_UUID             0x2803 // Characteristic

不参与用户数据,作为框架存在,所以我们在手机端是看不到这几个 UUID 的,但是我们在历程中,定义的时候确实是需要用到,而且位置是固定的,用的时候照抄常量,我们只需要记住:

  • 0x2800 只放在 服务第一行
  • 0x2803 只放在 每条特征第一行

如下:

// 1. 主服务声明 → UUID 用 0x2800
{
  {ATT_BT_UUID_SIZE, primaryServiceUUID},   // type = 0x2800
  GATT_PERMIT_READ,
  0,
  (uint8_t *)&devInfoService        // 值 = 0x180A
},

// 2. 每条特征必须先放 0x2803 声明
{
  {ATT_BT_UUID_SIZE, characterUUID},        // type = 0x2803
  GATT_PERMIT_READ,
  0,
  &devInfoMfrNameProps                      // 属性+句柄+真正UUID
},

4.2 示例对应图

我们这里上一下从机示例中的个人做的分析框图,这些会在我的从机服务分析文章中内容有详细说明,由于涉及到了 UUID,这里也放一遍加深印象。

首先是整体的框架图:

BLE
还有服务 UUID 和 特征 UUID 在程序中定义方式是不同的,如下图:

BLE

这里还有一个简单的框架示例程序,帮助大家加深印象:

// 1. 0x28xx 骨架 UUID
const uint8_t primaryServiceUUID[2]    = { 0x00, 0x28 };
const uint8_t characterUUID[2]         = { 0x03, 0x28 };

// 2. 0x180x 服务 UUID
const uint8_t envServUUID[2]           = { 0x1A, 0x18 };   // 0x181A

// 3. 0x2Axx 特征 UUID
const uint8_t tempUUID[2]              = { 0x6E, 0x2A };
const uint8_t humiUUID[2]              = { 0x6F, 0x2A };
const uint8_t battUUID[2]              = { 0x19, 0x2A };

// 4. 0x29xx 描述符 UUID
const uint8_t descCCC[2]               = { 0x02, 0x29 };
const uint8_t descUser[2]              = { 0x01, 0x29 };
const uint8_t descFormat[2]            = { 0x03, 0x29 };

// 5. 属性表 —— 与上面 0~9 行一一对应
static gattAttribute_t envAttrTbl[] = {
  /*0*/ {{ATT_BT_UUID_SIZE, primaryServiceUUID}, GATT_PERMIT_READ, 0, (uint8_t *)&envService},
  /*1*/ {{ATT_BT_UUID_SIZE, characterUUID},    GATT_PERMIT_READ, 0, &tempProps},
  /*2*/ {{ATT_BT_UUID_SIZE, tempUUID},         GATT_PERMIT_READ, 0, (uint8_t *)&tempVal},
  /*3*/ {{ATT_BT_UUID_SIZE, descCCC},          GATT_PERMIT_READ|GATT_PERMIT_WRITE, 0, (uint8_t *)tempCCC},
  /*4*/ {{ATT_BT_UUID_SIZE, characterUUID},    GATT_PERMIT_READ, 0, &humiProps},
  /*5*/ {{ATT_BT_UUID_SIZE, humiUUID},         GATT_PERMIT_READ, 0, (uint8_t *)&humiVal},
  /*6*/ {{ATT_BT_UUID_SIZE, characterUUID},    GATT_PERMIT_READ, 0, &battProps},
  /*7*/ {{ATT_BT_UUID_SIZE, battUUID},         GATT_PERMIT_READ, 0, (uint8_t *)&battVal},
  /*8*/ {{ATT_BT_UUID_SIZE, descUser},         GATT_PERMIT_READ, 0, (uint8_t *)battUserDesc},
  /*9*/ {{ATT_BT_UUID_SIZE, descFormat},       GATT_PERMIT_READ, 0, (uint8_t *)battFormat}
};

结语

本文详细的说明了一下 BLE 蓝牙的 UUID,应该算是特别全面了。

最后再总结一下:

  • 0x280x 只当“标签”,对用户不可见
  • 0x180x / 0x2Axx 是用户数据分别对应服务(0x180x)和特征(0x2Axx)
  • 0x29xx 只是附加说明
  • 其他没有定义的区间,测试可以临时用
  • 所有的16-bit UUID 都由 SIG 的官方文档 《16-bit UUID Numbers Document》统一管理
  • 自定义的 128-bit UUID,服务 UUID 可以和 特征 UUID 一样,16-bit 的服务和特征值的 UUID 不能一样
  • 同一设备,不同特征 UUID 任何时候都不能一样

好了,本文就到这里吧,谢谢大家! <3 △ <3

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分