ExpressLRS跳频机制源码解读

ExpressLRS跳频机制解析

源码信息

本文分析基于 ExpressLRS 开源项目,具体信息如下:

说明:本文旨在解读跳频机制的实现原理,所有代码和分析均来自上述开源项目。读者可访问仓库获取最新源码、提交问题和参与开发。

一、引言:为什么要跳频?

在无线通信中,跳频(Frequency Hopping Spread Spectrum,简称FHSS)是一种重要的抗干扰技术。它通过让收发双方按照预定序列在多个频道间快速切换,实现以下目标:

  • 抗干扰:避免长期停留在某个被干扰的频道

  • 提高可靠性:分散通信风险,单一频道故障不影响整体链路

  • 增强安全性:未经授权的设备难以跟踪通信频率

ExpressLRS(ELRS)作为开源高频宽遥控链路系统,其跳频机制的实现既高效又精巧。

二、整体架构:Tx与Rx如何同步跳频?

ELRS的跳频机制基于“时隙同步”原则:Tx(发射端)和Rx(接收端)按照相同的序列和节奏切换频率。

 ┌─────────────┐    同步频道     ┌─────────────┐
 │     Tx      │───────────────>│     Rx      │
 │  发射端     │                │  接收端     │
 └─────────────┘                └─────────────┘
        │                            │
        ├── 1. 基于相同种子生成序列 ──┤
        ├── 2. 同时从索引0开始计时 ──┤
        └── 3. 按相同间隔跳至下一频 ──┘

关键同步要素:

  • 相同种子:基于设备UID生成

  • 相同序列:256个频道索引的排列

  • 相同节奏:每N个数据包跳频一次(FHSShopInterval)

三、Tx侧跳频实现详解

3.1 初始化阶段:奠定跳频基础

Tx的初始化在tx_main.cppsetup()函数中完成,主要步骤包括:

 void setup() {
     // 1. 检查硬件配置
     if (setupHardwareFromOptions()) {
         // 2. 读取设备唯一标识符
         setupBindingFromConfig();
         
         // 3. ★核心步骤:初始化跳频序列
         // 基于UID生成随机种子,创建伪随机跳频序列
         FHSSrandomiseFHSSsequence(uidMacSeedGet());
         
         // 4. 注册无线电回调函数
         Radio.RXdoneCallback = &RXdoneISR;
         Radio.TXdoneCallback = &TXdoneISR;
         
         // 5. 设置初始频率(同步频道)
         Radio.currFreq = FHSSgetInitialFreq();
         
         // 6. 初始化射频模块
         Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
         
         // 7. 启动定时器控制时隙
         hwTimer::init(nullptr, timerCallback);
     }
 }

3.2 跳频序列生成算法

跳频序列是整套机制的核心。ELRS采用“分块随机置换”算法,确保序列既随机又包含必要的同步点。

 void FHSSrandomiseFHSSsequence(const uint32_t seed) {
     // 1. 获取频域配置(如2.4GHz ISM频段)
     FHSSconfig = &domains[firmwareOptions.domain];
     
     // 2. 计算同步频道位置(通常位于频段中部)
     sync_channel = (FHSSconfig->freq_count / 2) + 1;
     
     // 3. 计算频道间隔(带缩放因子保证精度)
     freq_spread = (FHSSconfig->freq_stop - FHSSconfig->freq_start) 
                   * FREQ_SPREAD_SCALE / (FHSSconfig->freq_count - 1);
     
     // 4. 构建跳频序列
     FHSSrandomiseFHSSsequenceBuild(seed, FHSSconfig->freq_count, 
                                    sync_channel, FHSSsequence);
 }

序列构建算法的巧妙之处:

  1. 分块结构:将256个位置分为若干块,每块大小等于频道总数

  2. 固定同步点:每块的第0位置固定为同步频道,确保定期回归

  3. 块内随机化:每块内部的其他位置随机交换,实现伪随机跳频

 // 简化版算法逻辑示意
 for (每个块) {
     块[0] = 同步频道;          // 固定为同步点
     块[同步频道] = 0;          // 避免冲突
     
     for (块内其他位置) {
         与同块内随机位置交换;   // 实现随机化
     }
 }

3.3 跳频触发时机

Tx在每次发送完成后判断是否需要跳频:

 void TXdoneISR() {
     if (busyTransmitting) {
         // 检查是否为跳频时机
         HandleFHSS();
         busyTransmitting = false;
     }
 }
 ​
 void HandleFHSS() {
     // 计算下一个包是否应该跳频
     uint8_t modresult = (OtaNonce + 1) % ExpressLRS_currAirRate_Modparams->FHSShopInterval;
     
     // modresult == 0 表示到达跳频点
     if (!InBindingMode && modresult == 0) {
         // 切换到下一个频率
         Radio.SetFrequencyReg(FHSSgetNextFreq());
     }
 }

关键变量说明:

  • OtaNonce:数据包计数器,每个时隙递增

  • FHSShopInterval:跳频间隔,如设置为47表示每48个包跳频一次

  • FHSSptr:当前在跳频序列中的位置索引

四、Rx侧跳频实现详解

4.1 初始化:与Tx保持同步

Rx的初始化流程与Tx高度一致,确保双方从相同的起点开始:

 void setup() {
     // 与Tx完全相同的初始化步骤
     FHSSrandomiseFHSSsequence(uidMacSeedGet());
     setupRadio();
     
     // Rx特有的定时器初始化
     hwTimer::init(HWtimerCallbackTick, HWtimerCallbackTock);
 }

4.2 双保险跳频触发机制

Rx采用“中断+定时器”双触发机制,确保跳频绝不遗漏:

机制一:数据包接收完成触发(RXdoneISR)
 bool RXdoneISR(SX12xxDriverCommon::rx_status const status) {
     if (ProcessRFPacket(status)) {
         // 尝试处理跳频
         didFHSS = HandleFHSS();
         return true;
     }
     return false;
 }
机制二:定时器后备触发(HWtimerCallbackTock)
 void HWtimerCallbackTock() {
     // 如果RXdoneISR没有处理跳频,则在这里补上
     if (!didFHSS) {
         HandleFHSS();
     }
     didFHSS = false;  // 重置标志
 }

这种设计确保了即使某个数据包丢失,Rx仍能通过定时器保持跳频同步。

4.3 定时器的双重职责

Rx定时器有两个回调函数,相位相差180度:

回调函数触发时机主要职责
HWtimerCallbackTick数据包接收中期更新时隙计数、发送控制数据
HWtimerCallbackTock数据包接收间隙跳频处理、周期性任务
 void HWtimerCallbackTick() {
     OtaNonce++;           // ★关键:推进时隙计数器
     SendRCdataToRF();     // 发送控制数据到射频
     // ...其他中间处理
 }
 ​
 void HWtimerCallbackTock() {
     if (!didFHSS) {
         HandleFHSS();     // 后备跳频触发
     }
     // ...周期性维护任务
 }

五、关键技术细节解析

5.1 设备UID到跳频种子的转换

跳频序列的随机性来源于设备唯一标识符(UID):

 uint32_t uidMacSeedGet() {
     // 提取UID的最后4字节,并与版本号异或
     const uint32_t macSeed = ((uint32_t)UID[2] << 24) + 
                              ((uint32_t)UID[3] << 16) +
                              ((uint32_t)UID[4] << 8) + 
                              (UID[5] ^ OTA_VERSION_ID);
     return macSeed;
 }

设计要点:

  • 每个设备有唯一的跳频序列

  • 固件版本更新(OTA_VERSION_ID变化)会改变序列,避免版本间干扰

  • 种子计算高效,适合在启动时快速执行

5.2 频率计算方法

频道索引到实际频率的转换:

 static inline uint32_t FHSSgetNextFreq() {
     // 1. 前进到序列中的下一个位置
     FHSSptr = (FHSSptr + 1) % FHSSgetSequenceCount();
     
     // 2. 获取频道索引
     uint8_t channelIndex = FHSSsequence[FHSSptr];
     
     // 3. 计算实际频率
     // 公式:起始频率 + (间隔 × 索引 / 缩放因子) - 频率修正
     return FHSSconfig->freq_start + 
            (freq_spread * channelIndex / FREQ_SPREAD_SCALE) - 
            FreqCorrection;
 }

5.3 伪随机数生成器

ELRS使用线性同余生成器(LCG)产生随机数:

 uint16_t rng(void) {
     const uint32_t m = 2147483648;  // 2^31
     const uint32_t a = 214013;      // 乘数
     const uint32_t c = 2531011;     // 增量
     seed = (a * seed + c) % m;      // LCG公式
     return seed >> 16;              // 取高16位作为随机数
 }

特点:

  • 算法简单,计算速度快

  • 周期足够长(2^31),满足跳频需求

  • 确定性:相同种子产生相同序列,保证Tx/Rx同步

六、跳频机制总结

6.1 设计亮点

  1. 完全同步:Tx和Rx基于相同算法和种子生成完全一致的跳频序列

  2. 容错机制:Rx端双重触发确保跳频不遗漏

  3. 灵活可调:通过FHSShopInterval参数控制跳频速度

  4. 资源高效:算法复杂度低,适合嵌入式平台

6.2 工作流程总结

 启动阶段:
 1. 读取设备UID
 2. 生成随机种子
 3. 构建跳频序列(256个频道索引)
 4. 设置初始频率(同步频道)
 ​
 运行阶段(Tx):
 1. 发送数据包
 2. 包计数器+1
 3. 检查是否到达跳频点
 4. 若到达,切换到序列中的下一个频率
 ​
 运行阶段(Rx):
 1. 接收数据包(触发跳频检查)
 2. 定时器到期(后备跳频检查)
 3. 保持与Tx完全同步的频率切换

结语

ELRS的跳频机制展示了一个精巧的嵌入式无线通信系统设计范例。它通过简洁的算法、高效的实施和鲁棒的同步机制,在有限的硬件资源上实现了可靠的跳频通信。

这种设计不仅适用于遥控模型领域,也为其他需要可靠无线通信的嵌入式应用提供了参考。通过深入理解这套机制,开发者可以更好地调试ELRS系统,甚至借鉴其设计思想用于自己的项目中。

致谢:感谢所有ExpressLRS开源项目的贡献者,他们的工作使这份技术解读成为可能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值