OFDM分组检测:算法、仿真与FPGA实现详解
去年调OFDM接收机的时候我被分组检测折腾得够呛。频谱仪上明明看到信号进来了接收链路却一点反应没有把门限调低一点噪声又把检测器弄得疯狂误触发FFT窗口切得七零八落解调出来的星座图完全没法看。后来把分组检测这一块的原理从头捋了一遍才发现问题全出在一些看起来不起眼的细节上。分组检测packet detection是OFDM接收机同步的第一步任务就一句话判断数据包什么时候开始。后级所有符号定时、载波频偏估计、信道估计全都要建立在“检测到了分组”这个前提上。这篇文章把我调同步链路的经验整理成一份可直接参考的记录从算法原理讲到MATLAB/Simulink仿真再到FPGA落地覆盖分组检测的选型、参数和排错方法适合正在跟OFDM同步较劲的工程师、研究生和硬件开发者。1. 分组检测为什么能卡住整条OFDM同步链路1.1 OFDM接收机同步要解决的四件事OFDM接收机的同步不是单一算法而是串在一起的一组任务。按照处理顺序大概是这样分组检测回答“数据包来了没有、大概从哪个时刻开始”符号定时确定FFT窗口的精确起点载波频偏估计与校正补偿收发端本振频率差采样钟同步消除收发端ADC采样时钟的偏差这四步是层层递进的关系。分组检测在最前面它的输出决定后面所有模块什么时候启动。如果分组检测给出的时刻偏了几个采样点后面的符号定时还能拉回来但要是在没有信号的时候报了一个“有包”后面的频偏估计和信道估计就会用纯噪声去算结果全部跑飞整条链路要等下一个包才可能恢复。所以很多接收机里分组检测模块往往还带一个触发信号用来复位后级的频偏估计累积器保证每次捕获都从干净状态开始。1.2 分组检测和符号定时的边界很多人会把分组检测和符号定时混在一起包括我刚接触OFDM接收机时也犯过这个迷糊。它们最大的区别在于精度要求。分组检测是粗同步允许有若干个采样点的误差它只需要判断“包来了没有、大致起点在哪”。打个比方就像比赛开始的发令枪裁判先听到响声不需要立刻精确到毫秒计时。符号定时则是细同步必须把FFT窗口起点对准到循环前缀允许的范围内差了一个采样点子载波之间的正交性就会被破坏带来符号间干扰ISI和子载波间干扰ICI。这个边界在工程里很重要。分组检测阶段如果过度追求精度反而危险因为低信噪比下越精细的估计抖动越大。正确的做法是先粗捕获、锁定大概位置然后启动后级的精同步模块把精度留给专门的模块去处理。1.3 OFDM为什么对同步误差这么敏感OFDM的核心是多载波并行传输子载波间隔做得很小。同样是1kHz的频偏单载波系统可能只是信噪比损失几个dBOFDM却会让子载波间的正交性被彻底破坏解调性能断崖式下降。符号定时也一样OFDM虽然有循环前缀CP做保护但一旦FFT窗口偏差超过CP长度ISI和ICI会同时出现而且无法通过均衡器完全消除。所以在OFDM接收机里同步模块占的逻辑资源和设计工作量往往比信道均衡还要大。分组检测作为第一环就像盖房子的地基地基没定位准后面砌得再漂亮也是白搭。这也是我为什么建议做OFDM接收机的朋友一定要先把分组检测这一级吃透再往下推频偏估计和信道估计顺序反了只会越调越乱。2. 分组检测算法怎么选从能量检测到Schmidl-Cox自相关2.1 能量检测简单但工程上难用分组检测最朴素的做法就是能量检测计算接收信号能量连续超过门限就认为有分组到达。这个思路直白实现也简单但在实际OFDM接收机里它有三个绕不开的问题。第一是门限跟AGC强相关。接收机前端的自动增益控制会根据信号强弱调整增益信号强时增益压低信号弱时增益抬高。结果就是“有没有信号”不能简单用绝对能量判断门限跟着AGC变来变去很难稳定。第二是噪声能量也在变化温度、带宽、干扰都会影响噪声底纯噪声时能量也可能冲高。第三是能量检测区分不了信号和同频干扰来一束窄带干扰同样能触发误检。所以能量检测很少作为OFDM分组检测的主力方案但它经常被用作“能量唤醒”告诉接收机“可能有东西来了”真正判决还是交给后面的检测器。2.2 双滑动窗口能量比检测双滑动窗口算法是对能量检测的一个改进核心思路是比能量而不是比绝对能量。接收机维护两个滑动窗口一个窗口统计当前信号的能量另一个窗口统计延迟了L个采样点的历史能量。判决变量取这两个窗口能量之比。当没有数据包时两个窗口里都是噪声比值接近1。当数据包前沿进入前一个窗口、而后一个窗口还停留在噪声区时比值会明显抬高超过门限就判定分组开始。它的好处是解决了AGC的影响因为分子分母同时受AGC缩放比值不变。坏处是低信噪比下比值抖动比较大需要较长的窗口做平均而窗口一长检测延迟也会变大。另外在连续传输模式下两个窗口都只含信号时比值又会掉回1附近所以它更适合突发分组场景。2.3 自相关检测利用前导的重复结构目前用得最多的还是自相关检测核心思路是在数据包最前面放一段有重复结构的训练序列。接收端把接收信号和延迟D个采样点的副本做共轭相关如果这段信号正好和训练序列的重复结构匹配相关值就会很大如果是噪声相关值接近0。然后除以窗口能量做归一化得到0到1之间的判决变量。最经典的实现是Schmidl-Cox算法它的检测变量对载波频偏不敏感、对AGC不敏感工程上非常稳。三种算法的对比如下算法判决依据优点主要问题能量检测绝对能量超过门限实现最简单对AGC和噪声底敏感误检漏检都难控制双滑动窗口能量比前后窗口能量比值不受AGC缩放影响低SNR下抖动大检测延迟和窗口长度绑定自相关检测延迟相关的归一化变量抗频偏、抗AGC鲁棒性好需要发送端插入训练序列存在平台效应选型建议很明确突发OFDM系统只要有前导设计优先选自相关没有前导才考虑双滑动窗口能量检测最多做唤醒不建议当最终判决。我自己做项目时除非是极低功耗、极低复杂度的场景否则一律上自相关。3. Schmidl-Cox自相关检测的原理拆解重复结构、判决变量与频偏免疫3.1 训练序列的重复结构从哪来Schmidl-Cox算法的前提是发送端的前导符号在时域上有重复结构。最早的做法是发送一个长度为N的OFDM符号前半段和后半段在时域上完全相同也就是形成[A A]的结构其中A的长度LN/2。在OFDM系统里这个前导可以直接由频域PN序列经过IFFT生成。但这里有个坑生成前导时不能简单地拿一个普通OFDM符号加上循环前缀来充当训练序列因为CP会破坏时域重复结构。正确做法是设计一个本身就是重复结构的时域符号或者把CP也纳入重复设计的一部分。否则接收端的自相关窗口怎么滑动都对不上理想的峰值位置。3.2 判决变量P(d)、R(d)和M(d)的定义假设接收信号是r[n]分组检测器在每个候选起点d计算三样东西延迟相关P(d)就是把r[d..dL-1]和r[dL..d2L-1]逐点共轭相乘再累加。这个值在d对准训练序列起点时最大。能量R(d)统计第二段窗口的接收能量用来归一化。归一化判决变量M(d)数学形式是M(d)|P(d)|²/R(d)²。如果d刚好对准训练序列起点P(d)的模值达到最大R(d)是训练序列后半段的能量两者量级匹配M(d)接近1。如果d落在纯噪声区P(d)的实部和虚部是随机相消的模值很小R(d)是噪声能量M(d)接近0。于是分组检测就变成了一个简单的门限判决问题M(d)超过门限就认为分组到达。3.3 为什么频偏不影响判决变量这是自相关检测最核心的优势也是它能在工程里存活这么多年的原因。存在载波频偏Δf时接收信号相对于发送信号会乘上一个随时间旋转的相位。因为训练序列前后两半完全重复它们在同一时刻的采样点之间的相位差是固定值等于2πΔf·L·T_s。P(d)里每一项都是两个相隔L的采样点共轭相乘所以每一项都会带上这个固定相位旋转但取模之后相位信息就被去掉了。而R(d)是纯能量不受相位影响。结果就是不管频偏有多大M(d)的幅度基本不变。不过要记住一点P(d)的相位里存着频偏信息后级的粗频偏估计可以拿它来计算。所以设计实现时不要把相位丢掉自相关检测不只是检测它还顺带为频偏估计铺好了路。3.4 平台效应和它的工程意义因为训练序列存在重复结构当滑动窗口完全进入训练序列范围内连续多个d的M(d)都会很高形成一个平台而不是一个尖锐的峰值。平台效应在精确符号定时里是个麻烦因为平台中点不好找对噪声敏感。但在分组检测阶段平台反而相当于给了你一个宽裕的捕获范围只要门限设得合理任何一个落在平台上的点都可以作为“分组已到达”的触发时刻误检概率不会因为窗口偏了几个点就急剧升高。如果实在不喜欢平台可以参考Minn和Park提出的改进前导结构用不同的重复模式把平台压成尖峰。代价是训练序列设计更复杂后级频偏估计范围也会受影响。我在突发通信项目里一般保留平台把精确定时交给后续模块链路反而更稳。4. 参数设置的工程经验门限、窗口长度、仿真链路怎么搭4.1 窗口长度L的取值逻辑L直接对应训练序列重复半段的长度在OFDM里通常等于FFT点数的一半例如N64时L32N256时L128。L越大自相关积累的点数越多噪声被平均得越充分判决变量在低信噪比下的方差越小。但L越大训练序列的总开销也越大帧效率下降。工程上选择L一般是在可接受的训练开销内尽量选大。但有一条铁律L必须和发送端训练序列的半段长度严格一致。差一个点相关增益就会损失检测灵敏度明显下降。我在实际项目中踩过这样的坑前导符号按N128设计但接收端某版代码把L写成了64结果检测变量在信噪比20dB以上才能稳定触发一开始还以为是AGC问题查了半天才发现是窗口长度不匹配。这类问题用代码review很难发现最好的办法是直接把发送端的训练序列时域波形导出来和接收端窗口长度对照一遍。4.2 归一化门限的设定方法理论上M(d)在信号段接近1噪声段接近0门限取0.5看着挺合理。但实测低信噪比下M(d)的峰值会明显低于1可能只有0.3到0.4而且噪声段的M(d)也有起伏特别是在强多径环境下。我的做法是先在仿真里扫一遍M(d)的统计特性把不同SNR下的峰值分布、噪声底分布都统计出来画直方图然后选一个让两类分布重叠概率最低的门限再留3dB左右的裕量。比如峰值集中在0.6噪声底集中在0.1那门限取0.25到0.3比较稳如果峰值只有0.35噪声底在0.1门限就得放到0.2附近。门限设得太高会漏检尤其在AGC还没稳定、前几个符号幅度偏小的时候设得太低则会在噪声和干扰上频繁误检导致后级同步被反复触发。记住一个原则门限是用统计结果定出来的不是拍脑袋拍出来的。4.3 AGC建立期和多径的影响怎么处理接收机开机或者切换增益的瞬间AGC需要一段时间收敛。在这段时间里信号幅度可能被压得很低也可能被放得很大分组检测的判决变量会跟着明显波动。工程上有两个处理办法一是在AGC锁定之前禁止分组检测用一个锁定标志去门控检测模块二是在检测算法里加一个最小能量预判能量太低就直接跳过不参与自相关判断。多径的影响要复杂一些。多径会把训练序列的能量在时间上铺开自相关平台的边缘变得模糊时延扩展接近CP长度时M(d)的峰值会下降平台也更容易出现凹陷。这类环境下门限不能设得太贴近峰值最好在平台范围内选一个相对稳健的触发点。4.4 仿真链路怎么搭才能提前暴露问题建议先用浮点仿真把整个算法跑通搭建一条可复现的基带链路随机比特流、QAM符号映射、插入带重复结构的训练序列、IFFT、加循环前缀、过信道、加噪声最后送到分组检测模块。这样一个链路的好处有三个能随时导出中间信号观察M(d)的实际波形能方便地控制SNR、频偏、多径参数还能用同一套信号去验证不同的算法和参数。把仿真跑通后再转定点同时要注意浮点和定点判决变量的差异。定点化之后M(d)的动态范围变小噪声底的“地板”会抬高原来在浮点下成立的门限到定点下可能失效必须用定点模型重新标定。这一步偷懒的话硬件出来大概率要返工。5. MATLAB/Simulink实测核心代码、波形观察与典型踩坑5.1 MATLAB核心实现与复杂度优化自己写一个分组检测函数其实不难关键是用滑动累积把复杂度降下去。朴素写法是每个滑动位置都重新算L个点的相关时间复杂度O(NL)数据一长就等得人心烦。滑动累积的思路是先算第一个位置之后每移动一个点从累积值里减去移出的采样点贡献加上移入的采样点贡献总复杂度降到O(N)。代码我放在下面用的是延迟自相关形式窗口长度L、门限threshold作为参数传入function [detected, metric] packet_detect(rx, L, threshold) N length(rx); if N 2*L detected []; metric []; return; end Metric zeros(1, N-2*L1); P sum(conj(rx(1:L)) .* rx(L1:2*L)); R sum(abs(rx(L1:2*L)).^2); for d 1:N-2*L1 Metric(d) abs(P)^2 / (R^2 eps); if d N-2*L1 P P - conj(rx(d)) * rx(dL) conj(rx(dL)) * rx(d2*L); R R - abs(rx(dL))^2 abs(rx(d2*L))^2; end end idx find(Metric threshold, 1); if isempty(idx) detected []; else detected idx; end metric Metric; end这段代码里有两个容易写错的地方。一是eps的加入防止R为0时除零纯噪声段的R很小但一般不为0加eps更稳。二是滑动更新P时移出的是conj(rx(d))*rx(dL)移入的是conj(rx(dL))*rx(d2L)下标差一个L。这个下标一旦写错检测点就全偏了而且波形看起来还像是正常的特别迷惑人。5.2 Simulink中OFDM调制解调模块怎么配合Simulink里有OFDM调制解调相关模块可以直接生成基带信号用来验证分组检测很方便。我一般把发送端搭成突发模式用一个脉冲信号控制数据包使能使能期间连续发送前导加数据使能之外发零。接收端把分组检测的检测时刻接到帧同步上帧同步再把有效数据段切出来送进OFDM解调模块观察星座图。搭建时要注意几点。仿真步长和采样率必须一致否则接收数据的实际采样率和你算的L对不上检测性能会莫明其妙地变差。前导的生成方式要避免用带CP的普通OFDM符号直接充当重复结构否则自相关峰值会出现在CP边缘而不是前导起点。Simulink默认的离散求解器还可能引入一个采样点的延迟检测时刻会系统性偏后记录下来做补偿就行。5.3 实测中常见的几个怪现象与排查方向我把实际遇到过的、帮人排查过的现象整理成一张表现象可能原因处理方向持续检测不到分组门限太高L不匹配前导没有重复结构SNR过低降低门限导出发送波形核对L检查前导设计噪声下频繁误检门限太低能量检测受AGC影响非高斯噪声提高门限加能量预判统计噪声底重新标定检测点系统性偏后滑动窗口初始偏差Simulink/FPGA流水线延迟测量固定延迟做补偿或选到达最早的有效触发点低SNR性能突然变差滑动更新下标写错定点化动态范围不够对比朴素写法扩大定点位宽重标门限这些现象里最坑的就是“低SNR性能突然变差”因为它往往不是算法问题而是实现细节问题。遇到这种问题建议在浮点仿真里把滑动累积结果和朴素计算结果逐点对比很快就能定位到是不是更新公式写偏了。5.4 从WiFi到无人机ID信号分组检测其实很协议无关很多人问无人机ID信号是不是OFDM这要看具体实现但不少远程ID方案确实用上了OFDM或者类似的多载波体制。回到分组检测这个层面不管协议是WiFi、LTE还是无人机ID只要物理层前导设计里有重复结构Schmidl-Cox这类的自相关检测就能直接使用。区别只是训练序列的具体数值、长度、带宽不同核心判决逻辑完全一样。所以把分组检测和具体协议解耦来学反而更容易看清本质。你换一个系统要改的只是前导参数和门限算法框架不用动。这也是我建议初学者先不要扎进某个协议标准里抠细节而是先把通用同步算法搞明白的原因。6. FPGA落地要点与分组检测之后的同步衔接6.1 滑动相关器的硬件流水线怎么搭FPGA实现分组检测核心是把自相关和能量统计做成流水线。一个常见的误区是以为每个时钟周期都要算L次复数乘加把资源预算做得很大。其实滑动结构下每个周期只需要处理一个采样点进入、一个采样点移出本来就该是每个周期常数次运算。具体做法是维护两个环形缓冲一个缓存最近2L个采样点另一个缓存对应的共轭乘积累加值。新采样点到来时更新相关累加值和能量累加值。做判决时用比较器判断M(d)threshold但硬件里尽量不要做除法可以改成判断abs(P)^2大于threshold乘以R^2把门限转成定点乘法资源省很多。如果ADC采样率很高单路流水来不及处理可以用多路并行把2L个采样点拆成若干段并行累加最后做树状求和。并行路数由FPGA时钟和ADC采样率的比值决定比如ADC时钟250MHz、FPGA逻辑也跑250MHz就单路如果ADC时钟1GHz、FPGA逻辑只能跑250MHz就要4路并行。6.2 定点化的位宽估算与截断策略ADC的位宽一般是12位或14位自相关累加L个点最大幅度增长是log2(L)位。比如L64从12位变到18位左右再考虑瞬时噪声峰值可能超出均值通常要多留2到3位取20位比较保险。能量统计的位宽类似。归一化判决变量如果保留浮点会很浪费资源实际硬件里基本都转成定点比较。另外要注意P(d)的复数乘法会引入截断误差低信噪比下这些误差可能把判决变量压低。一个工程技巧是在FPGA里仿一遍纯噪声输入打印出M(d)在无信号时的最大值作为硬件噪声底的参考。如果硬件噪声底明显高于浮点仿真说明位宽不够或者截断策略太激进需要加大位宽或者改舍入方式。6.3 从检测时刻到频偏估计、符号定时的衔接分组检测输出检测时刻之后后级一般还要做三件事粗频偏估计、符号定时精同步、细频偏估计。粗频偏估计可以直接复用分组检测里的P(d)相位因为前面已经说过P(d)携带固定的相位旋转用它的相位除以相应的常数就能得到频偏初值。符号定时精同步则可以把检测时刻作为粗略起点在一个小范围内搜索FFT窗口的最佳位置。这里有个工程细节检测到分组之后不要让后级模块立即以最早触发时刻为基准锁定而是等判决变量稳定通过平台后再启动。因为平台效应下最早的触发点可能比最佳FFT窗口起点提前好几个采样点先把起点锚定在平台中段附近再精同步收敛会更快误锁概率也更低。6.4 做FPGA之前一定要先把定点模型跑透根据我自己的经验做FPGA实现之前先把算法在MATLAB里做成定点模型用同一组测试向量验证硬件和软件行为一致这一步能省掉大量板上调试时间。不要急着写Verilog/VHDL否则你会发现自己一边调硬件、一边又在怀疑算法两边互相干扰进度反而更慢。分组检测虽然看起来只是同步链路里的一小步但它一旦误动作后面所有模块都会跟着遭殃。花时间把这一级的设计验证做扎实是整颗OFDM接收机芯片或整套FPGA接收链里性价比最高的事情。最后再分享一点个人体会。我调分组检测踩过最大的坑是训练序列结构和接收端窗口长度没对齐。这听起来特别基础但项目一忙起来、代码一多这种低级错误反而最隐蔽。所以我的习惯是调任何同步模块之前先把发送端的时域波形导出来数一数重复周期再对着接收端的参数核对一遍。另外一个小技巧是调门限不要只看几个点把整个M(d)波形抓下来看平台形状、看噪声底高度往往一眼就能定位问题。同步链路后面还有符号定时和频偏估计跟分组检测的衔接点非常多下次有时间我再接着写。