讲解一下 BLE 蓝牙 MAC 地址分类及应用 ...... 矜辰所致
前言
最近在 CH585 使用过程中,遇到要动态修改蓝牙 MAC 地址的应用,于是了解了一下 BLE MAC 地址有关的定义规则,发现还是有必要写一篇博客来记录说明一下的。
所以本文的内容就是介绍一下 BLE 蓝牙 MAC 地址规则以及在应用中如何规范合理的定义 MAC 地址。
相关博文:
BLE 蓝牙空中报文格式与解析(广播包)沁恒微 RISC-V 蓝牙 入门教程目录:
【导航】沁恒微 RISC-V 蓝牙 入门教程目录 【快速跳转】.
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- [一、BLE MAC 地址分类](#一、BLE MAC 地址分类)
- 二、格式详解
-
- [2.1 公共地址](#2.1 公共地址)
- [2.2 随机地址](#2.2 随机地址)
-
- [2.2.1 静态设备地址](#2.2.1 静态设备地址)
- [2.2.2 不可解析私有地址](#2.2.2 不可解析私有地址)
- [2.2.3 可解析私有地址](#2.2.3 可解析私有地址)
- [2.2.4 关于 IRK](#2.2.4 关于 IRK)
- [三、 如何区分地址类型](#三、 如何区分地址类型)
- [四、 实际应用中的修改设置](#四、 实际应用中的修改设置)
-
- [4.1 上电默认地址](#4.1 上电默认地址)
- [4.2 动态修改流程](#4.2 动态修改流程)
- [4.3 静态地址](#4.3 静态地址)
- [4.4 不可解析私有地址](#4.4 不可解析私有地址)
- [4.5 可解析私有地址](#4.5 可解析私有地址)
- [4.6 公共地址](#4.6 公共地址)
- 结语
一、BLE MAC 地址分类
BLE 设备地址主要分为公共设备地址(Public Device Address)和随机设备地址(Random Device Address),随机地址又可细分为 静态地址 和 可解析与不可解析的私有地址,框架如下图:

最终对于我们应用来说,就等于分成了四类:
- 公共地址 Public Address
- 私有静态地址 Static Private Address
- 私有不可解析地址 Non-Resolvable Private Address
- 私有可解析地址 Resolvable Private Address
二、格式详解
2.1 公共地址
对于公共设备地址,格式解析如下:
格式:6 字节(48-bit),符合 IEEE EUI-48
Company ID: 24-bit\] + \[Company Assigned: 24-bit
↑ MSB 高位
示例:
DC:32:62:12:56:78
└─┬──┘ └─┬──┘
IEEE OUI 厂商分配
Comp ID Comp Assigned
(需购买) (内部管理)
.
这是最常见的情况,属于 MA-L 尺寸的地址块,另外两种是 MA-M 和 MA-S 类型,也是 48-bit 的 MAC 地址,格式和上面一致,区别就是 IEEE OUI 需要多占用一些位置,那么留给厂商自动管理的 Comp Assigned 部分就变少了,MA-M Comp Assigned 部分 20bit ,MA-L Comp Assigned 部分只有 12 bit 。
适用场景:
设备需要永久固定身份,一些工业设备,高端旗舰产品,比如网关、工业传感器、 beacon 基站等等。
对于我们实际手机扫描到公共地址的设备,如下图所示:

上面 DC:32:62:D0:BE:68 前面 24 bit DC:32:62 是南京沁恒微的公司 ID,这个是可以查询的(查询的网站大家自己网上找一下):

在用户界面层面,一般来说蓝牙 MAC 地址排列按照 MSB -> LSB(从左到右,高位在左边) 顺序。蓝牙手机 APP 扫描到的 MAC 地址都是高位在左。
2.2 随机地址
对于随机设备地址,我们上面介绍过,其下面有3个子类,其最高 2 bit 决定子类型(bit 47:46,即最高字节的 bit7:6),表格如下:
| bit7 (MSB) | bit6 | 子类型 |
|---|---|---|
| 0 | 0 | Non-resolvable Private |
| 0 | 1 | Resolvable Private (RPA) |
| 1 | 0 | Reserved(预留) |
| 1 | 1 | Static Device Address |
我们一个一个来格式说明。
2.2.1 静态设备地址
Static Device Address(静态设备地址),格式:

解析如下:
格式要求:
bit 47:46 = 11(最高字节在 0xC0 到 0xFF 之间)
剩余 46 bit 就是随机的数据,可以自定义,但是至少有一个 0 和一个 1(不能全 0 或全 1)
示例:C0:11:22:33:44:55
↑
1100 0000 → bit7=1, bit6=1 ✓
手机实际扫描图如下:

适用场景:
一台设备模拟多个设备、动态切换地址,智能硬件、DIY 项目、测试设备。
2.2.2 不可解析私有地址
Non-resolvable Private Address(不可解析私有地址),格式:

解析如下:
格式:
bit47:46 = 00(最高字节在 0x00 到 0x3F 之间)
剩余 46 bit 完全随机,可以全0,也可以全1。
示例:
3F:FF:FF:FF:FF:FF
↑
0011 1111 → bit7=0, bit6=0 ✓
手机实际扫描图如下:

适用场景:
纯广播 Beacon,一次性设备,不想被任何设备记住身份完全匿名场景,不能用于需要配对的设备。
2.2.3 可解析私有地址
Resolvable Private Address (RPA)(可解析私有地址),格式:

解析如下:
格式:
bit 47:46 = 01(最高字节在 0x40 到 0x7F 之间)
整体 48bit 格式:
0b01 + prand (22-bit) + hash (24-bit)
↑ MSB 高位
其中:
prand(22 bit):随机数,必须至少有一个 0 和一个 1
hash(24 bit): 用 IRK + prand 加密生成的哈希值 hash = AES(IRK, prand)
示例:42:AC:0B:12:6A:00
↑
0100 0010 → bit7=0, bit6=1 ✓
手机实际扫描图如下:

适用场景:
手机、耳机、手环等消费类电子,设备需要频繁换地址,但又要和已配对手机保持连接,苹果 / 谷歌强制隐私要求的场景,必须保护用户隐私的场景。
2.2.4 关于 IRK
上面可解析私有地址里面,讲到了 IRK ,我们额外介绍一下什么是 IRK。
IRK = Identity Resolution Key(身份解析密钥),身份解析密钥(IRK) 用于构造可解析私有地址(RPA),每个设备都会有自己的 IRK。
设备用自己的 IRK + 随机数(prand),通过加密算法算出一个哈希值(hash),再拼上 prand,就得到了一个临时的 RPA 地址。
两个设备第一次配对(Bonding)时,当双方需要互相解析对方的私有地址时,会互相交换并保存对方的 IRK(如果一方是固定公共地址、不使用隐私地址,只需保存对方 IRK,不需要交换自己的 IRK) 。
配对成功后,IRK 会被存在设备的绑定列表里(保存在 Flash 中掉电不丢失),后续连接用 IRK 解析 RPA 认出对方。
IRK 是用于配对后的设备识别,那他是什么时候产生的呢?
在查阅了很多资料,最后找到蓝牙官方有文档说明:

IRK 可以由设备在生产阶段被分配,或随机生成,也可以使用其他方式。至于我们 CH585 的 IRK ,如果需要使用可解析私有地址,需要随机生成一下,下面会有对应说明。
三、 如何区分地址类型
只通过查看设备的 MAC 地址,是无法确定地址类型的。
因为我们不能保证地址的高位几个字节是厂商的 ID,还是用户自定义的,有可能是自定义的数据正好匹配上了厂商 ID 位置。
唯一可靠的确定 MAC 地址类型的方式是 查看设备广播/数据包中的 TxAdd 位 (此位为0: 公共地址,此位为1:随机地址)。
在我之前博文 《BLE 蓝牙空中报文格式与解析(广播包)》 有广播包的格式解析:

确定了随机地址,再通过查看 MAC 地址的最高 2 bit 决定子类型(bit 47:46,即最高字节的 bit7:6)。
通过蓝牙协议分析仪可以很直观的查看(使用的是沁恒微蓝牙协议分析仪) :


总结一下上面的基础理论介绍,可以用下面的一张图片概括:

上面的理论介绍和类型区分是基于 MAC 地址规范使用的情况,有时候在产品开发 / 调试 的时候,可能使用一些不规范的定义,比如仅供测试的假的公共地址。
四、 实际应用中的修改设置
完成了理论的介绍,下面来说明一下实际应用,如何实现不同的 MAC 地址。
4.1 上电默认地址
芯片上电以后就要配置自己的 MAC 地址, 根据 BLE_MAC 宏定义是否存在来决定使用哪种 MAC 地址,但是在初始化时候配置的 MAC 地址,都属于公共地址,如下图:

如果不需要中途修改 MAC 地址,直接修改上面自定义数组可以改变设备的 MAC 地址(公共地址)。
4.2 动态修改流程
如果在应用途中需要修改 MAC 地址,可以使用下面函数:
c
bStatus_t GAP_ConfigDeviceAddr( uint8_t addrType, uint8_t *pStaticAddr );
//第一个参数可选如下宏定义
// GAP_ADDR_TYPE_DEFINES GAP Address Types
#define ADDRTYPE_PUBLIC 0x00 //!< Use the BD_ADDR
#define ADDRTYPE_STATIC 0x01 //!< Static address
#define ADDRTYPE_PRIVATE_NONRESOLVE 0x02 //!< Generate Non-Resolvable Private Address
#define ADDRTYPE_PRIVATE_RESOLVE 0x03 //!< Generate Resolvable Private Address
要使得修改的 MAC 地址生效,需要先关闭广播,修改MAC 地址,再打开广播流程操作,可参考如下流程:

此为参考示例,在参数修改处使用 GAP_ConfigDeviceAddr 函数配置新的 MAC 地址。只要满足 关闭广播 -> 修改配置 -> 确保广播关闭后,再开启广播 ,代码逻辑可以自行调整。
测试方式:大家可以在设备开启任务里加一个任务,比如 10s 后启动这个修改配置的任务,那么上电时候我们扫描可以看到设备原本的 MAC 地址, 10s 后再扫描就能发现设备的 MAC 地址发生了变化:
c
if(events & SBP_START_DEVICE_EVT)
{
// Start the Device
GAPRole_PeripheralStartDevice(Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs);
tmos_start_task(Peripheral_TaskID, MY_ChangePower_EVT, 16000);
return (events ^ SBP_START_DEVICE_EVT);
}
4.3 静态地址
静态地址设置示例如下:
c
if(events & MY_ChangePower_EVT)
{
uint8_t myAddr[6] = {0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x00};
PRINT("Set Set...\r\n");
uint8_t initial_advertising_enable = FALSE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable);
// set set
// GAP_SetParamValue(TGAP_ADV_TX_POWER,0x3B);
GAP_ConfigDeviceAddr(ADDRTYPE_STATIC, myAddr);
return (events ^ MY_ChangePower_EVT);
}
说明,使用此函数设置静态地址,协议栈会自动把最高2位 bit 47:46 设置为 1。
然后,这个 MAC 地址的顺序和初始化时候设置的数组那个顺序结果是反的,仔细看也能知道初始化的时候赋值时那个 for 循环是反过来的,这里就提一下。
结果图就是上文中格式解析部分,手机实际扫描图对应部分。
4.4 不可解析私有地址
不可解析私有地址设置示例如下:
c
if(events & MY_ChangePower_EVT)
{
uint8_t ownAddr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F};
PRINT("Set Set...\r\n");
uint8_t initial_advertising_enable = FALSE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable);
// set set
// GAP_SetParamValue(TGAP_ADV_TX_POWER,0x3B);
GAP_ConfigDeviceAddr(ADDRTYPE_PRIVATE_NONRESOLVE, ownAddr);
return (events ^ MY_ChangePower_EVT);
}
说明,使用此函数设置不可解析私有地址,要注意最高位 bit 47:46 必须为 0 才能设置成功。 也就是自定义数组最后一个字节的 bit7 和 bit6 必须为 0 。
结果图也在上文中格式解析部分 。
4.5 可解析私有地址
可解析私有地址要准备 IRK ,把自己的 IRK 告诉协议栈,用来做配对准备,然后设置自己为可解析私有地址。
示例如下:
c
if(events & MY_ChangePower_EVT)
{
PRINT("Set Set...r\n");
//关闭广播
uint8_t initial_advertising_enable = FALSE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable);
//读取 IRK ,如果失败,就生成随机 IRK
tmos_snv_init();
PRINT("tmos %x\n",tmos_snv_read(BLE_NVID_IRK,KEYLEN,BLE_IRK));
PRINT("IRK %x %x %x %x %x %x\n",BLE_IRK[5],BLE_IRK[4],BLE_IRK[3],BLE_IRK[2],BLE_IRK[1],BLE_IRK[0]);
if(tmos_snv_read(BLE_NVID_IRK,KEYLEN,BLE_IRK))
{
uint32_t rand;
uint8_t j;
for(j=0; j<4; j++)
{
rand = tmos_rand();
BLE_IRK[0+4*j] = BREAK_UINT32(rand, 0);
BLE_IRK[1+4*j] = BREAK_UINT32(rand, 1);
BLE_IRK[2+4*j] = BREAK_UINT32(rand, 2);
BLE_IRK[3+4*j] = BREAK_UINT32(rand, 3);
}
PRINT("gets here\n");
tmos_snv_write(BLE_NVID_IRK,KEYLEN,BLE_IRK);
tmos_snv_notify(1);
}
GAPRole_SetParameter(GAPROLE_IRK, KEYLEN, BLE_IRK);
// uint8_t myAddr[6] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
uint8_t myAddr[6] = {0};
GAP_ConfigDeviceAddr(ADDRTYPE_PRIVATE_RESOLVE, myAddr);
return (events ^ MY_ChangePower_EVT);
}
说明,使用此函数设置可解析私有地址,用来定义 MAC 地址的数组 myAddr[6] 数据是没有意义的,协议栈会自动生成随机地址。
除了最高位 bit 47:46 = 0b 01,每一次生成 MAC 地址都是随机的,这个大家可以自己测试一下。
4.6 公共地址
最后再来看下,如果动态修改地址里面设置为公共地址会出现什么情况,根据前面我们可以推测出来,使用如下代码进行配置:
c
uint8_t myAddr[6] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
GAP_ConfigDeviceAddr(ADDRTYPE_PUBLIC, myAddr);
使用此函数设置公共地址,这里的用来定义 MAC 地址的数组 myAddr[6] 数据是没有意义的,设备地址会变成上电时候初始化的地址。
说明,使用此函数设置公共地址,最终都会变成初始化的时候 CH58x_BLEInit 函数中 cfg.MacAddr[] 被赋予的地址。
结语
本文我们详细介绍了 BLE 蓝牙 MAC 地址分类和格式,也针对实际的应用讲解了在使用 CH58x 芯片时候如何设置修改 MAC 地址。希望能够解决大家应用中与 MAC 地址有关的问题 。
好了,本文就到这里。谢谢大家!