☰
BLE设备地址全解析:从蓝牙协议到Android/iOS实战
2026/9/30 13:45:11 网站建设 项目流程

做BLE开发这些年,最常被新手问到的问题之一就是设备地址。扫描回调里返回的那6字节地址,到底是啥?是不是手机或开发板的MAC?为什么同一台设备,昨天扫出来是AA:BB:CC:DD:EE:FF,今天就变成了11:22:33:44:55:66?更迷惑的是,iPhone上拿到的还是一个UUID,压根不像地址。这篇文章就把BLE设备地址这件事彻底讲透,涉及ble通信协议里的链路层行为、ble连接过程中地址怎么传递,以及Android、iOS、uni-app、nRF52840抓包和ble mesh remote provisioning场景下地址的各种表现。无论你是刚接触ble蓝牙的新手,还是已经在ESP32、nRF52上写过固件的老手,这些内容都值得留个底。

1. BLE设备地址是什么:广播包和连接包里的“身份ID”

蓝牙低功耗工作在2.4GHz频段,这个频段被划分成40个射频通道,其中3个是广播通道(37、38、39),37个是数据通道。在BLE世界里,设备之间的发现、连接、数据收发,底层都靠链路层PDU在跑,而设备地址就是这些PDU里最核心的身份标识。

1.1 地址的48位格式与两个大类型

BLE设备地址长度固定是6字节,也就是48位,平时我们看到的“XX:XX:XX:XX:XX:XX”就是它的展现形式。但这个地址和你熟悉的以太网MAC地址有本质区别,它不一定是由厂商烧录的,也不一定是全球唯一的。

从协议栈层面看,BLE地址一共分两个大类:Public Device Address(公共设备地址)和 Random Device Address(随机设备地址)。凡是名字里带“Random”的,都意味着这个地址不是从IEEE申请来的固定OUI,而是设备自己生成的。判断一个地址是公共还是随机,不靠猜,而是看广播包或扫描请求里带的标志位:TxAdd和RxAdd。TxAdd=0表示发送方地址是Public类型,TxAdd=1表示发送方地址是Random类型。

在nRF52840这类芯片的开发中,很多人直接用SDK默认地址,那往往是静态随机地址。如果不理解地址类型,就容易出现“明明设备改了MAC,为什么扫描端还是显示老地址”这种让人摸不着头脑的问题。

1.2 广播扫描流程里的TxAdd和RxAdd到底做了什么

BLE设备之间的发现流程并不复杂。广播者周期性地在37、38、39三个广播通道上发送ADV_IND,这个包里面带着AdvA(广播者地址)和TxAdd标志。扫描者收到广播后如果想获取更多信息,可以回一个SCAN_REQ,里面包含ScanA(扫描者地址)和AdvA(目标广播者地址),同时通过RxAdd标志表明自己的地址类型。

这里最关键的一点是:每个链路层控制包里的地址字段都是6字节,但地址到底是Public还是Random,完全由头部标志位决定。所以抓包的时候,你不能只盯着地址的十六进制去猜,必须结合标志位来解析。

1.3 地址在连接建立时序里出现在哪个环节

完整的BLE连接过程可以简化为:广播设备发ADV_IND,扫描设备(此时变成发起者)发送CONNECT_REQ,双方进入连接状态。CONNECT_REQ这个包非常关键,里面同时携带了发起者地址(InitA)和广播者地址(AdvA),还有初始连接参数:连接间隔、从机延迟、超时时间、跳频增量等。

为了更直观,我把这个时序写出来,你在nRF52840上用抓包器分析时,看到的就是这个顺序:

广播者 扫描者/发起者 |<------------- ADV_IND (AdvA) ----------------| | | |-------------- SCAN_REQ (ScanA, AdvA) ------->| | | |<------------- SCAN_RSP (AdvData) ------------| | | |<------------- CONNECT_REQ (InitA, AdvA) -----| | | |============= 数据通道双向通信 ================|

这个时序就是很多人常说的ble蓝牙建立时序图核心部分。注意看,从SCAN_REQ开始,双方就会把自己的地址放进包里,用来做链路层过滤和身份确认。如果其中一方是随机地址,连接建立后,每次重连时地址都有可能变化,这就引出了后面要讲的地址类型问题。

2. 四种地址类型逐一说清,别再傻傻当MAC用

很多人踩坑,根源是把BLE地址理解成了“蓝牙MAC”。实际上,BLE协议栈把地址分得还挺细,除了公共地址,随机地址里又拆成三种。这四种地址长得都一样,都是6字节,但行为和用途完全不同。

2.1 Public Device Address:厂商花钱买的“固定身份证”

Public Device Address才是真正意义上的全球唯一MAC地址。它的前3字节来自IEEE分配的OUI(组织唯一标识符),后3字节由厂商自己分配,组合起来就是一个48位的全球唯一地址。想用这种地址,厂商需要向IEEE缴费申请OUI。

实际产品里用Public Address的并不多,原因有两个:一是费用和合规成本比较高,二是它完全没有隐私性。只要设备用固定公共地址广播,任何人都能通过扫描器长期跟踪这台设备的行踪。所以现在的消费类蓝牙设备,尤其是手机、手表、耳机,普遍不会直接暴露Public Address。

2.2 Static Random Address:低成本产品的通用做法

Static Random Address,翻译成中文通常叫静态随机地址。它的两个最高位是11,其余46位由设备随机生成。这个地址一旦生成,在一次上电周期内保持不变,可以认为是一台设备在正式广播前的“自我命名”。

很多BLE模组出厂默认就是这个地址类型。比如ESP32、nRF52832跑默认例程的时候,扫描端看到的地址往往不是芯片烧录时的MAC,而是SDK在启动时生成的一个静态随机地址。需要注意,如果设备每次上电都重新生成,那对用户来说就是“每次连接都要重新找设备”;更好的做法是把随机地址保存到非易失性存储区,比如Flash或者NVDS,保证重启后地址不变。

2.3 Non-resolvable Private Address:临时变化的“马甲”

Non-resolvable Private Address,两个最高位是00,其余46位按一定周期轮换。这种地址的作用很简单:不让别人跟踪,但也不提供任何能被识别的信息。它适合那些不需要长期绑定、只是临时广播数据的场景。

比如一个温度传感器,每10分钟广播一次数据,每次广播用一个新的随机地址。扫描端即使收到了,也无法确认它是不是上次那台设备。这种隐蔽性是好事,但也意味着你不能靠它做白名单过滤,无法主动连接一台地址会变的设备。

2.4 Resolvable Private Address:带密钥的“变装”方案

Resolvable Private Address是隐私保护的主力,两个最高位是01。它的核心思路是:地址看起来在变,但拥有正确密钥(IRK,Identity Resolving Key,身份解析密钥)的接收方,可以通过密码学计算识别出设备身份。

日常使用中,iPhone和绝大多数Android手机对外广播时用的就是这种地址。你在手机蓝牙设置里看到的,和它实际发出的地址,并不是一回事。连接过的设备,因为存了对方的IRK,所以哪怕地址轮换了,系统依然能认出“这是同一台手机”。这也是为什么AirPods这类配件能和手机保持稳定连接,而不会因为地址变了就断连。

2.5 怎么快速辨认:看最高两位就够

虽然最可靠的判断方式是看链路层头部标志位,但如果你想从地址本身快速区分类型,看第一字节的最高两位就行:

地址类型最高两位是否变化典型场景
Public Device Address非11/01/00(通常00开头,但仍由OUI决定)固定少数工业设备、开发板
Static Random Address11基本固定,可重启变化模组、开发板默认
Non-resolvable Private Address00定期变化临时广播、隐私广播
Resolvable Private Address01定期变化,可被IRK解析手机、消费电子设备

我建议你在固件设计阶段就明确自己用哪种地址,不要只图省事用默认值。如果你的产品需要绑定用户,Resolvable Private Address加IRK管理是目前最稳妥的方案。

3. 可解析私有地址的原理:IRK到底怎么“认出”你

有一种情况很常见:你用手机连续扫同一个开发板,显示的地址每次都变,但点连接又能连上。这是因为手机上已经保存了对应设备的IRK,能在后台把这串不断变化的地址“翻译”回真实身份。这块机制很值得展开讲。

3.1 从一个例子开始:扫描端如何判断设备“没变”

假设一个蓝牙耳机使用Resolvable Private Address,每过15分钟切换一个新地址。它的手机App如果只靠扫描到的地址去匹配,那每次都要重新弹窗确认。实际上不是这样,手机在第一次配对后已经把耳机的IRK存进了系统蓝牙协议栈。之后每次收到广播,就算AdvA是一个从没见过的6字节地址,系统也会拿着所有已保存的IRK去尝试解析,只要匹配上,就认为“老熟人来了”。

3.2 生成和解析的关键公式

Resolvable Private Address的48位由两部分组成:高24位是哈希值,低24位是随机数prand。两个最高位固定为01。生成过程并不复杂:

1. 随机生成一个24位的prand,并把最高两位强制设为01; 2. 用IRK作为密钥,对prand执行AES-128加密; 3. 取加密结果,得到24位哈希值; 4. 地址 = 哈希值 || prand(哈希在高位,prand在低位)。

接收方向上的解析就是反过来:

# 伪代码,仅示意逻辑,实际实现需注意字节序 def resolve_address(address, irk): prand = address & 0xFFFFFF expected_hash = aes128_encrypt(irk, prand) actual_hash = (address >> 24) & 0xFFFFFF return expected_hash == actual_hash

如果哈希匹配,接收方就确认这个地址来自持有该IRK的设备。整个过程对上层应用是透明的,链路层直接处理完了。

3.3 白名单过滤为什么要靠地址解析

BLE协议支持白名单过滤,但白名单里不仅能放固定地址,还能放IRK。当扫描或广播开启“白名单+地址解析”模式时,控制器会在硬件层面直接判断:这个新地址解析后是不是白名单里的身份。这种做法比“扫到广播再逐层上报”效率高得多,适合对功耗敏感的低功耗产品。

在实际项目中,我踩过的一个坑是:把设备设置成Static Random Address后加入了白名单,结果设备重启后生成新地址,白名单立刻失效。后来改成用IRK白名单,问题才彻底解决。

4. 平台实战:Android、iOS、uni-app里地址怎么用

地址类型讲清楚后,回到开发场景。不同操作系统对BLE地址的暴露程度完全不一样,这里面的差异如果没摸清,很容易写出“在Android上正常、在iOS上连不上”的代码。

4.1 Android:BluetoothDevice.getAddress()的那些坑

Android的BluetoothDevice对象有getAddress()方法,看起来能拿到MAC地址,实际上有很多限制。Android 6开始,获取地址需要定位权限;Android 6以下系统,这个方法返回的通常是真实MAC地址;但从Android 12开始,系统进一步限制地址可见性,某些设备上这个方法会直接返回“02:00:00:00:00:00”,这是一个被系统屏蔽后的固定占位值。

更麻烦的是,如果对端外设使用的是随机地址,Android返回的就是当前扫描到的那个随机地址,而不是设备的固定身份。所以不要把getAddress()的结果当成业务主键去存数据库,否则下一次扫描可能就匹配不上了。

4.2 iOS:为什么peripheral.identifier不是MAC地址

iOS/ iPadOS的CoreBluetooth框架里,每个扫描到的peripheral只有一个identifier属性,类型是UUID,不是BLE MAC地址。这个UUID是系统根据设备的链路层身份(通常是IRK)和本机密钥计算出来的,同一台iPhone上稳定,但换一台iPhone、恢复系统设置、或者删除蓝牙配对记录后,它都可能变。

iOS没有公开API能直接读取BLE设备地址。你找不到“getMACAddress”之类的方法,因为Apple从隐私设计上就不打算把地址暴露给应用层。理解了这一点,就不会再纠结“为什么iOS拿不到MAC地址”了。

4.3 uni-app跨平台:能不能拿deviceId去createBLEConnection

热搜里有个非常具体的问题:uni-app在iOS上能不能根据蓝牙的deviceId建立连接?答案是能,但你要先搞清楚这个deviceId在不同平台是什么。uni-app的onBluetoothDeviceFound回调里有个deviceId字段,Android端通常是蓝牙MAC地址(或当前随机地址),iOS端就是peripheral.identifier这个UUID。拿这个deviceId传给uni.createBLEConnection,系统是认的,连接能建立。

但真正要注意的是:不要把这个deviceId当作永久ID去存。iOS重启、蓝牙重置、App卸载重装后,identifier可能变化。稳妥做法是每次启动重新扫描,根据设备名、广播数据里的自定义字段、ServiceUUID来综合判断目标设备,然后拿当次的deviceId去连接。

4.4 跨平台设备识别方案总结

如果你在做一个跨平台App,想稳定识别同一台BLE设备,建议按优先级这么选:

  • 在广播数据里放入自定义的设备唯一ID字段,扫描端解析广播后拿到ID做业务识别;
  • 使用iBeacon或Eddystone这类广播协议,里面携带的major/minor或namespace/instance信息可以当作设备标识;
  • 对支持配对的设备,使用系统级的配对绑定机制,靠IRK识别身份;
  • 尽量别依赖链路层MAC地址做长周期存储,地址类型和平台限制都会让你踩坑。

我会在设计BLE设备固件时,特意把设备序列号写进广播的厂商自定义数据段。这样App连不连得上,都先能通过广播内容识别设备,用户体验好很多。

5. 抓包实测:用nRF52840嗅探真实BLE地址

前面讲的都是纸上谈兵,实际调问题时,最直观的办法就是抓空中的BLE包来看。这里我以nRF52840为例,把从硬件准备到看地址字段的完整过程理一遍。

5.1 准备工作:刷sniffer固件、装Wireshark

nRF52840本身是一颗非常常用的BLE芯片,把它刷成Sniffer固件后,就能变成一个BLE抓包器。nRF官方提供了nRF Sniffer for Bluetooth LE这个工具,配合Wireshark使用。具体步骤是:用nRF Connect for Desktop或nrfutil把sniffer固件烧进nRF52840 dongle,然后在Wireshark里选择对应的串口捕获接口。

nRF Connect for Desktop里的“Bluetooth Low Energy”应用也可以直接查看设备,但如果你要看链路层细节,比如TxAdd、AdvA、InitA字段,还是Wireshark最合适。刷固件时注意选择与你芯片型号完全匹配的sniffer版本,用错固件会出现抓不到包或者乱码。

5.2 看ADV_IND包:TxAdd标志和AdvA字段怎么读

抓包后,在Wireshark里选中一个广播包,展开“Bluetooth LE Link Layer”协议树,你会看到类似这样的内容:

Bluetooth LE Link Layer Advertisement Type: ADV_IND Advertiser Address: 5a:xx:xx:xx:xx:xx Advertiser Address Type: Random (1)

这里的关键就是Advertiser Address Type:如果是Random (1),说明TxAdd=1,地址是随机类型。你再多抓几个广播包,如果AdvA在变化,说明对端用的是Private Address;如果不变,可能是Static或Public。

我调试时有个习惯:先用抓包器确认设备地址类型,再去代码里查SDK配置。这样能迅速判断问题是出在固件配置,还是出在上层扫描逻辑。

5.3 观察CONNECT_REQ:发起者和应答者地址在哪

连接建立时抓一个CONNECT_REQ包,展开后能看到:

  • InitA:发起者地址,通常是你的手机或开发板;
  • InitA Type:发起者地址类型;
  • AdvA:应答者(广播者)地址;
  • AdvA Type:应答者地址类型。

这个包里的地址类型信息,能帮你确认双方在连接协商时用的是什么地址。有一次我排查一个“偶发连不上”的问题,抓包后发现手机作为发起者,每次Connection Request里的InitA都在变,对端固件又做了白名单过滤,导致白名单一直匹配不上。后来改成关闭白名单或改用IRK匹配,问题迎刃而解。

5.4 实际案例:同一台手机为什么每次广播地址都变

用抓包器对准一台iPhone,你会发现它发出的ADV_IND包里的AdvA地址每隔一小段时间就换一次,而且最高位总保持01。这就是Resolvable Private Address的典型表现。如果这台iPhone之前配对过某台设备,那台设备只要存储了iPhone的IRK,就能在新地址出现后仍然认出它。

开发时如果你想减少这种“地址乱跳”的干扰,可以在设备的调试固件里把地址固定成Static Random Address,抓包和日志匹配都会轻松很多。但产品化之前一定要记得改回带隐私保护的方案。

6. BLE Mesh场景下的地址问题:别把Unicast Address当MAC

蓝牙Mesh是近年很火的方向,尤其是工业照明、智能楼宇这类大规模组网场景。这里面的地址体系和经典BLE完全不一样,如果还拿“广播地址”的概念去套,会非常痛苦。

6.1 Mesh地址体系与链路层地址的区别

BLE Mesh网络里的设备不叫“从机”,而叫“节点”。每个节点在被配置(Provisioning)后,会获得一个16位的Unicast Address(单播地址),这是Mesh网络内的唯一标识。这个地址和链路层的BLE MAC地址没有任何映射关系,它只存在于Mesh协议层。

Mesh里还有Group Address(组播地址)和Virtual Address(虚拟地址)。比如一栋楼的照明系统,会把“3楼所有灯”配成一个Group Address,发一条消息就能同时控制一批设备。这种抽象是Mesh的优势,但也意味着你不能靠扫描到的BLE广播地址去反推它是Mesh里的哪个节点。

6.2 Remote Provisioning中怎么发现和绑定设备

热搜词里提到的ble mesh remote provisioning,指的是远程配置未入网设备的过程。普通Provisioning需要Provisioner和被配设备在物理距离内能互相扫描到,但Remote Provisioning允许一个已经入网的节点,通过Mesh网络转发Provisioning PDU,让Provisioner去配置另一个不在身边的设备。

这里被配置设备在链路上仍会发出Unprovisioned Device Beacon,里面携带的是一个128位的Device UUID,用来标识设备身份。所以做远程配置时,你关注的重点不是链路层6字节地址,而是Device UUID和Provisioning过程中分配的Unicast Address。

6.3 排查建议

远程配置连不上的时候,我一般会分几步排查:先确认代理节点有没有发出Mesh Beacon,再打开抓包工具看Unprovisioned Device Beacon里的Device UUID和广播信号强度,最后检查代理节点是否被授权执行Remote Provisioning操作。链路层地址在这种场景里只是“搬运工”,再怎么变都不影响Mesh层的身份识别。

7. 常见问题速查表与避坑清单

根据我过去几年的经验,把BLE地址常见的问题整理成一张速查表,方便你排查时快速定位:

问题可能原因解决思路
同一台设备地址一直在变使用Private Resolvable/Non-resolvable地址配对并存储IRK,或改用Static地址调试
Android上拿不到真实MACAndroid 6+权限限制,Android 12+系统屏蔽改从广播数据中的业务ID识别设备
iOS上只有UUID没有MACCoreBluetooth设计如此将peripheral.identifier作为会话级标识,不长期依赖
uni-app iOS deviceId连接失败identifier变化或设备已不在广播每次启动重新扫描,按广播内容匹配后连接
白名单过滤失效白名单存的是固定地址,对端却用私有地址白名单改用IRK匹配
Mesh节点身份对不上把Unicast Address当MAC用用Mesh地址体系和Device UUID做节点标识

除了表格里的坑,还有几个经验可以分享:

固件里如果用Static Random Address,务必保存到掉电不丢失的区域。我见过太多产品重启后地址改变,导致用户手机里保存的老设备怎么都连不上。

如果你的外设需要被多个App扫描和识别,最好不要在扫描端做“只能连一个”的强绑定逻辑,BLE本身支持一对多广播,业务层的状态管理要清晰。

抓包分析时,优先用Wireshark看链路层字段,不要只看上层工具的“友好展示”。很多协议栈问题只有链路层才能暴露出来。

最后,产品量产前,建议把所有设备统一写入唯一的业务序列号。链路层地址只作为“信使”,业务识别全靠序列号,这样无论Android还是iOS、无论地址如何轮换,你的系统都不会乱。

做BLE开发这几年,我个人最深的体会是:设备地址只是发现阶段的“临时姓名”,千万别把它当成用户的“身份证号”。理解清楚地址类型、平台差异和Mesh特殊场景,你就能少走很多弯路。遇到问题的时候,不妨先抓个包,看看空中到底发生了什么,再决定要不要改代码。这个习惯帮我省下了大量排查时间,也推荐给你试试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询