V
Published on 2026-01-27 / 6 Visits
0
0

ExpressLRS 与 LR1121 底层通信机制深度解析

本文以 Q&A 形式整理了关于 ExpressLRS 项目中 LR1121 射频芯片通信机制的技术讨论,涵盖半双工通信、时隙同步、速率与距离权衡等核心话题。


1. LR1121 半双工通信的底层机制

Q: LR1121 是半双工的,底层双向通信的技术细节是怎样的?

LR1121 作为半双工收发器,在同一时刻只能处于发送或接收状态之一。ExpressLRS 通过中断驱动的状态机实现双向通信。

工作模式

LR1121 支持以下关键模式:

模式 说明
LR1121_MODE_SLEEP 0x00 睡眠模式
LR1121_MODE_STDBY_RC 0x01 RC 振荡器待机
LR1121_MODE_STDBY_XOSC 0x02 晶振待机
LR1121_MODE_FS 0x03 频率合成模式(空闲态)
LR1121_MODE_RX_CONT 0x04 连续接收模式
LR1121_MODE_TX 0x05 发送模式

模式切换实现

// src/lib/LR1121Driver/LR1121.cpp:451-498
void LR1121Driver::SetMode(lr11xx_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber)
{
    switch (OPmode)
    {
    case LR1121_MODE_RX_CONT:
        // 进入连续接收模式,timeout = 0xFFFFFF
        hal.WriteCommand(LR11XX_RADIO_SET_RX_OC, buf, 3, radioNumber);
        break;

    case LR1121_MODE_TX:
        // 进入发送模式
        hal.WriteCommand(LR11XX_RADIO_SET_TX_OC, buf, 3, radioNumber);
        break;

    case LR1121_MODE_FS:
        // 进入频率合成模式(快速切换的中间态)
        hal.WriteCommand(LR11XX_SYSTEM_SET_FS_OC, radioNumber);
        break;
    }
}

中断驱动的收发流程

┌─────────────────────────────────────────────────┐
│           CONTINUOUS RX STATE                   │
│  SetMode(LR1121_MODE_RX_CONT, SX12XX_Radio_All)│
└────────────┬────────────────────────────────────┘
             │ RX_DONE IRQ (DIO1 上升沿)
             ▼
┌─────────────────────────────────────────────────┐
│    RX ISR HANDLER                               │
│  - 从缓冲区读取数据包                            │
│  - 解码 RSSI/SNR                                │
│  - 调用 RXdoneCallback()                        │
└────────────┬────────────────────────────────────┘
             │ 应用层决定是否发送遥测
             ▼
┌─────────────────────────────────────────────────┐
│    TRANSMIT (TXnb)                              │
│  1. 编码 payload                                │
│  2. 写入 TX 缓冲区                              │
│  3. 命令: WRITE_BUFFER8_SET_TX (0x0704)         │
└────────────┬────────────────────────────────────┘
             │ TX_DONE IRQ
             ▼
┌─────────────────────────────────────────────────┐
│    TX ISR → TXdoneCallback()                    │
│    应用层调用 RXnb() 返回接收状态                │
└─────────────────────────────────────────────────┘

关键 SPI 命令

命令 Opcode 说明
SetRx 0x0209 进入接收模式
SetTx 0x020A 进入发送模式
SetFS 0x011D 进入频率合成模式
WriteBuffer+SetTx 0x0704 写缓冲区并发送
SetRxTxFallbackMode 0x0213 设置自动回落模式

2. FS 模式是什么?

Q: FS 是一种可以随时进入 TX 的 RX 模式吗?

不是的。FS (Frequency Synthesis) 模式既不收也不发,它是一种"预热空闲"状态。

FS 模式的实际含义

组件 状态 说明
PLL(锁相环) ✅ 已锁定 频率已稳定
晶振 ✅ 运行中 时钟正常
LNA(低噪声放大器) ❌ 未激活 不能接收
PA(功率放大器) ❌ 未激活 不能发送

简单理解:引擎已发动,但车还在空挡。

为什么用 FS 而不是其他模式?

模式 PLL 状态 切换到 TX/RX 速度 功耗
SLEEP 关闭 最慢(需重启 PLL) 最低
STDBY_RC 关闭 较慢
FS 已锁定 最快 中等
RX_CONT 锁定+LNA开 较高

FS 的优势:从 FS 切换到 TX/RX 只需激活 PA/LNA,省去 PLL 锁定时间(通常几十到几百微秒)。

在 ExpressLRS 中的实际用途

主要用于双射频分集时隔离另一个射频

// src/lib/LR1121Driver/LR1121.cpp:633-644
// 双射频模式下,如果射频1要发送
if (radioNumber == SX12XX_Radio_1)
{
    SetMode(LR1121_MODE_FS, SX12XX_Radio_2);  // 射频2进入FS,不接收自己的TX信号
}

状态切换示意

┌─────────┐
        │  SLEEP  │  ← 最省电,但唤醒慢
        └────┬────┘
             │ 唤醒
             ▼
        ┌─────────┐
        │  STDBY  │  ← 待机,PLL 未锁定
        └────┬────┘
             │ 启动 PLL
             ▼
        ┌─────────┐
        │   FS    │  ← PLL 已锁定,空闲态(不收不发)
        └────┬────┘
           ╱   ╲
    激活LNA     激活PA
         ╱       ╲
        ▼         ▼
   ┌────────┐  ┌────────┐
   │   RX   │  │   TX   │
   └────────┘  └────────┘

3. TX/RX 切换是手动还是自动的?

Q: 在单个 LR1121 模块下,发射和接收都是手动切换的吗?还是模块提供了特殊功能?

答案是:半自动。

芯片提供的硬件自动功能

RxTxFallbackMode(已使用)

在初始化时配置:

// src/lib/LR1121Driver/LR1121.cpp:132-135
// 7.2.5 SetRxTxFallbackMode
uint8_t FBbuf[1] = {LR11XX_RADIO_FALLBACK_FS};
hal.WriteCommand(LR11XX_RADIO_SET_RX_TX_FALLBACK_MODE_OC, FBbuf, ...);

效果:TX 完成或 RX 超时后,芯片硬件自动回落到 FS 模式。

实际的切换流程

┌────────────────────────────────────────────────────────────┐
│                      软件控制                               │
│  RXnb() ──────────────────────────► 进入 RX_CONT 模式      │
│  TXnb() ──────────────────────────► 进入 TX 模式           │
└────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌────────────────────────────────────────────────────────────┐
│                    硬件自动 (Fallback)                      │
│  TX 完成 ─────────────────────────► 自动进入 FS 模式       │
│  RX 超时/完成 ────────────────────► 自动进入 FS 模式       │
└────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌────────────────────────────────────────────────────────────┐
│                      软件控制                               │
│  ISR 回调通知应用层 ──────────────► 应用决定下一步操作      │
│  应用调用 RXnb()/TXnb() ──────────► 进入下一个模式         │
└────────────────────────────────────────────────────────────┘

总结

操作 由谁控制
进入 RX 模式 软件手动 调用 RXnb()
进入 TX 模式 软件手动 调用 TXnb()
TX/RX 完成后回到 FS 硬件自动 (Fallback 机制)
从 FS 进入下一个状态 软件手动 根据协议逻辑决定

不仅仅是 RX 时间大,而是遥测总吞吐量高。

绝对遥测包频率

模式 包速率 TLM Ratio 遥测频率
F1000 1000 Hz 1:128 7.8 包/秒
500Hz 500 Hz 1:128 3.9 包/秒
100Hz 100 Hz 1:32 3.1 包/秒
50Hz 50 Hz 1:16 3.1 包/秒

虽然 F1000 的 TLM ratio 看起来很稀疏 (1:128),但因为基础包速率是 1000Hz,绝对遥测频率反而更高。

遥测突发 (Telemetry Burst) 机制

// src/src/common.cpp:196-214
// 每 512ms 内可以连续发送多少个数据包
uint8_t TLMBurstMaxForRateRatio(uint16_t rateHz, uint8_t ratioDiv)
{
    constexpr uint32_t TELEM_MIN_LINK_INTERVAL_MS = 512U;
    unsigned retVal = TELEM_MIN_LINK_INTERVAL_MS * rateHz / ratioDiv / 1000U;
    if (retVal > 1) --retVal;
    return retVal;
}

F1000 计算:

burst = 512 * 1000 / 128 / 1000 = 4 - 1 = 3

100Hz 1:32 计算:

burst = 512 * 100 / 32 / 1000 = 1.6 → 1

实际下行带宽

// src/lib/tx-crsf/TXModuleParameters.cpp:434-436
uint32_t bandwidthValue = bytesPerCall * 8 * burst * hz / ratiodiv / (burst + 1);

F1000 带宽(假设 OTA4 每包 5 字节):

= 5 * 8 * 3 * 1000 / 128 / 4 = 234 bps ≈ 29 字节/秒

100Hz 带宽:

= 5 * 8 * 1 * 100 / 32 / 2 = 62 bps ≈ 7.8 字节/秒

总结

因素 说明
高包速率 1000Hz 基础速率,即使 TLM ratio 稀疏,绝对遥测频率也高
更大的 Burst 每 512ms 周期内可连续发 3 个数据包
FSK/FLRC 调制 空中时间短,每包间隔只需 1ms

5. TX 和 RX 如何实现时隙对齐?

Q: 发射机和接收机是如何协调发送和接收时隙对齐的?

这是 ExpressLRS 最核心的时序同步机制,采用 PFD (Phase Frequency Detector) 实现相位锁定。

整体架构:Tick-Tock 定时器

TX 和 RX 都有一个硬件定时器,产生交替的 TickTock 事件:

TX 定时器:   ──┬──Tock──┬──Tick──┬──Tock──┬──Tick──┬──
               │  TX包  │        │  TX包  │        │
               ▼        │        ▼        │        │
             发送数据   │      发送数据   │        │

RX 定时器:   ──┬──Tock──┬──Tick──┬──Tock──┬──Tick──┬──
               │        │   RX   │        │   RX   │
               │        │  处理  │        │  处理  │

关键:RX 需要将自己的 Tock 与 TX 数据包的到达时间对齐。

PFD 相位检测器

// src/lib/PFD/PFD.h
class PFD {
    uint32_t intEventTime;  // RX 本地 Tock 触发时间
    uint32_t extEventTime;  // 收到 TX 数据包的时间

    int32_t calcResult() {
        return extEventTime - intEventTime;  // 相位差
    }
};

相位锁定流程

1. RX 收到数据包 → PFDloop.extEvent(收包时间 + slack)
2. RX Tock 中断   → PFDloop.intEvent(当前时间)
3. 计算相位差    → RawOffset = extEvent - intEvent
4. 低通滤波      → Offset = LPF_Offset.update(RawOffset)
5. 调整定时器    → hwTimer::phaseShift() 或 FreqOffset++/--

频率和相位校正

// src/src/rx_main.cpp:607-645
void updatePhaseLock() {
    int32_t RawOffset = PFDloop.calcResult();
    int32_t Offset = LPF_Offset.update(RawOffset);      // 相位偏移
    int32_t OffsetDx = LPF_OffsetDx.update(变化率);     // 频率漂移

    if (RXtimerState == tim_locked) {
        // 已锁定:微调频率
        if (Offset > 0) hwTimer::incFreqOffset();  // 本地时钟太快
        if (Offset < 0) hwTimer::decFreqOffset();  // 本地时钟太慢
    } else {
        // 未锁定:大幅相位调整
        hwTimer::phaseShift(Offset >> 1);
    }
}

两种校正方式

状态 校正方式 说明
未锁定 phaseShift() 一次性大幅调整相位(最大 1/4 周期)
已锁定 FreqOffset++/-- 每周期微调 1μs 补偿晶振偏差

完整时序图

时间 →
        ┌────────┐         ┌────────┐         ┌────────┐
TX:     │ TX 包1 │         │ TX 包2 │         │ TX TLM │
        └────┬───┘         └────┬───┘         └────┬───┘
             │ 空中传播         │                  │
             ▼                  ▼                  ▼
        ┌────────┐         ┌────────┐         ┌────────┐
RX:     │ RX收包 │         │ RX收包 │         │ TX遥测 │◄── RX发送
        └────┬───┘         └────┬───┘         └────────┘
             ▼                  ▼
        PFD.extEvent      PFD.extEvent
             │                  │
        ┌────┴────┐        ┌────┴────┐
RX      │  Tock   │        │  Tock   │
Timer:  │ intEvent│        │ intEvent│
        └─────────┘        └─────────┘
             │                  │
             ▼                  ▼
        updatePhaseLock() - 调整定时器使 Tock 对齐包到达

遥测时隙协调

TX 和 RX 共享相同的 OtaNonce 计数器(通过 SYNC 包同步):

// TX 端 - src/src/tx_main.cpp:857
const bool nextIsTLM = (OtaNonce + 1) % ExpressLRS_currTlmDenom == 0;
if (nextIsTLM) {
    TelemetryRcvPhase = ttrpPreReceiveGap;  // 准备接收遥测
    Radio.RXnb();                            // 切换到 RX 模式
}

// RX 端 - src/src/rx_main.cpp:454
uint8_t modresult = OtaNonce % ExpressLRS_currTlmDenom;
if (modresult == 0) {
    // 发送遥测数据包
}

总结

机制 作用
PFD 测量 RX 本地时钟与 TX 数据包到达的相位差
LPF 滤波 平滑相位测量,减少抖动
phaseShift 初始连接时的粗调(一次性相位跳变)
FreqOffset 锁定后的细调(补偿晶振 ppm 偏差)
OtaNonce TX/RX 共享的计数器,决定哪个时隙是遥测
SYNC 包 TX 周期性发送,携带 OtaNonce 确保同步

6. 为什么 ELRS 不使用 AutoTxRx?

Q: ELRS 没有使用 AutoTxRx 的原因是这个特殊的遥控场景需要的上下行带宽不对称吗?

是的,但还有其他原因。

1. 上下行带宽不对称

上行 (TX→RX):  每个时隙都发包   → 1000 包/秒
下行 (RX→TX):  1:128 才发遥测   → 7.8 包/秒

如果用 AutoTxRx(TX 完自动进 RX 等响应),那 127/128 的时隙都在无意义地等待

2. 时隙类型多样

ELRS 的上行包有多种类型:

时隙0: RC 数据
时隙1: RC 数据
时隙2: SYNC 包(可能)
...
时隙127: RC 数据
时隙128: RC 数据 → 然后等遥测

AutoTxRx 是硬件自动的,无法区分"这个时隙要等响应"还是"这个时隙不用等"。

3. 双射频分集需要精确控制

// 发送时:一个射频 TX,另一个进入 FS 防止自干扰
if (radioNumber == SX12XX_Radio_1) {
    SetMode(LR1121_MODE_FS, SX12XX_Radio_2);
}

AutoTxRx 会让两个射频同时进入 RX,造成混乱。

4. FHSS 跳频时机

TX包 → 跳频 → TX包 → 跳频 → ... → TX包 → 等遥测(不跳频)

跳频发生在 TX 完成后、下一包开始前。AutoTxRx 自动进入 RX 就没有合适的时机切换频率。

5. LBT(部分法规区域)

在 EU CE 等法规区域,需要先"听"再"发":

听(RX) → 信道空闲? → 发(TX) → 等遥测(RX)

6. RxTxFallbackMode 已经够用

hal.WriteCommand(LR11XX_RADIO_SET_RX_TX_FALLBACK_MODE_OC,
                 LR11XX_RADIO_FALLBACK_FS);  // TX/RX 完成后自动回到 FS

这比 AutoTxRx 更灵活:软件决定下一步是进 RX 还是直接发下一包。

总结

因素 AutoTxRx 的问题
带宽不对称 大量时隙无意义等待
包类型多样 无法区分是否需要等响应
双射频分集 无法单独控制每个射频
FHSS 跳频 没有时机执行跳频
LBT 法规 无法实现"先听后发"
PFD 同步 需要精确控制定时器触发时机

7. SYNC 包会占用正常的数据时隙吗?

Q: SYNC 包会占用包频率吗?占用情况如何?

是的,SYNC 包会占用正常的 RC 数据时隙,但影响很小。

发送条件(三重限制)

// src/src/tx_main.cpp:547-558
// 条件1: 必须在"同步频率"上
FHSSonSyncChannel()

// 条件2: 距离上次 SYNC 要满足时间间隔
(now - SyncPacketLastSent > SyncInterval)

// 条件3: syncSlot 轮转机制
(syncSlot / 2) <= NonceFHSSresult

具体占用率

模式 连接后 SyncInterval 未连接 SyncInterval
F1000 5000ms 3ms
100Hz LoRa 5000ms 600ms

连接后:每 5 秒才发一个 SYNC 包,对 RC 数据几乎无影响。

未连接时:频繁发送 SYNC 以快速建立连接。

Sync Spam 机制(特殊情况)

在配置变更或连接建立时,会强制密集发送 SYNC:

#define syncSpamAmount 3                    // 普通 spam
#define syncSpamCounterAfterRateChange 10   // 速率变更后 spam

总结

状态 SYNC 对 RC 数据的影响
已连接稳定 极小 (~1包/5秒)
刚建立连接 短暂增加 (spam 3-10包)
未连接扫描 很大 (密集发送找 RX)
配置变更时 短暂增加 (spam 10包)

8. AutoTxRx 的工作原理是什么?

Q: AutoTxRx 是什么策略?如果两边都使用 AutoTxRx,会有自动避让吗?

AutoTxRx 的两种模式

不是"不发送就接收",而是两种有限状态机模式:

AutoRX 模式(TX 后自动 RX)

TX 发送 → 可配置延迟 → 进入 RX → 收到1个包或超时 → 回到 Standby

AutoTx 模式(RX 后自动 TX)

RX 接收 → 收到包 → 可配置延迟 → 进入 TX 发送预装数据 → 回到 Standby

两边都使用 AutoTxRx 会怎样?

没有自动避让机制,会靠"碰撞+重试"

场景1: A 和 B 都配置为 AutoRX(TX 后等响应)

时间 →
A: [TX]──delay──[RX等待]──────────[超时]
B: [TX]──delay──[RX等待]──────────[超时]
        ↑
     两边同时发,谁也收不到对方
场景2: A 配置 AutoRX,B 配置 AutoTx(主从模式)

时间 →
A: [TX]──delay──[RX等待]──────[收到B响应]
B: ────[RX]────[收到A]──delay──[TX响应]

这种模式可以工作

高频发送的碰撞问题

情况 结果
两边都 AutoRX 死锁:都在发,没人听
两边都 AutoTx 死锁:都在等,没人发
A: AutoRX, B: AutoTx 主从模式:可以工作
双方都无协调高频发送 碰撞率 ≈ 发送占空比

解决方案:需要上层协议

AutoTxRx 只是硬件便利功能,不提供冲突避免:

协议层 机制
TDMA 时分多址,分配固定时隙(ELRS 的做法)
LoRaWAN RX1/RX2 窗口,网关调度
CSMA/CA 发送前监听,随机退避
主从轮询 主机发,从机只响应

9. 速率为什么会影响通信距离?

Q: 底层是配置了模块不同的调制方法还是校验方法?

核心是调制参数的综合变化,不仅仅是校验。

不同速率的底层配置对比

900MHz LoRa 模式参数变化

速率 SF BW CR 灵敏度 TOA (μs)
250Hz SF5 500kHz 4/8 -111 dBm 3216
100Hz SF7 500kHz 4/7 -117 dBm 8770
50Hz SF8 500kHz 4/7 -120 dBm 18560
25Hz SF9 500kHz 4/7 -123 dBm 29950

FSK 高速模式对比

速率 调制 比特率 BW 灵敏度 TOA (μs)
F1000 GFSK 300 kbps 467kHz -101 dBm 658

核心参数解析

SF (Spreading Factor) - 扩频因子

这是影响距离最关键的参数

SF = 每个符号携带的 chirp 数量 = 2^SF chips/symbol

SF5: 2^5 = 32 chips → 快速但需要强信号
SF9: 2^9 = 512 chips → 慢速但能解调极弱信号

原理

  • SF 越大,同样的信息被"拉伸"得越长
  • 能量分散在更长的时间上
  • 接收端有更多时间积分能量,从噪声中提取信号

CR (Coding Rate) - 编码率

CR 4/5: 每 4 位有效数据加 1 位校验 (20% 冗余)
CR 4/7: 每 4 位有效数据加 3 位校验 (75% 冗余)
CR 4/8: 每 4 位有效数据加 4 位校验 (100% 冗余)

信号处理增益

Processing Gain (dB) = 10 × log10(BW / BitRate)

SF5 (250Hz): PG ≈ 21 dB
SF9 (25Hz):  PG ≈ 33 dB

增益差 = 12 dB ≈ 4 倍距离

灵敏度与距离的关系

Friis 传输公式:

链路预算 = 发射功率 - 接收灵敏度 - 衰落余量

25Hz (SF9): Tx 100mW (20dBm), Rx -123dBm
  链路预算 = 20 - (-123) - 10 = 133 dB
  对应距离 ≈ 20-30 km (空旷环境)

F1000 (FSK): Tx 100mW (20dBm), Rx -101dBm
  链路预算 = 20 - (-101) - 10 = 111 dB
  对应距离 ≈ 1-2 km (空旷环境)

差距:22 dB 的灵敏度差异 ≈ 10-15 倍距离差

总结:速率 vs 距离的权衡

参数 高速模式 (F1000) 低速模式 (25Hz)
调制方式 FSK LoRa CSS
SF N/A (FSK) SF9
符号时间 3.3 μs 1024 μs
灵敏度 -101 dBm -123 dBm
TOA 658 μs 29950 μs
距离 ~2 km ~30 km
延迟 1 ms 40 ms

核心机制

  1. 调制方式改变:FSK vs LoRa
  2. 扩频因子增大:SF5→SF9 (LoRa 内部)
  3. 编码冗余增加:CR 4/8 vs 4/7
  4. 空中时间延长:658μs → 29950μs (45倍)

不是简单的校验增加,而是整个物理层调制方案的根本性改变。


参考资料


本文基于 ExpressLRS 源代码分析,代码版本基于 2024/2025 年的 master 分支。


Comment