
简介面向OFDM通信与认知无线电频谱感知研究的MATLAB仿真源码包适用于高校无线通信课程设计、论文仿真及入门学习者。针对阴影和深度衰落下单节点感知结果不可靠的问题代码覆盖循环平稳特征检测、能量检测以及协作频谱感知可在Rayleigh衰落信道下对比硬判决“与”“或”准则与基于似然比检测LRT软判决的融合性能。压缩包内含6个文件其中4个为.m脚本源码2个为txt说明与信道数据文件整体仅6KB结构精简、易于读改复现。已有234人学习下载。读者可结合源码与说明快速搭建OFDM信号频谱感知仿真环境理解多节点协作判决如何提升检测可靠性也可在此基础上扩展D-S证据理论融合算法适用于课程设计与论文前期验证。1. OFDM信号频谱感知为什么绕不开循环平稳检测与协作做认知无线电和频谱监测的人迟早会撞上同一个场景主用户用的是OFDM信号4G/5G/Wi-Fi基本都是这套体制次用户要在很低的信噪比下判断这个频段“有人用还是没人用”。能量检测在这个场景里会翻车因为OFDM时域波形和噪声长得太像而且接收机噪声功率本身有不确定性门限怎么调都调不准。循环平稳检测却能靠OFDM循环前缀带来的隐周期把它从噪声里挖出来。但单节点再准也怕阴影衰落和隐藏终端所以还要在融合端做协作频谱感知把多个节点的判决结果汇总。再加上历史频谱数据HistoricalSpectrum当先验能让判决门限和融合权重都变得更聪明。这篇文章就按这条链路把OFDM信号生成、循环平稳检测、协作融合和历史频谱加权一次讲透。2. 搭一个最小实验生成OFDM信号并标定循环平稳特征2.1 OFDM发送信号模型与循环前缀带来的隐周期OFDM发射端做的事情并不复杂把QPSK或QAM符号扔到N个子载波上做一次IFFT得到时域符号再把这个符号的最后L个采样点复制到开头组成一个带循环前缀的完整符号。这个循环前缀看起来是冗余但它恰好让OFDM信号不再是平稳随机过程而是循环平稳过程信号的自相关函数随时间呈现周期性。周期就是OFDM符号周期T_sym (N L) / fs。因为循环前缀里的采样点和符号尾部完全一样信号和它延迟L个采样点之后的副本之间存在强相关。相关峰在每个符号周期重复一次这就是循环平稳检测的物理基础。白噪声没有这种结构不管你延迟多少、积累多久它都不会在某个循环频率处出现相关峰。这个“有峰”和“没峰”的对比就是检测器设计的基本盘。在工程上我们通常用循环自相关函数Cyclic Autocorrelation Function, CAF来刻画这个特征。对接收信号x[t]循环自相关的估计式为R_x^α[ν] (1/N_obs) Σ x[tν] x*[t] e^{-j2παt}。当α 1/T_sym时这个统计量的幅度会被循环前缀放大得很明显当α是其他值时它趋近于零。下面直接写代码把这个过程跑通。2.2 用Python生成OFDM信号并计算循环自相关切片这里给出一个可以直接运行的最小实验生成100个QPSK调制的OFDM符号叠加上高斯白噪声在信噪比5dB下计算循环自相关在α 1/T_sym处的取值并和邻近的非循环频率位置对比。import numpy as np # OFDM 参数 N 64 # 子载波数 L 16 # 循环前缀长度 M 100 # OFDM 符号数 snr_db 5 # 接收信噪比 # 1) 生成 QPSK 频域符号加 CP 合成时域 OFDM 信号 rng np.random.default_rng(42) data rng.integers(0, 4, size(M, N)) sym np.exp(1j * np.pi / 4 * (2 * data 1)) # QPSK 星座点 ifft_out np.fft.ifft(sym, axis1) # 沿子载波维做 IFFT tx np.hstack([ifft_out[:, -L:], ifft_out]) # 每个符号前拼接 CP tx tx.reshape(-1) # 并串转换 # 2) 按目标信噪比加复高斯白噪声 sig_pow np.mean(np.abs(tx) ** 2) noise_pow sig_pow / (10 ** (snr_db / 10)) noise rng.standard_normal(tx.size) 1j * rng.standard_normal(tx.size) rx tx noise * np.sqrt(noise_pow / 2) # 3) 循环自相关估计只看 alpha1/T_sym、时延-L 这条支路 fs 1.0 # 归一化采样率 T_sym N L # 一个符号的采样点数 80 alpha 1 / T_sym # 归一化循环频率 lag -L # CP 对应的延迟采样点 N_obs len(rx) mean_val np.mean(rx[N_obs // 2:]) xx rx[N_obs // 2:] - mean_val # 去直流避免谱泄漏串扰 t np.arange(abs(lag), N_obs // 2) R_target np.mean( xx[t lag] * np.conj(xx[t]) * np.exp(-2j * np.pi * alpha * t) ) R_contrast np.mean( xx[t lag] * np.conj(xx[t]) * np.exp(-2j * np.pi * (alpha 0.02) * t) ) print(falpha1/T_sym 处循环自相关幅度: {abs(R_target):.4f}) print(f对照频率处循环自相关幅度: {abs(R_contrast):.4f})这段代码的逻辑分三步先生成OFDM基带信号再按信噪比加噪最后在指定的循环频率α 1/T_sym处估计循环自相关。关键参数是子载波数N和循环前缀长度L它们直接决定T_sym和循环频率的位置。N取64、L取16是常见配置对应T_sym80个采样点循环频率α0.0125归一化。运行结果会看到目标位置幅度显著大于对照位置。如果信噪比降到0dB以下单靠100个符号积累可能不够可以增大M到200或400。这里把观测数据取后半段是为了规避瞬态实际工程中信号持续存在就不需要这个裁剪。T_sym的计算务必与发射端一致否则循环频率对不上后面的检测统计量会直接衰减。3. 单节点循环平稳检测器落地从统计量到门限判决3.1 检测统计量怎么构造、怎么归一化上一章的循环自相关幅度本身可以作为检测统计量但它有个问题信号功率大小会影响数值范围不同频段、不同距离下没法用同一套门限。所以构造检测量时要做归一化常见做法是除以接收信号平均功率的平方。这样得到的统计量本质上是谱相关在某个循环频率处的“强度占比”和绝对功率无关门限设一次就能到处用。我一般会再多取一个对照频率选一个肯定没有循环平稳特征的位置比如α 0.02用同样算法估计出一个基底值。这个基底反映的是噪声和残余干扰的贡献。把目标频率的幅度和基底做比值得到的信噪比增益比绝对门限稳定得多尤其适合SNR不确定的场景。实际实现时把两个频率的估计交替计算同时还能发现频谱搬移问题——如果OFDM信号存在较大的载波频偏目标峰会从1/T_sym处偏移到1/T_sym Δf这时对照频率反而可能比目标频率高这就是要不要做频偏补偿的信号。统计量的积累长度选多少也要有个数。循环平稳检测靠的是多个符号周期重复相关观测时间至少要覆盖50到100个OFDM符号。拿5G的典型参数看子载波间隔30kHz时符号周期约33μs100个符号就是3.3ms这个积累时间对频谱感知完全够用。观测窗太短是这类检测器最常见的失败原因后面避坑章节会专门说。3.2 门限设计Neyman-Pearson准则与蒙特卡洛校准循环平稳检测的门限没法闭式求出因为循环自相关估计的分布在小样本下不是理想高斯工程上最靠谱的做法是蒙特卡洛校准。固定虚警概率目标值Pf_target在纯噪声条件下反复跑检测器统计出能刚刚好让虚警率达到目标的门限值。下面这段代码演示纯噪声下的门限标定以及有信号时的检测概率评估。import numpy as np def cyclic_stat(signal, alpha, lag, fs1.0): 计算归一化循环自相关统计量简化版 N_obs len(signal) mean_val np.mean(signal[N_obs // 2:]) xx signal[N_obs // 2:] - mean_val t np.arange(abs(lag), N_obs // 2) R np.mean( xx[t lag] * np.conj(xx[t]) * np.exp(-2j * np.pi * alpha * t) ) P np.mean(np.abs(xx) ** 2) # 平均功率 P P if P 1e-12 else 1e-12 return abs(R) ** 2 / P ** 2 # 功率归一化 # 参数 N, L, M, fs 64, 16, 200, 1.0 T_sym N L alpha 1 / T_sym lag -L trials 500 # 纯噪声下扫门限统计虚警概率 noise_pow 1.0 for thr in [0.01, 0.02, 0.05, 0.1]: fa 0 for _ in range(trials): n (np.random.standard_normal(M * T_sym) 1j * np.random.standard_normal(M * T_sym)) n n * np.sqrt(noise_pow / 2) if cyclic_stat(n, alpha, lag) thr: fa 1 print(f门限 {thr:.2f} - 虚警概率 {fa / trials:.3f})运行后能看到门限从0.05降到0.02虚警概率大致从几个百分比往下降。这里只演示了粗扫描实际项目要把扫描步长加密到0.001量级并且把trials加到几千次门限曲线才平滑。噪声功率在这个验证里设为1.0是因为归一化统计量已经除掉了P理论上输入噪声功率大小不影响统计量分布但数值上建议保持和真实接收机一致避免浮点精度带来的边角偏差。得到满足Pf_target的门限后再在同样的参数下注入OFDM信号跑出对应的检测概率Pd。一次完整的参数标定流程是先噪声标定门限再信号标定Pd最后上硬件收真实数据复验。不要在实验室只跑噪声就上线会让现场虚警高到没法看。3.3 三个必调参数循环平稳检测器看似只有一个统计量实际可调的东西不少。我梳理三个最关键的参数作用推荐起始值观测符号数M决定积累增益M越大峰越明显100~300最低不少于50循环频率点α决定峰值是否对准CP周期k/T_symk1优先级最高频偏补偿决定峰值是否脱落用CP相关做粗估计补偿后再检测观测符号数M值得多说一句它不是越大越好因为实际频谱环境是时变的主用户可能随时占用和释放观测窗口拉太长会把“忽然有人来”的场景平滑掉。做实时监测时我一般设一个200ms的滑动窗按信号带宽折算成符号数既保证积累增益又不过分迟钝。循环频率点从k1开始试如果CP很长k2、k3也有分量但幅度逐级衰减。工程上只取k1因为检测器要的就是最大信噪比增益没有必要同时监测多个循环频率来增加计算量。载波频偏的补偿通常用一个单独的粗估计模块利用CP相关估计出频偏Δf然后对信号进行频移修正。这个坑在后面避坑章节展开这里先记住不做频偏补偿检测性能在移动场景下会断崖式下跌。4. 协作频谱感知融合决策硬投票、软合并与HistoricalSpectrum加权4.1 单节点判决为什么不可靠阴影衰落与隐藏终端单节点检测做得再精细也躲不开无线环境里的两个老问题。第一个是阴影衰落节点A可能在写字楼某层靠窗位置信号路径被密集建筑遮挡实际接收SNR比理想情况低10dB以上单节点此时会漏检。第二个是隐藏终端主用户发射机和次用户接收机之间隔着一座山或一堵厚墙节点根本看不见信号但它周围的协作节点可能看得清清楚楚。这两类问题靠提高单节点灵敏度不划算正确做法是多布几个节点把局部判决汇总起来做协作频谱感知。协作感知的核心思路是假设有K个节点位置分散每个节点独立做本地检测得到本地判决d_k0或1融合中心再按规则汇聚成全局判决。这样做的好处不只是增加检测概率更重要的是让虚警和漏检在空间上解耦——同一个主用户信号被多个独立衰落信道观测集体同时出错的概率远低于单个节点出错。下面把两种主流融合方案都写出来对比。4.2 硬合并OR规则、AND规则与K/N规则硬合并是最容易落地的方案节点只上报0/1融合中心再做逻辑运算。OR规则是任一节点判1则全局判1检测概率高、漏检低但虚警会随节点数线性上升。AND规则反过来要求全部节点都判1才判1虚警低但漏检率偏高。实用中K/N规则最平衡设定阈值K节点中至少有K个判1才判1相当于在检测概率和虚警概率之间找一个折中点。import numpy as np # 模拟 6 个协作节点的本地检测性能 n_nodes 6 pd_ind np.array([0.90, 0.95, 0.80, 0.70, 0.93, 0.88]) # 各节点检测概率 pf_ind np.array([0.05, 0.08, 0.03, 0.06, 0.04, 0.07]) # 各节点虚警概率 rng np.random.default_rng(3) trials 10000 hit_or, hit_and, hit_k 0, 0, 0 fa_or, fa_and, fa_k 0, 0, 0 for _ in range(trials): # 主用户存在 dec rng.random(n_nodes) pd_ind hit_or np.any(dec) hit_and np.all(dec) hit_k (np.sum(dec) 3) # 主用户不存在 dec rng.random(n_nodes) pf_ind fa_or np.any(dec) fa_and np.all(dec) fa_k (np.sum(dec) 3) print(fOR 规则: 检测概率 {hit_or/trials:.3f}, 虚警概率 {fa_or/trials:.3f}) print(fAND 规则: 检测概率 {hit_and/trials:.3f}, 虚警概率 {fa_and/trials:.3f}) print(fK3 规则: 检测概率 {hit_k/trials:.3f}, 虚警概率 {fa_k/trials:.3f})这段蒙特卡洛仿真把三种规则的差异量化得很直观。OR规则检测概率最高但虚警概率被6个节点的虚警率抬到接近0.25AND规则虚警概率非常低可检测概率也掉到不足0.7K3规则把两者拉到中间。参数里K的取值建议是节点数的一半左右实际场景如果每个节点虚警率控制得不错K取ceil(n_nodes/3)能拿到更低虚警同时保住检测率。硬合并不需要传输软信息对回传带宽和融合中心算力要求都低适合节点多但各节点间信道只有窄带控制链路的场景。缺点是节点本地门限一旦设置不当坏节点会持续投出错误票所以后面要引入历史频谱数据来给节点“打分”。4.3 软合并与HistoricalSpectrum加权融合如果节点之间回传带宽充裕软合并能比硬合并多榨出几个dB的性能。节点把本地检测统计量Λ_k直接上报融合中心计算加权和Λ_fusion Σ w_k Λ_k。这里的权重w_k就是做文章的地方——每个节点的可靠性不可能完全一样有的节点长期处在强干扰区有的节点天线朝向糟糕如果它们和可靠节点一样权重融合结果会被拉低。权重的一种合理选择来自历史频谱数据HistoricalSpectrum统计每个节点在过去一段时间的检测正确率或者统计目标频段在过去一段时间的占用概率。思路是历史上经常被漏检的节点说明它所在位置阴影严重给它降低权重历史上频繁虚警的节点说明它周围有伪装成信号的干扰源同样降权。还有一种做法是把频段历史占用率当作先验例如某个频段在每天上下班高峰占用率超过90%融合门限就在那个时段自动收缩让判决更敏感。# 软合并 HistoricalSpectrum 置信度加权 lam np.array([2.1, 1.8, 2.6, 1.5, 2.2, 1.9]) # 各节点归一化统计量 hist_correct np.array([0.92, 0.78, 0.95, 0.61, 0.88, 0.74]) # 历史正确率 w hist_correct / hist_correct.sum() lam_fusion np.dot(w, lam) # 门限来自噪声基底估计上一章的蒙特卡洛标定值 thr 1.0 print(f加权融合统计量: {lam_fusion:.3f} - 判决: {占用 if lam_fusion thr else 空闲})这段代码里权重直接用了历史正确率归一化。注意历史正确率要按时间窗口滑动更新不能用全生命周期的累计值因为节点可能被挪了位置昨天的差节点今天换了天线可能变好节点。常见做法是维护一个长度为几百个检测周期的队列每周期推入一次命中/未命中的二值结果用过去N次的平均正确率作为当前置信度。历史数据还会被时间衰减因子平滑越近的判决对权重影响越大这个做法的好处读者跑起来就能感受到某个节点突然受到强干扰而连续误判它在融合权重里会很快被压下去而不是拖累全局好几个小时。5. OFDM循环平稳检测与协作感知的避坑清单5.1 能量检测被噪声不确定性卡死循环平稳检测也不是万能现象低信噪比下能量检测门限无论怎么调虚警率都压不住设备误报满天飞。原因接收机噪声功率估计本身有误差而且会随温度和增益档位漂移能量检测存在SNR wall信噪比低于某个值后检测器无论怎么加长积分都无法改善。解决换循环平稳检测它不是看能量而是看OFDM循环前缀带来的周期性噪声的不确定性对相关结构没有影响。但注意循环平稳检测要求信号确实具备循环平稳性如果是单载波且无CP的体制它退化得很厉害所以项目初期就要确认目标信号是不是OFDM类别把循环平稳检测当成万能药。5.2 载波频偏让循环频率峰脱靶现象检测统计量比预期低得多蒙特卡洛标定的门限在真实信号上完全不工作把循环频率上下扫一遍后发现峰值出现在α 1/T_sym Δf的位置。原因OFDM接收信号存在残余载波频偏循环频率被整体搬移。解决在检测统计之前加一个CP相关频偏估计器粗估Δf后把信号乘以e^{-j2πΔft}搬回来频偏不太大时也可以把检测器的循环频率点做成一个小搜索窗口例如在1/T_sym附近扫描±1kHz取最大输出作为统计量。后者实现简单移动场景下推荐用。5.3 观测窗口太短积累增益没起来现象接收信号明明很强但循环自相关幅值和噪声基底差不多判决输出随机抖动。原因观测符号数M太少循环平稳特征还没积累出来。每个符号周期只有很小的周期相关叠加几十个符号以内统计量方差很大。解决把观测窗口拉长到至少50个OFDM符号推荐100以上同时检查接收采样率是否真的等于fs如果采样率估算偏了T_sym算错会导致循环频率严重偏差这个和频偏问题要一起排查。5.4 历史频谱数据的时效性陷阱现象某个频段在历史统计里长期显示“高占用”但实际现场已经好几轮检测周期没人占用自适应门限却一直偏向“占用”那一侧虚警概率被抬高。原因历史占用率用了长期累计统计没有区分数据的时效性。解决放弃全生命周期平均改用滑动窗口或指数加权移动平均。窗口长度取一天内该频段业务周期的尺度比如频谱监测系统通常设30分钟到1小时的滑动窗这样早晚高峰的规律能保留突发事件也能在十几分钟内反映到门限上。更激进的做法是加时间戳分时段建模把同一频段按小时维度维护占用率曲线这种精细历史频谱模型对室内基站类主用户特别好用。5.5 蒙特卡洛标定门限时仿真要覆盖干扰与噪声不确定性现象外场测试虚警率比实验室高数倍。原因实验室标定门限时使用的“纯噪声”是高斯白噪声现场真实底噪有窄带干扰、邻频泄漏和非高斯分量。解决标定门限时噪声输入里混入一部分模拟干扰例如相邻信道的DVB-T或NR信号泄漏并且在标定集合中把噪声功率在±3dB范围内扫描一遍。这样做出来的门限不是一个点而是一个包络实际判决门限取包络上限虽然牺牲少量检测灵敏度但能保证虚警不爆炸。6. 进阶技巧用历史占用率做自适应判决门限固定门限的最大问题是它假设信道环境不变但真实频谱监测场景里目标频段的占用模式每天都在变化。占用率高的时段主用户随时可能回来门限应该更敏感占用率低的时段噪声波动更容易诱发虚警门限应该更保守。把历史频谱占用率接进门限设计是一个代价极低但收益明显的工程技巧。做法很简单用一个滑动窗口维护历史占用概率p_hist然后让判定门限随p_hist线性漂移thr_dyn thr0 - β * (p_hist - 0.5)。当p_hist接近1时门限向下偏移判决更容易触发避免在高占用时段漏检当p_hist接近0时门限向上抬压制虚警。β的取值要靠现场数据调我一般从0.1倍的thr0开始观察一周的虚警率曲线再微调。下面这段代码演示了滑动窗口历史更新和动态门限的计算过程。from collections import deque import numpy as np # 假设检测周期为 10 分钟保存最近 24 小时 144 个周期的历史 history deque(maxlen144) thr0 1.0 # 蒙特卡洛标定的基准门限 beta 0.08 # 门限调节强度初始从 0.1 倍 thr0 起调 # 模拟每个检测周期融合中心给出的软统计量 lam_stream [0.9, 1.1, 0.8, 2.3, 2.8, 1.2, 0.7, 0.9, 1.5, 3.1] for lam in lam_stream: history.append(1 if lam thr0 else 0) # 先用基准门限粗判占用 p_hist np.mean(history) # 历史占用率 thr_dyn thr0 - beta * (p_hist - 0.5) # 动态门限 final_decision lam thr_dyn print(f统计量 {lam:.2f}, 历史占用率 {p_hist:.2f}, f动态门限 {thr_dyn:.2f}, 判决 {int(final_decision)})这段代码的关键是历史队列的更新时机先用偏保守的基准门限得到本次占用与否的粗标签再把它推进历史窗口最后才用更新后的动态门限做最终判决。粗标签偶尔出错并不可怕因为滑动窗口的平均效应会平滑掉零星错误真正要避免的是把“最终判决”直接写成“标签”那样可能会导致一个自激循环。这个技巧和协作融合里的历史权重形成互补融合权重管的是节点信任度动态门限管的是决策敏感度两者都吃HistoricalSpectrum但作用在不同环节。如果系统已经有可视化平台我通常会同时画出p_hist曲线和thr_dyn曲线当p_hist发生跳变时检查是业务规律还是节点判断错误现场排障时比自己盲猜快得多。我的习惯是先跑一段纯噪声数据把虚拟警率压下来再灌入录制好的真实OFDM信号验证检测概率最后才上在线自适应。这套流程虽然多花半天时间但能避免很多上线后被业务侧“误报投诉”的尴尬。希望这些坑位和方法能帮你在自己的频谱感知项目里少走几步弯路。本文还有配套的精品资源点击获取