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 次 | 避免过于频繁影响通信性能 |
| 距离模型 | 对数距离路径损耗模型 | 仅供参考,实际精度有限 |
程序运行流程:
- Server 和 Client 建立连接
- Client 启动周期定时器(如每秒触发)
- 定时器回调中调用
sle_read_remote_device_rssi(conn_id) read_rssi_cb收到 RSSI 值,加入滑动窗口- 计算窗口平均值,输出当前信号强度
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 反推距离:
// 粗略距离估算(仅供参考!)
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,可以提前避免与信号过弱的设备建立不稳定的连接。