welle.io源码深潜:OFDM定时同步、粗频偏校正与相位参考跟踪的实现原理

📅 发布时间:2026/8/23 11:32:24
welle.io源码深潜:OFDM定时同步、粗频偏校正与相位参考跟踪的实现原理 welle.io源码深潜OFDM定时同步、粗频偏校正与相位参考跟踪的实现原理【免费下载链接】welle.ioA DAB/DAB Software Defined Radio (SDR)项目地址: https://gitcode.com/gh_mirrors/we/welle.iowelle.io 是一款开源的 DAB/DAB 软件定义无线电SDR接收机能用 RTL-SDR、Airspy、SoapySDR 等设备接收数字音频广播。它最核心的解码环节——OFDM 定时同步、粗频偏校正与相位参考跟踪——都集中在一个类里完成。本文带你从源码层面拆解这套锁定信号的完整流水线。先搞懂为什么 DAB 必须先同步DAB 信号由连续的 OFDM 帧组成每个帧包含一个空符号null symbol和若干 OFDM 符号其中第一个符号是相位参考符号PRS。在 Mode I 中关键参数是见 src/backend/dab-constants.cppT_u 2048有用 OFDM 样本数FFT 长度T_s 2552含循环前缀的完整符号长度T_null 2656空符号长度信号沉默的间隙carrierDiff 1000载波间隔 1 kHz接收端的难题是采样流里你不知道每个符号从哪个样本开始也不知道本机振荡器偏了多少 Hz。welle.io 用两级粗同步 逐帧精跟踪的策略解决它主战场在 src/backend/ofdm-processor.cpp 的run()方法。第一步捕捉空符号的电平跌落OFDM 定时同步的第一级非常朴素——找凹陷。空符号期间不发射信号所以信号包络会周期性跌落。在 src/backend/ofdm-processor.cpp 中接收机维护一个 50 样本的滑动窗口envBuffer统计当前平均强度currentStrength并与长期平均电平sLevel比较当前强度低于0.50 * sLevel→ 判定进入空符号找到跌落起点之后等待强度回升到0.75 * sLevel以上 → 空符号结束下一个 PRS 符号即将开始若超过T_F或T_null 50个样本仍未满足判定同步失败goto notSynced重来。这一步只做到大概知道符号边界精确位置交给下一步。第二步用 PRS 相关峰精确定位第一个样本PRS 的内容是确定性的——每个载波上的相位由传输模式Mode I/II/IV查表给出表源自 ETSI EN 300 401 标准见 src/backend/phasetable.cpp 中的modeI_table等。既然答案已知就可以做相关检测。PhaseReference::findIndex 的流程是教科书式的频域相关对收到的T_u个样本做 FFT频域与预存的 PRS 参考谱refTable做共轭相乘等价于时域卷积IFFT 回到时域得到一个冲激响应曲线曲线峰值的位置就是 PRS 第一个样本的精确位置。有意思的是峰选策略可配置共有三种模式src/backend/radio-receiver-options.h它们各自权衡的是多径信号下锁在哪一个峰模式思路适用场景StrongestPeak直接选最强峰须 ≥3 倍均值旧算法简单但可能被多径干扰峰骗到EarliestPeakWithBinning20 样本分箱保留 4 个最强峰取最早者参考 EBU TR 024避免锁在延迟多径峰上ThresholdBeforePeak默认滑动窗口找相关曲线最早超过半峰阈值处兼顾稳健性与多径容忍相关曲线同时被送到前端的冲激响应图onNewImpulseResponse所以你在 welle.io 专家界面里看到的延迟剖面其实就是这一步的副产品 粗频偏校正频谱平移了几个载波时钟偏差会让整个频谱偏移。由于 PRS 频谱形状已知偏移量 频谱移动了多少个载波间隔。welle.io 在 OFDMProcessor::processPRS 中提供三种估计方法FreqsyncMethodGetMiddle滑动窗求 K 个载波的能量和找能量中心偏离T_u/2多少getMiddleCorrelatePRS比较相邻载波相位差序列与参考序列的相关性搜索范围 ±36 个载波PatternOfZeros默认正确频谱中特定载波位置的相位差恰好是 0 或 ±π扫描失配最小的位置。得到偏移后累加到coarseCorrector并有两道保险搜索范围限制在 ±35 kHz 内超出即复位判定失锁若 FIC 的 CRC 正确率已超过 50%冻结粗校正——避免短暂信号抖动把本来好好的接收打乱src/backend/ofdm-processor.cpp。校正的物理实现是一个数字本振NCO构造一张INPUT_RATE点的单位复指数表oscillatorTable每读一个样本就把相位指针localPhase推进coarseCorrector fineCorrector步并乘以对应复指数getSamples。这样在 Hz 精度上抵消频率误差且对文件回放、不同采样率的输入都通用。时钟精度好的接收机还可以直接disableCoarseCorrector加速锁定。细频偏跟踪循环前缀的自相关积分粗校正后还剩残余频偏利用**循环前缀guard interval**持续跟踪循环前缀是同一符号尾部T_g个样本的拷贝所以相邻T_u个样本的相位差直接暴露了频偏。run() 的 Data_symbols 段 在一帧内逐样本累加buf[i] * conj(buf[i - T_u])帧末取其相位并做低通积分fineCorrector 0.1 * arg(FreqCorr) / M_PI * (params.carrierDiff / 2);0.1 的系数让它平滑收敛一旦fineCorrector超过 ±半个载波间隔就进位折叠进coarseCorrectorsrc/backend/ofdm-processor.cpp始终保持细校正器在小范围线性区内工作。相位参考跟踪差分解调让慢漂移自动消失最后一级在 src/backend/ofdm-decoder.cpp 里也是 DAB 解调最优雅的设计PRS 处理processPRS对符号 0 做 FFT把结果整份存为phaseReference顺带估计 SNR带内载波能量 vs 带外噪声数据符号处理decodeDataSymbol→ofdm-decoder.cpp跳过T_g个循环前缀样本后做 FFT然后对每个载波做差分解调const DSPCOMPLEX r1 fft_buffer[index] * conj(phaseReference[index]); phaseReference[index] fft_buffer[index];当前载波乘以上一个符号同一位置载波的共轭再更新参考。这一步同时消除了残余载频偏移两个符号间只积累微小相位差乘积后几乎归零和逐符号的公共相位噪声。解码得到的r1直接映射为软比特I/Q 归一化到 ±127送往 FIC/MSC 处理链。注意正因为做了差分解调phaseReference的初值必须来自 PRS——如果某帧丢失同步这个参考链就断了一次这也是 welle.io 对同步状态如此敏感的原因。一张图串起来完整同步流水线空符号检测→ 50 样本滑动平均找电平跌落PRS 相关→ FFT 频域相关 峰选策略锁定符号首样本粗频偏校正→ 对 PRS 频谱做载波级平移估计±35 kHz 内逐样本 NCO 补偿→ 粗/细校正量在时域复指数上即时生效细频偏跟踪→ 循环前缀自相关0.1 系数低通积分差分解调→ 符号间相位参考跟踪残余漂移自动抹平。延伸阅读核心源码导航定时同步与频率校正主循环src/backend/ofdm-processor.cppPRS 相关检测与 FFT 窗口策略src/backend/phasereference.cpp各模式 PRS 相位表ETSI 标准表src/backend/phasetable.cpp差分解调与软比特输出src/backend/ofdm-decoder.cpp同步算法配置项可切换的峰选/频同步策略src/backend/radio-receiver-options.h各模式帧参数T_u/T_s/T_null 等src/backend/dab-constants.cpp这套先粗后细、逐级收紧的同步设计是几乎所有 OFDM 接收机Wi-Fi、LTE、DAB的通用范式而 welle.io 把它写得异常清晰——读透这一个处理器你对 SDR OFDM 接收机就有完整的心理模型了 【免费下载链接】welle.ioA DAB/DAB Software Defined Radio (SDR)项目地址: https://gitcode.com/gh_mirrors/we/welle.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考