引言
在使用nRF52开发蓝牙应用时,你可能会遇到这样的问题:扫描手机蓝牙时,只能获取部分信息,而无法获取完整的设备名称。但奇怪的是,手机之间却能正常显示蓝牙名称。这是为什么呢?
其实,这是因为蓝牙设备的名称(Complete Local Name)可能被拆分成广播包(Advertising Data) 和扫描回应包(Scan Response) ,而nRF52默认的被动扫描模式可能只接收广播包,导致名称不完整。
本文将详细解释:
- 为什么nRF52扫描不到完整名称?
- 广播包(Advertising Data)和回应包(Scan Response)的区别
- 如何修改nRF52代码,启用主动扫描并拼接完整名称
- 手机为什么能直接获取名称,而nRF52不行?
- 调试技巧与常见问题排查
1. 蓝牙广播机制:为什么名称可能不完整?
蓝牙低功耗(BLE)设备在广播时,会发送两种数据包:
- Advertising Data(广播包):包含基础信息(如设备类型、短名称、UUID等),最大31字节。
- Scan Response(扫描回应包):补充信息(如完整名称、厂商自定义数据等),最大31字节。
为什么名称会被拆分?
-
如果设备名称较长(如
"My-Phone-XYZ-123456"
),31字节可能不够用,因此:- 广播包 可能只包含短名称(如
"My-Phone"
)。 - Scan Response 可能包含剩余部分(如
"-XYZ-123456"
)。
- 广播包 可能只包含短名称(如
nRF52默认扫描模式的问题
- 被动扫描(Passive Scanning) :只接收广播包,不请求
Scan Response
,导致名称不完整。 - 主动扫描(Active Scanning) :发送
SCAN_REQ
请求Scan Response
,能获取完整数据。
2. 解决方案:修改nRF52代码,启用主动扫描
要让nRF52获取完整名称,必须:
- 启用主动扫描(Active Scanning)
- 在代码中拼接广播包和回应包的数据
步骤1:修改扫描参数
在nRF52的BLE初始化代码中,设置active=1
(主动扫描):
ini
ble_gap_scan_params_t scan_params = {
.active = 1, // 1=主动扫描(发送SCAN_REQ请求回应包)
.interval = 0x0040, // 扫描间隔
.window = 0x0040, // 扫描窗口
.timeout = 0, // 无限扫描
.scan_phys = BLE_GAP_PHY_1MBPS, // 使用1M PHY
};
sd_ble_gap_scan_start(&scan_params, &scan_buffer);
步骤2:解析并拼接广播数据
在BLE_GAP_EVT_ADV_REPORT
事件中,处理广播包和回应包:
arduino
void handle_adv_report(const ble_gap_evt_t *p_evt) {
const ble_gap_evt_adv_report_t *report = &p_evt->params.adv_report;
// 1. 解析广播包数据
parse_ad_data(report->data.p_data, report->data.len, false); // false=广播包
// 2. 如果是Scan Response(回应包)
if (report->type.scan_response) {
parse_ad_data(report->data.p_data, report->data.len, true); // true=回应包
}
}
// 解析广播/回应数据的通用函数
void parse_ad_data(const uint8_t *data, size_t len, bool is_scan_response) {
size_t offset = 0;
while (offset < len) {
uint8_t field_len = data[offset];
uint8_t field_type = data[offset + 1];
// 检查是否为设备名称字段(0x09=短名称,0x08=完整名称)
if (field_type == BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME ||
field_type == BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME) {
uint8_t name_len = field_len - 1; // 减去type占用的1字节
const char *name = (const char *)&data[offset + 2];
// 根据包类型拼接名称
if (is_scan_response) {
strcat(global_device_name, name); // 追加回应包中的名称部分
} else {
strcpy(global_device_name, name); // 初始化广播包中的名称部分
}
}
offset += field_len + 1; // 移动到下一个字段
}
}
3. 为什么手机能直接获取名称,而nRF52不行?
-
手机默认行为:
- 扫描时自动发送
SCAN_REQ
,并合并广播包和回应包。 - 部分手机(如iPhone)可能限制广播数据,仅对已配对设备显示名称(隐私保护)。
- 扫描时自动发送
-
nRF52默认行为:
- 被动扫描,不请求
Scan Response
,导致名称不完整。
- 被动扫描,不请求
4. 调试技巧与常见问题
调试方法
-
使用nRF Connect App验证:
- 扫描目标设备,检查广播包和回应包是否包含完整名称。
-
打印nRF52接收到的广播数据:
kotlinNRF_LOG_INFO("ADV Data (len=%d): %s", report->data.len, hexdump(report->data.p_data, report->data.len));
-
检查手机蓝牙设置:
- Android:确保"蓝牙可见性"开启。
- iOS:名称可能仅在连接后可见。
常见问题
-
名称仍然不完整?
- 检查
global_device_name
缓冲区是否足够大(建议≥32字节)。 - 确保
active=1
(主动扫描)已启用。
- 检查
-
Scan Response未收到?
- 部分设备(如iPhone)可能不响应
SCAN_REQ
(隐私模式)。 - 尝试建立连接后读取
Device Name
特征(UUID:0x2A00
)。
- 部分设备(如iPhone)可能不响应
5. 总结
要让nRF52正确扫描蓝牙设备名称,关键步骤:
- 启用主动扫描(
active=1
) ,确保请求Scan Response
。 - 在代码中拼接广播包和回应包,组合完整名称。
- 调试时使用nRF Connect App对比,确保数据正确解析。