跳转至

RSSI 测距

SLE RSSI 读取、平滑滤波、距离模型

前置阅读:Hello SLE

学习目标

  • 理解 RSSI 的物理含义及其与距离的关系
  • 掌握 sle_read_remote_device_rssi() 的使用方法和回调处理
  • 掌握滑动窗口平均滤波对 RSSI 的平滑效果
  • 理解 RSSI 测距的局限性(多径效应、天线朝向、环境干扰)
  • 能够在连接状态下实时读取和监控 RSSI

规格与功能

本案例演示如何读取连接状态下的 RSSI 值,并通过滑动窗口滤波获得稳定的信号强度指示。

规格项 配置值 说明
RSSI 读取方式 主动请求 + 回调 调用 sle_read_remote_device_rssi() 后结果在回调中返回
RSSI 范围 -127 ~ +20 dBm 0x7F 表示无效值
滤波方式 10 样本滑动平均 消除瞬时抖动
读取频率 每秒 1 次 避免过于频繁影响通信性能
距离模型 对数距离路径损耗模型 仅供参考,实际精度有限

程序运行流程:

  1. Server 和 Client 建立连接
  2. Client 启动周期定时器(如每秒触发)
  3. 定时器回调中调用 sle_read_remote_device_rssi(conn_id)
  4. read_rssi_cb 收到 RSSI 值,加入滑动窗口
  5. 计算窗口平均值,输出当前信号强度

RSSI 是判断连接质量的"体温计"——掉线前 RSSI 通常会先恶化。在产品固件中监控 RSSI 趋势比监控绝对值更有意义。

基本概念

RSSI 是什么

RSSI(Received Signal Strength Indicator) 是接收端测量到的信号强度,单位 dBm。它是一个负值,越接近 0 表示信号越强:

-20 dBm ──── 极强(贴在一起)
-40 dBm ──── 很强(1~2 米)
-60 dBm ──── 正常(5~10 米)
-80 dBm ──── 弱(通信边缘)
-100 dBm ──── 极弱(即将断连)
RSSI 范围 信号质量 建议
> -50 dBm 优秀 所有功能正常,MCS 可用最高等级
-50 ~ -65 dBm 良好 常规通信正常,高吞吐可用 MCS8-10
-65 ~ -75 dBm 一般 建议降低 MCS,提高导频密度
-75 ~ -85 dBm 连接边缘,考虑降低 PHY 到 1M
< -85 dBm 极差 可能随时断连,尽快切换到低功耗低速率模式

RSSI 的读取方式

SLE 提供两种方式获取 RSSI:

方式 API 时机 特点
主动读取 sle_read_remote_device_rssi() 连接状态下随时调用 结果通过 read_rssi_cb 异步返回
扫描获取 无单独 API 扫描结果 sle_seek_result_info_t 仅扫描时可用,连接后不能获取
// 扫描结果中的 RSSI
typedef struct {
    sle_addr_t addr;
    uint8_t    data[SLE_ADV_DATA_LEN_MAX];
    uint16_t   data_len;
    int8_t     rssi;  // RSSI (-127 ~ +20, 0x7F=无效)
} sle_seek_result_info_t;

扫描时获取的 RSSI 反映的是广播信号强度,连接后的 RSSI 反映的是连接信道强度。两者可能不同(连接可能使用了不同于广播信道的频点)。

RSSI 为什么不稳定

RSSI 在现实环境中不是恒定值——即使设备不动,它也会上下波动 5~10 dBm:

flowchart TD
    A[发射信号] --> B[多路径反射]
    A --> C[天线方向性]
    A --> D[环境干扰]

    B --> E[同时收到直射和反射信号<br/>叠加后可能增强或减弱]
    C --> F[设备旋转 90°<br/>RSSI 可能变化 10 dBm]
    D --> G[WiFi/微波炉/蓝牙<br/>同频段干扰]

    E --> H[RSSI 波动]
    F --> H
    G --> H

因此,单次 RSSI 读数的参考价值有限。需要多读几次做平均:

#define RSSI_AVG_WINDOW 10

static int8_t g_rssi_window[RSSI_AVG_WINDOW];
static uint8_t g_rssi_idx = 0;

void add_rssi_sample(int8_t rssi)
{
    g_rssi_window[g_rssi_idx % RSSI_AVG_WINDOW] = rssi;
    g_rssi_idx++;
}

int8_t get_rssi_average(void)
{
    int32_t sum = 0;
    uint8_t count = (g_rssi_idx < RSSI_AVG_WINDOW) ? g_rssi_idx : RSSI_AVG_WINDOW;
    for (uint8_t i = 0; i < count; i++) {
        sum += g_rssi_window[i];
    }
    return (int8_t)(sum / count);
}

用 RSSI 估算距离(及为什么不准)

理论上,信号强度随距离衰减遵循对数路径损耗模型:

RSSI(d) = RSSI(d0) - 10 × n × log10(d / d0)

其中:
  RSSI(d)  = 距离 d 处的 RSSI
  RSSI(d0) = 参考距离 d0 (通常 1m) 处的 RSSI
  n        = 路径损耗指数 (自由空间 = 2,室内 = 2.5~4)

由 RSSI 反推距离:

d = d0 × 10^((RSSI(d0) - RSSI(d)) / (10 × n))
// 粗略距离估算(仅供参考!)
float estimate_distance_cm(int8_t rssi, int8_t rssi_at_1m, float n)
{
    // d = 1m × 10^((rssi_1m - rssi) / (10n))
    float exponent = (rssi_at_1m - rssi) / (10.0f * n);
    return 100.0f * powf(10.0f, exponent);
}
影响因素 对距离估算的影响
多径反射 信号增强 → 估算距离偏近
墙体阻隔 信号衰减 → 估算距离偏远
天线方向 设备朝向变化 → 距离大幅波动
人体遮挡 2.4GHz 易被含水物体吸收 → 距离偏远
环境变化 开门/关门/人员走动 → 持续波动

用 RSSI 测距最多做到"很近 / 正常 / 很远"三档判断。如果需要精确测距(厘米级),请使用 HADM Channel Sounding(见 HADM 测距)。

涉及 API

API 谁调用 用途
sle_read_remote_device_rssi() Server 或 Client 发起 RSSI 读取请求
sle_connection_register_callbacks() 双方 注册回调(含 read_rssi_cb
sle_start_seek() / sle_stop_seek() Client 扫描获取 RSSI(连接前)

案例说明

做什么

Client 连接 Server 后,周期性读取 RSSI,通过 10 样本滑动窗口滤波后输出平滑的信号强度。当 RSSI 持续低于阈值时发出弱信号告警。

关键配置

RSSI 读取频率

频率 适用场景 对通信的影响
每秒 1 次 常规信号监控 基本无影响
每 100ms 1 次 移动设备追踪 轻微影响吞吐
每 10ms 1 次 快速测距 明显降低可用带宽

RSSI 读取本身也是一次空中交互(虽然数据量极小),过于频繁的读取会挤占数据通信的调度机会。

代码详解

注册 RSSI 回调

static sle_connection_callbacks_t g_conn_callbacks = {
    .connect_state_changed_cb      = conn_state_cb,
    .connect_param_update_cb       = conn_param_update_cb,
    .connect_param_update_req_cb   = conn_param_update_req_cb,
    .pair_complete_cb              = pair_complete_cb,
    .read_rssi_cb                  = rssi_read_cb,  // RSSI 回调
    .set_phy_cb                    = set_phy_cb,
};

sle_connection_register_callbacks(&g_conn_callbacks);

发起 RSSI 读取

// 周期定时器中发起读取
static void rssi_timer_cb(uintptr_t arg)
{
    if (g_conn_id != 0) {
        errcode_t ret = sle_read_remote_device_rssi(g_conn_id);
        if (ret != ERRCODE_SUCC) {
            printf("[rssi] read request failed: %d\n", ret);
        }
    }
}

滑动窗口滤波

#define RSSI_WINDOW_SIZE 10

static int8_t  g_rssi_buffer[RSSI_WINDOW_SIZE];
static uint8_t g_rssi_count = 0;

static void rssi_read_cb(uint16_t conn_id, int8_t rssi, errcode_t status)
{
    if (status != ERRCODE_SUCC) {
        printf("[rssi] read failed: %d\n", status);
        return;
    }

    // 存入滑动窗口
    g_rssi_buffer[g_rssi_count % RSSI_WINDOW_SIZE] = rssi;
    g_rssi_count++;

    // 计算平均值
    uint8_t samples = (g_rssi_count < RSSI_WINDOW_SIZE)
                      ? g_rssi_count : RSSI_WINDOW_SIZE;
    int32_t sum = 0;
    for (uint8_t i = 0; i < samples; i++) {
        sum += g_rssi_buffer[i];
    }
    int8_t avg_rssi = (int8_t)(sum / samples);

    printf("[rssi] raw=%d dBm, avg=%d dBm (%d samples)\n",
           rssi, avg_rssi, samples);

    // 弱信号告警
    if (avg_rssi < -80) {
        printf("[rssi] WARNING: weak signal!\n");
    }
}

连接前后 RSSI 对比

// 扫描阶段获取 RSSI
static void seek_result_cb(sle_seek_result_info_t *result)
{
    printf("[scan] found device, RSSI=%d dBm\n", result->rssi);

    if (strstr((char *)result->data, "hello_server") != NULL) {
        if (result->rssi < -80) {
            // 信号太弱,可能连接不稳定
            printf("[scan] WARNING: device signal very weak, "
                   "connection may be unstable\n");
        }
        // 记录并连接
        memcpy(&g_remote_addr, &result->addr, sizeof(sle_addr_t));
        sle_stop_seek();
    }
}

连接前检查扫描 RSSI,可以提前避免与信号过弱的设备建立不稳定的连接。