这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 基于nrf54lm20dk和xiaonrf54l15开发板的ChannelSou

共1条 1/1 1 跳转至页

基于nrf54lm20dk和xiaonrf54l15开发板的ChannelSounding

菜鸟
2026-11-01 01:29:21     打赏
基于 nRF54 系列的 BLE Channel Sounding 测距系统技术解析

本文基于两个实际工程展开分析:

工程硬件平台角色关键源码
initiatornRF54LM20 DK(nrf54lm20b/cpuapp)CS 发起端(Initiator)+ 测距请求方(RAS RREQ)src/connected_cs_initiator.c
xiao_ledSeeed XIAO nRF54L15CS 反射端(Reflector)+ 测距响应方(RAS RRSP)src/main.c

两个设备通过 BLE 连接后协作完成蓝牙信道探测(Channel Sounding, CS)测距, 发起端实时输出 IFFT、相位斜率(PBR)、往返时间(RTT)三种距离估计值。


一、技术原理

1.1 BLE Channel Sounding 是什么

Channel Sounding 是蓝牙核心规范 Core 6.0 引入的安全精细测距(Fine Ranging)特性。 与传统的 RSSI 测距(误差可达数米)不同,CS 直接测量电磁波的传播时间和相位变化, 在真实多径环境下可实现亚米级精度,同时通过 CS Security 机制抵御中继/中路人为欺骗攻击。

CS 测距由两个角色协作完成:

  • Initiator(发起端):发送 CS 同步包并启动测量流程(本文的 initiator 工程);

  • Reflector(反射端):接收并回传同步包(本文的 xiao_led 工程)。

一次完整的测量以 Procedure(测距流程)→ Event → Subevent → Step 的层级组织。 每个 Step 对应一次特定的射频收发动作,规范定义了四种 Step 模式:

模式名称测量内容对应测距方法
Mode-0校准双方内部时钟对齐校准消除收发切换延迟
Mode-1RTT 包精确时间戳(ToA/ToD)往返时间法(RTT)
Mode-2单音序列每个信道的 IQ 相位采样基于相位的测距(PBR)
Mode-3混合RTT 包 + 单音序列RTT + PBR 融合

1.2 两种核心测距方法

方法一:往返时间法(RTT,Round-Trip Time)

原理非常直观:电磁波以光速传播,测量信号往返时间即可算出距离。

2d = c × (ToA_initiator − ToD_initiator − ToD_reflector偏移 − ToA_reflector偏移)

在 Mode-1 Step 中,双方各自记录"本端发送时刻/对端到达时刻"的时间差:

tof = ( toa_tod_initiator − tod_toa_reflector ) / 4      (单位:双倍时钟 tick)

源码中的实现(distance_estimation.c,简单版):

tof = (mode_1_data[i].toa_tod_initiator -
       mode_1_data[i].tod_toa_reflector) / 2;
tof_mean_ns = tof_mean / 2.0f;
distance = tof_mean_ns * SPEED_OF_LIGHT_NM_PER_S;

关键点:Mode-0 步骤测出双方设备的时钟偏移与收发切换延迟, 在计算中抵消掉,否则纳秒级误差就会放大成分米级距离误差 (光速下 1 ns ≈ 30 cm)。

  • 优点:计算简单、抗多径相对好、不需要相位连续性

  • 缺点:单次精度受限于时间戳分辨率(约 ±15 cm)

方法二:基于相位的测距(PBR,Phase-Based Ranging)

Mode-2 中,发起端和反射端在 72 个 BLE 射频信道(2402 MHz 起,每信道 1 MHz) 上交替发送单音信号,双方接收机的 IQ 解调模块采样出每个信道的复数相位:

θ(f) = 4π·f·d / c + φ0

两个设备的相位相乘共轭合并后,得到只含距离信息的相位序列:

calc_complex_product(local_iq.i, local_iq.q, peer_iq.i, peer_iq.q,
                     &combined_i, &combined_q);
theta[n] = atan2(combined_q, combined_i);

由于相位以 2π 为周期会出现"跳变",必须先做相位解缠绕(Phase Unwrapping):

if (difference > PI)      theta[j] -= 2π;   /* 向后回绕 */
else if (difference < -PI) theta[j] += 2π;  /* 向前补偿 */

然后对 (频率, 相位) 序列做最小二乘线性回归,得到相位-频率斜率:

phase_slope = linear_regression(frequencies, theta, n);
distance = -phase_slope * (c / (4π)) / 1e6;   /* 米 */

斜率越大代表相位随频率变化越快,即传播路径越长。

  • 优点:精度高(可达 10 cm 级)、天然抗时钟偏差

  • 缺点:多径反射会导致相位畸变,需要良好的滤波与质量评估

方法三:IFFT 法(Nordic cs_de 库的增强算法)

Nordic 的距离估算库 cs_de 对 PBR 数据做了进一步加工: 把多信道 IQ 相位序列转换为频域冲激响应(512 点 FFT,CONFIG_BT_CS_DE_512_NFFT=y), 对冲激响应做超分辨率分析,提取直达径的"飞行时间"谱峰。 它本质上与相位斜率法共享数据,但对多径环境更鲁棒,输出 ifft 估计值。

1.3 RAS:把原始测距数据搬运到发起端

BLE CS 有一个独特的架构约束:Reflector 侧也产生测量数据(Mode-2 时它同样进行 IQ 采样;RTT 时间戳双方都要交换)。 为了让 Reflector 的数据能到达 Initiator 做统一解算,蓝牙规范定义了Ranging Service(RAS,UUID 0x185B):

  • RREQ(Ranging Requestor,发起端):订阅 RAS 特征、读取/接收测距数据;

  • RRSP(Ranging Responder,反射端):缓存本端 Step 数据,通过 GATT 传输给对端。

RAS 支持两种数据投递模式(本工程两种都支持,按对方能力动态选择):

  • Real-time(实时):Reflector 在 CS Procedure 进行中实时 Notify 数据;

  • On-demand(按需):Reflector 缓存完成后由 RREQ 通过 Control Procedure 拉取。

1.4 测距软件链路总览

┌────────── 射频层(SoftDevice Controller / 硬件 radio) ──────────┐
│  Mode-0 校准 → Mode-1 RTT 包 → Mode-2 IQ 单音 → ...循环        │
└──────────────────────────┬───────────────────────────────────┘
                           │ HCI 事件(subevent step data)
┌──────────────────────────▼───────────────────────────────────┐
│  Zephyr BT Host:bt_le_cs_* API / conn 回调                   │
│  RAS 层:本端数据留存 + GATT 交换对端数据                      │
└──────────────────────────┬───────────────────────────────────┘
                           │ 合并后的 local + peer step data
┌──────────────────────────▼───────────────────────────────────┐
│  cs_de_calc():质量检查 → IQ 平均 → PBR/IFFT/RTT 三路解算      │
│  应用层:9 点滑窗 + 中值滤波 → 最终距离输出                    │
└──────────────────────────────────────────────────────────────┘

二、项目实现详解

2.1 反射端工程 xiao_led(XIAO nRF54L15)

2.1.1 硬件适配:RF 开关的设备树配置

XIAO nRF54L15 板载一个射频开关(用于 CS 天线选择),必须在设备树 overlay 中声明给蓝牙控制器的天线复用驱动boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay:

cs_antenna_switch: cs-antenna-config {
    status = "okay";
    compatible = "nordic,bt-cs-antenna-switch";
    ant-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>,   /* rfsw-ctl:开关通道选择 */
                <&gpio2 3 GPIO_ACTIVE_HIGH>;  /* rfsw-pwr:开关供电使能 */
    multiplexing-mode = <0>;
};

同时关闭与 RF 开关冲突的 PDM 麦克风电源域,避免 GPIO 被占用。 控制器的 CS 天线路径配置(BT_CTLR_SDC_CS_*)与该节点配合, 射频硬件在 Mode-2 单音收发时自动切换天线。

2.1.2 Kconfig:精简的反射端配置

prj.conf的关键配置:

CONFIG_BT_LL_SW_SPLIT=n          # 强制只用 SoftDevice Controller(Zephyr 软控制器不支持 CS)
CONFIG_BT_CHANNEL_SOUNDING=y     # 启用 CS 主机支持
CONFIG_BT_RAS=y
CONFIG_BT_RAS_RRSP=y             # 本设备作为 Ranging Responder
CONFIG_BT_CTLR_SDC_CS_ROLE_REFLECTOR_ONLY=y  # 控制器只保留反射角色,省资源
CONFIG_BT_RAS_MAX_ANTENNA_PATHS=1            # 单天线,简化 XIAO 硬件
CONFIG_BT_L2CAP_TX_MTU=498       # Ranging Profile 要求 MTU ≥ 247
2.1.3 应用逻辑

反射端是"被动"角色,代码分四块:

1. LED 连接指示(工程名由来)——用 devicetree led0 别名封装dk_leds_init() / dk_set_led_on(),连接成功点亮、断开熄灭。

2. 广播 RAS 服务,让发起端能按 UUID 过滤找到自己:

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_RANGING_SERVICE_VAL)),
    BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, ...),
};

3. CS 连接回调链:作为外设,大部分 CS 流程由协议栈自动完成, 代码只需在回调里记录状态:

BT_CONN_CB_DEFINE(conn_cb) = {
    .connected = connected_cb,
    .disconnected = disconnected_cb,
    .le_cs_read_remote_capabilities_complete = remote_capabilities_cb, /* 能力交换 */
    .le_cs_config_complete = config_create_cb,      /* CS 配置建立 */
    .le_cs_security_enable_complete = security_enable_cb, /* CS 安全激活 */
    .le_cs_procedure_enable_complete = procedure_enable_cb, /* 测距启动 */
};

4. 主循环:连接后完成反射角色设置与测距参数写入

const struct bt_le_cs_set_default_settings_param default_settings = {
    .enable_initiator_role = false,
    .enable_reflector_role = true,                    /* 我只当反射端 */
    .cs_sync_antenna_selection = BT_LE_CS_ANTENNA_SELECTION_OPT_REPETITIVE,
    .max_tx_power = BT_HCI_OP_LE_CS_MAX_MAX_TX_POWER, /* 满功率发射 */
};
bt_le_cs_set_default_settings(connection, &default_settings);
/* 等待对端创建 config 后,写入 procedure 时序参数(subevent 长度、PHY、天线配置等)*/

断开后 sys_reboot(SYS_REBOOT_COLD) 冷重启复位状态机,回到广播等待下一次测量。

2.2 发起端工程 initiator(nRF54LM20 DK)

发起端是整个系统的"大脑",负责:扫描连接 → 建立 CS → 收集双方原始数据 → 解算距离。

2.2.1 连接建立流程(main 函数前半段)
被动扫描(RAS UUID 过滤) → 连接 → 加密(L2) → MTU 交换(498)
→ GATT 服务发现(Ranging Service) → 读 RAS 特性位
→ 订阅 RAS 数据通道 → CS 能力交换 → 创建 CS 配置
→ CS 安全激活 → 设置 Procedure 参数 → 启动测距

每一步都由信号量(k_sem)串行同步,确保协议栈时序正确:

k_sem_take(&sem_connected, K_FOREVER);      /* 等连接 */
bt_conn_set_security(connection, BT_SECURITY_L2);  /* CS 安全要求加密链路 */
...
bt_gatt_dm_start(connection, BT_UUID_RANGING_SERVICE, &discovery_cb, NULL);

CS 配置的核心参数(cs_config_get()):

config_params->mode = CS_CONFIG_MODE;        /* Kconfig 选择:Mode2+子模式1 等 */
config_params->min_main_mode_steps = 2;
config_params->max_main_mode_steps = 5;
config_params->mode_0_steps = 3;             /* 3 个校准步 */
config_params->role = BT_CONN_LE_CS_ROLE_INITIATOR;
config_params->rtt_type = BT_CONN_LE_CS_RTT_TYPE_AA_ONLY;
config_params->channel_selection_type = BT_CONN_LE_CS_CHSEL_TYPE_3B;

bt_le_cs_set_valid_chmap_bits() 会把 79 个经典信道中 CS 不可用的边缘信道剔除, 生成合法的 72 信道图。

2.2.2 本端 Step 数据的采集

CS Procedure 运行后,控制器每完成一个 subevent 就上报一次, 应用在连接回调 subevent_result_cb 中把本端(Initiator 视角)的原始 HCI step data 追加进缓冲区:

static void subevent_result_cb(struct bt_conn *conn,
                               struct bt_conn_le_cs_subevent_result *result)
{
    ...
    net_buf_simple_add_mem(&latest_local_steps, step_data, len);
    if (result->header.procedure_done_status == BT_CONN_LE_CS_PROCEDURE_COMPLETE) {
        /* 记录 ranging counter,等待对端同编号数据 */
    }
}
2.2.3 对端数据的获取(RAS 双模式)

读取 RAS 特性位后动态选择投递方式:

if (ras_feature_bits & RAS_FEAT_REALTIME_RD) {
    /* 对端支持实时模式:直接订阅实时测距数据 */
    bt_ras_rreq_realtime_rd_subscribe(connection, &latest_peer_steps, ranging_data_cb);
} else {
    /* 否则走按需模式:RD Ready 订阅 + Control Procedure 拉取 */
    bt_ras_rreq_rd_ready_subscribe(connection, ranging_data_ready_cb);
    bt_ras_rreq_on_demand_rd_subscribe(connection);
    bt_ras_rreq_cp_subscribe(connection);
}

ranging_data_ready_cb 里用 ranging counter 对账确保本端和对端数据来自 同一次 Procedure,防止新旧数据错配:

if (ranging_counter == most_recent_local_ranging_counter) {
    bt_ras_rreq_cp_get_ranging_data(connection, &latest_peer_steps,
                                    ranging_counter, ranging_data_cb);
}
2.2.4 数据解析与质量过滤

收到成对数据后,用 bt_ras_rreq_rd_subevent_data_parse() 同步遍历 local/peer 两个数据流,按 Step 模式分流处理:

  • Mode-2/3 步:extract_pcts() 解析双方的 IQ 相位校正项(PCT), 经 bt_le_cs_parse_pct() 还原为复数 IQ 样本; 只接受 TONE_QUALITY_HIGH 的采样点,并按天线路径分桶做累积平均降噪。

  • Mode-1 步:extract_rtt_timings() 校验 AA 检查、RSSI、时间戳有效性后, 累加 (toa_tod_initiator − tod_toa_reflector) 得到 RTT 累计值。

此外 m_is_tone_quality_ok() 统计有效 IQ 的信道数, 低于 15 个信道则标记该次测距音质不合格。

2.2.5 距离解算与滤波输出

调用 Nordic 预编译解算库一步完成三路估计:

cs_de_quality_t quality = cs_de_calc(&m_cs_de_report);
if (quality == CS_DE_QUALITY_OK) {
    store_distance_estimates_in_buffer(&m_cs_de_report.distance_estimates[ap],
                                       &distance_estimate_buffers[ap]);
}

应用层在库输出之上再加一层滑窗中值滤波,抑制偶发的多径跳变:

#define DE_SLIDING_WINDOW_SIZE 9

/* 每种算法维护最近 9 次有效估计,取中位数作为输出 */
averaged_result.ifft        = median_inplace(num_ifft,        temp_ifft);
averaged_result.phase_slope = median_inplace(num_phase_slope, temp_phase_slope);
averaged_result.rtt         = median_inplace(num_rtt,         temp_rtt);

最后主循环打印:

Latest distance estimates on antenna path 0: ifft: 1.23, phase_slope: 1.31, rtt: 1.40 meters

(选择中位数而非均值,是因为多径尖峰属于离群值,中值更鲁棒; 缓冲区用互斥锁保护,因为它同时被 BT RX 线程和主线程访问。)

2.3 双端协作时序

XIAO nRF54L15 (Reflector/RRSP)          nRF54LM20 DK (Initiator/RREQ)
        │                                        │
        │◄──────── 广播(RAS UUID 0x185B) ────────│ 扫描过滤,自动连接
        │◄────────── 加密 + MTU 交换 ────────────│
        │◄────────── GATT 服务发现 ──────────────│
        │                    ...                 │ CS 能力/配置/安全协商(HCI 自动完成)
        │◄────────── procedure parameters ───────│
        │            ═══ CS Procedure 运行 ═══   │◄─ subevent_result_cb 存本端数据
        │── RAS Notify: 对端 step data ─────────►│
        │                                        │ ranging counter 对账
        │                                        │ cs_de_calc() 解算
        │                                        │ 9 点滑窗中值滤波
        │                                        │ → 打印三路距离估计

---

实际结果如下图所示

image.png


串口输出结果

image.png








共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]