
简介面向通信信号处理学习与研究者的CPM连续相位调制线性均衡器MATLAB实现资料包重点解决CPM信号经信道后的码间干扰抑制问题并采用维特比判决器完成最优序列检测。资源共29个文件主体为14个.m脚本涵盖调制、相位编码、均衡器设计、维特比解码、误码率统计等完整链路另有7个.mat数据文件保存仿真中间结果与波形7个.asv为备份版本1张图片用于结果展示整体仅533KB轻量易用。目前已有127人浏览学习适合数字通信课程设计、毕业设计或算法仿真验证。通过这份资料可获取可运行的CPM均衡仿真框架理解线性均衡与维特比判决的结合方式并可直接调参观察不同信噪比下的性能曲线为深入掌握CPM解调与均衡技术提供实用参考。1. CPM调制的线性均衡器到底解决什么问题相位记忆、码间串扰和一次判决拿到一个标题里带“CPM调制”“线性均衡器”“维特比判决器”的.zip工程十有八九是物理层算法仿真。CPM调制连续相位调制的特点是信号相位连续、包络恒定适合非线性功放和窄带突发通信但它的相位是“攒”出来的当前符号的相位变化要承接之前所有符号的贡献这种记忆让接收机不能像QPSK那样一拍定符号。真实信道里还有多径、滤波和定时偏差带来的码间串扰单靠一个均衡器抬不高性能单靠一个判决器又扛不住强ISI。解法就是标题这组组合线性均衡器先把信道拖尾和大部分ISI削掉维特比判决器再对残余记忆做最大似然序列判决。这篇笔记就按这个顺序把信号模型、均衡器参数、维特比状态定义和整套链路代码拆给你。2. 从CPM信号到线性均衡器为什么“线性”两个字放在这里不违和2.1 CPM的复基带表示相位是攒出来的符号不能孤立看CPM的复数基带信号写出来很简单s(t) sqrt(2E/T) * exp(j * φ(t; α))其中相位路径φ(t; α) πh * Σᵢ αᵢ * q(t - iT)。αᵢ是±1的符号序列h是调制指数q(t)是相位脉冲它是频率脉冲g(t)的积分。频率脉冲只在L个符号长度内非零所以当前时刻的相位不仅依赖当前符号还依赖前L-1个符号这就是CPM的记忆来源。很多人第一次见“CPM调制配线性均衡器”会觉得矛盾调制的相位本身就是非线性的指数映射怎么用线性均衡器去均衡关键在于Laurent分解。CPM可以近似展开成若干个PAM分量的叠加主分量占了绝大部分能量GMSK、MSK这些常用体制甚至只需要取第一二个主分量就能很好地近似。近似之后的信号是一个线性调制信号这时候线性均衡器才有资格上场。换句话说标题里的“线性均衡器”不是去拟合那个指数相位而是对Laurent分解后的符号序列做信道反向滤波。这一点想通后面所有代码都不绕。2.2 线性均衡器为什么选LMS抽头结构、收敛条件和参数下限线性均衡器在这个工程里的常见形态是一个复基带横截滤波器输入是符号速率的接收采样输出是均衡后的符号估计。实现方式一般是LMS自适应少数场景用RLS但RLS对矩阵求逆的数值稳定性要求高定点实现很容易发飘仿真里看误码率图差别不大工程上我一般先用LMS跑通再考虑RLS。LMS的关键参数是抽头数taps和步长mu。抽头数取决于信道冲击响应的有效长度突发通信里常见的时延扩展是2到4个符号取11到21个抽头基本够用。步长的上限受输入功率约束经验下限是mu不超过 1/(taps * 输入信号功率)。更稳的做法是先对均衡器输入做归一化rx_norm rx / (rms(rx) 1e-6)再把mu取0.01到0.05。这个归一化看着土实际上能避免一半的收敛翻车。要注意抽头延迟线输出天然带一个群延迟等于(taps-1)/2个符号后面跟维特比判决器时这个延迟必须算进训练序列对齐里。2.3 最小可复现的CPM发射函数Matlab代码与边界处理先给一个通用CPM发射函数用于生成复基带仿真波形。它不依赖Communication Toolbox只用了卷积和cumsum方便看清楚相位是怎么累积出来的。function [tx, phi] cpm_modulate(bits, h, L, sps, pulse_type) % bits : 1/-1 符号序列长度 N % h : CPM 调制指数MSK 就是 h0.5, L1 的特例 % L : 频率脉冲记忆长度单位是符号数 % sps : 每个符号的采样点数 % pulse_type: rect 矩形频率脉冲 / rc 升余弦频率脉冲 % 返回值: % tx : 复基带波形长度约 N*sps % phi : 相位轨迹用于调试和画图 N length(bits); % 构造频率脉冲 g(t)面积归一化为1 switch lower(pulse_type) case rect g ones(1, L*sps) / (L*sps); case rc t 0:(L*sps-1); g 0.5 * (1 - cos(2*pi*t/(L*sps))) / (L*sps); otherwise error(只写了 rect 和 rc其它频率脉冲需要自己补); end % 相位脉冲 q(t) 是 g(t) 的积分cumsum 后要除以 sps q cumsum(g) / sps; % 相位路径phi pi*h * sum(bits_i * q(t - iT)) % 用卷积实现末尾多补一个符号让相位收尾完整 symbols_ext [bits, bits(end)]; phase_base pi * h * conv(symbols_ext, q); % 截取中间 N*sps 个采样点避免卷积边界效应 start_idx (length(q) 1) / 2; idx start_idx : start_idx N*sps - 1; phi phase_base(idx); tx exp(1j * phi); end这段代码有三个地方容易踩坑。第一频率脉冲必须归一化面积等于1否则调制指数h的实际效果会偏大偏小导致维特比网格状态数对不上。第二cumsum之后除以sps是因为每个符号内采样了sps个点离散积分要还原成连续积分量纲。第三conv之后边界处会多出一段过渡相位必须从第(length(q)1)/2个点开始截取不然波形开头会多出一截错误相位。用这个函数生成MSK信号h取0.5L取1pulse_type选rect再把sps设成4以上就能看到恒包络且相位连续的特征。3. 维特比判决器接入状态网格、分支度量和回溯三件套3.1 为什么均衡器输出不能直接判符号残余ISI和记忆均衡器不等于零 forcing均衡它在抑制噪声和消除ISI之间做折中输出端一般还残留一部分可控的ISI。更重要的是CPM本身带着相位记忆即使信道是干净的均衡器输出也不能映射到固定几个星座点上。你在示波器上看这个输出会看到一坨旋转的轨迹硬判决掉进去就是10%以上的误码率。正确做法是最大似然序列估计也就是在所有可能的符号序列里找一条让接收序列对数似然最大的路径。直接遍历2^N条序列不现实维特比把这个问题拆成网格图上的动态规划每个时刻只保留到达每个状态的最优路径路径数从2^N压到状态数乘以N。代价是延迟判决要等回溯深度足够才能出结果这正是“使用维特比判决器进行判决”这句话的落点。3.2 状态怎么定义CPM的相位状态加符号状态CPM网格状态由两部分组成相位状态和符号记忆状态。若调制指数h是有理数p/q相位状态有2q个当p为偶数时约化成q个。符号记忆部分若频率脉冲长度为L则需要记住最近L-1个符号每一种排列是一个状态。总状态数等于两者乘积。用MSK举例h0.5L1相位状态只有0、π/2、π、3π/2四个符号记忆状态只有1个所以网格一共4状态。换成GMSKh0.5L3符号记忆是2^(3-1)4种相位状态4个总状态16个。L再大状态数指数上涨维特比就开始吃资源这也是为什么工程上会把均衡器和维特比配合用均衡器先把信道记忆压短维特比只需要处理CPM自身的短记忆。分支度量在加性高斯白噪声下就是欧氏距离的平方metric | r[n] - target |²target是假设该分支对应的符号组合在接收端产生的理想参考值。每次转移从旧状态出发走一条分支到新状态把分支度量加到旧状态的累计度量上保留每个新状态的最大累计度量同时记住这条路径的来源。3.3 维特比判决器核心代码最小实现与参数说明下面这段代码不依赖工具箱输入是均衡器输出的符号速率序列、分支转移表和回溯深度输出是判决后的符号序列。function [seq, metrics_out] viterbi_mlse(rx, trans, trace_len) % rx : 均衡器输出N x 1 复数序列 % trans : M x 4 的转移表 % [old_state, symbol_idx, new_state, target] % old_state/new_state 从0开始编号 % symbol_idx 是这条分支对应的符号索引回溯时用来恢复符号 % target 是对应分支的理想参考值复数 % trace_len: 回溯深度一般取 5~10 倍的状态记忆长度 N length(rx); nstate max(max(trans(:,[1 3]))) 1; % 累计度量初始化状态0起始度量0其它状态不可达 metric -inf(nstate, 1); metric(1) 0; % 记录每个时刻每个状态的最优来源状态 prev_state zeros(nstate, 1); % 记录每个时刻每个状态幸存路径对应的符号 survivor_sym zeros(nstate, N); for n 1:N next_metric -inf(nstate, 1); next_prev prev_state; for b 1:size(trans, 1) s0 trans(b, 1) 1; s1 trans(b, 3) 1; cand metric(s0) abs(rx(n) - trans(b, 4)).^2; if cand next_metric(s1) next_metric(s1) cand; next_prev(s1) s0; survivor_sym(s1, n) trans(b, 2); end end metric next_metric; prev_state next_prev; % 防溢出每回溯长度做一次减最大度量 if mod(n, trace_len) 0 metric metric - max(metric); end end % 回溯 [~, st] max(metric); seq zeros(N, 1); for n N:-1:1 seq(n) survivor_sym(st, n); st prev_state(st); end metrics_out metric; end这段代码的关键点是cand metric(s0) 欧氏距离找的是最大度量等价于最小欧氏距离路径。初始化时只有状态0可达其它状态是-inf这保证了第一跳必须从正确状态出发。survivor_sym按时刻存符号回溯时从最后一个时刻往前倒着读得到的是一个延迟输出的序列。trace_len如果设太小路径会过早剪枝导致出现错误幸存路径设太大则输出延迟大对突发帧来说浪费字段常见做法是取CPM记忆长度L的5到10倍L1的MSK用10就够。4. 把线性均衡器和维特比判决器串成一条可跑的链路主程序与调参4.1 链路参数表和输入输出对齐在动手写主程序之前先把整个链路的接口参数定下来避免后面调参时抓瞎。以一个突发帧长度5000符号、多径信道2径为例参数表如下参数取值说明调制指数 h0.5MSK网格状态4个记忆长度 L1频率脉冲长度符号数训练序列长度400用于LMS收敛均衡器抽头数11奇数群延迟5个符号LMS步长 mu0.02输入归一化后使用信道抽头[1, 0.5, -0.2]符号速率等效信道回溯深度 trace_len105~10倍L输入输出对齐是这里最容易翻车的一环。均衡器抽头数11意味着输出序列比输入短10个点群延迟是5个符号。维特比判决器本身又有一个回溯延迟所以误码统计时不能从头对齐要跳过均衡器群延迟再取数据段。很多工程包把“判决序列比发端序列错了几十个点”误判成算法不行其实是对齐错了。4.2 主程序代码LMS训练加维特比判决下面给一个等效符号速率的完整链路脚本。这里用等效模型把发射端简化成MSK相位累积是为了把篇幅聚焦在均衡器和维特比判决器上实际工程中可以把第2章的cpm_modulate过采样波形替换进来接收端加匹配滤波和定时同步均衡器和判决器这两段不用动。clear; rng(1); % 参数 train_len 400; % 训练符号数 data_len 5000; % 数据符号数 mu 0.02; % LMS 步长 taps 11; % 均衡器抽头数 trace_len 10; % 维特比回溯深度 hch [1, 0.5, -0.2]; % 符号速率信道 % 发射 % 随机生成 1/-1 符号头部是训练序列 train_bits 2 * (rand(1, train_len) 0.5) - 1; data_bits 2 * (rand(1, data_len) 0.5) - 1; tx_seq [train_bits, data_bits]; % MSK 等效相位模型每个符号相位旋转 ±pi/2 phase pi/2 * cumsum(tx_seq); tx exp(1j * phase); % 信道 rx_chan filter(hch, 1, tx); snr_dB 12; noise_var var(rx_chan) / (10^(snr_dB/10)); rx rx_chan sqrt(noise_var/2) * (randn(size(rx_chan)) 1j*randn(size(rx_chan))); % LMS 均衡器 % 输入功率归一化避免步长失效 P mean(abs(rx).^2); rx_norm rx / sqrt(P); % 训练段已知直接做 LMS w zeros(taps, 1); D (taps - 1) / 2; N length(rx_norm); eq_out zeros(N - taps 1, 1); err_sq zeros(N - taps 1, 1); for n 1:(N - taps 1) x rx_norm(n : n taps - 1).; y w * x; eq_out(n) y; if n train_len d tx_seq(n); % 训练模式 else d sign(real(y)); % 判决引导模式 end e d - y; err_sq(n) abs(e)^2; w w mu * conj(e) * x; end % 去掉均衡器群延迟取数据段 data_start D 1 train_len; data_end D 1 train_len data_len - 1; eq_data eq_out(data_start : data_end); % 维特比判决器 % 构造 MSK 的 4 状态转移表 states [0, pi/2, pi, 3*pi/2]; trans []; for s 1:4 for sym [-1, 1] new_phase mod(states(s) pi/2 * sym, 2*pi); [~, new_state] min(abs(states - new_phase)); target exp(1j * new_phase); trans(end1, :) [s-1, sym, new_state-1, target]; end end [det_seq, ~] viterbi_mlse(eq_data., trans, trace_len); det_seq det_seq.; % 误码统计 % 维特比输出有回溯延迟粗略地把前面20个点当保护段跳过 skip 20; ber sum(det_seq(skip1:end) ~ data_bits(1:end-skip)) / (data_len - skip); fprintf(BER %.5f\n, ber);LMS部分需要注意几个细节。输入的rx_norm做了功率归一化所以mu取0.02才稳定不归一化时同样的mu在信噪比变化时会表现得很“玄学”。训练模式和判决引导模式的分界点是n小于等于train_len但均衡器输出第n个点其实对应发射的第n个符号经过信道延时后的叠加所以训练序列必须放在数据帧的最前面不能用随机数据做判决引导初始化。维特比转移表中target用的是新状态的相位对应的是该符号间隔结束时的相位在实验室模型里这个近似足够换成真实过采样CPM时target要改成该符号间隔内匹配滤波器输出的平均参考点。4.3 三个必调参数步长、抽头数、回溯深度这三个参数对最终误码率的影响是递减的但每个都有自己的一套调法。LMS步长是最敏感的。mu太小收敛慢训练序列只有400个符号可能还没收敛到稳态就切到数据段mu太大权值在最优值附近抖动相当于给均衡器输出额外加了一份噪声。判断方法很简单把err_sq拉出来看训练段末尾应该出现一个平稳的底噪平台如果曲线还在单调下降说明训练长度不够或mu偏小。抽头数的选择看多径时延。用上面的信道[1, 0.5, -0.2]有效时延跨度是3个符号11抽头已经足够。抽头太多会增加群延迟和LMS稳态误差抽头太少则残余ISI压不下去。一个可复现的经验法则是抽头数等于多径符号跨度加2到4再取奇数。回溯深度只在信道很长时起作用。MSK的L等于1回溯深度10已经几乎达到最优。如果换了GMSK或者L4的CPM回溯深度至少要到30否则你会看到误码率出现一个降不下去的平层改成40又变好这就是回溯深度不够的典型特征。5. 避坑均衡器误收敛和维特比判决器的5个常见问题5.1 误差曲线不降反升LMS步长和输入功率现象err_sq画出来不是单调下降而是一直抖动甚至越来越大BER在0.3附近上不去。原因输入的rx功率不可控比如信道增益是10倍mu还是按0.02去更新权值权值更新量直接爆掉。或者mu超过了稳定学上限。解决把输入做功率归一化再让mu落在0.005到0.05之间。工程里常见做法是对每一帧都重新计算一次输入功率不能用上一帧的参数硬套。这里有个血泪经验突发通信不同帧之间的接收功率往往差几个dB归一化如果只做一次后半帧照样发散必须按帧实时算。5.2 误码率平台降不下去状态数与调制指数不匹配现象信噪比从8dB提升到20dBBER曲线先降后平停在某个平台。原因维特比转移表里的状态数定义错了。比如实际调制指数h0.5相位状态应该是4个结果按2个状态去构造转移表分支参考值错了再高的SNR也救不回来。解决回头核对调制指数。hp/q相位状态是2q个p为偶数时合并成q个。写代码时不要手算直接从发射参数里生成状态表。这里建议在调试阶段把相位轨迹画出来和发射端phi对照一下看判决器内部假设的相位跳变是否一致。5.3 判决结果整体错位均衡器群延迟现象BER不高但错码位置集中在某一片看起来像随机突发错。原因均衡器抽头数11输出相对输入延迟5个符号维特比判决输出又额外延迟trace_len。统计误码时直接拿det_seq和data_bits从头对齐必然错位。解决先用一个已知的m序列做延迟估计。把m序列当作数据段头部接收端判决后做滑动相关相关峰位置就是总延迟。等效模型里群延迟是(taps-1)/2真实系统还有匹配滤波器和定时同步的延迟不要靠猜。这个问题查起来很折磨人建议一开始就在主程序里加一个delay_estimate变量别偷懒。5.4 星座点整体旋转残余相偏现象均衡器输出星座看着是开的但整体有固定角度旋转维特比误码率高。原因LMS只能均衡信道幅度和相位畸变对接收机本振和发端之间的固定相差无能为力。训练序列若正好是旋转对称的LMS会把旋转错误地吸收进权值一旦切到数据段判决引导模式也跟着错。解决在均衡器输出到维特比之间加一个相位估计器。常见做法是训练序列算出平均相位差phase_est angle(sum(train_rx .* conj(train_tx)))然后把整个数据段乘以exp(-1j * phase_est)。注意要补偿的是均衡器输出端的数据不是均衡器输入端。5.5 维特比路径度量溢出现象仿真跑到后半段det_seq突然变成全0或者固定状态。原因累计度量一直在加欧氏距离但不回溯时状态度量被不断累积成很大的负数超过双精度表示范围后变成-inf之后就永远进不了候选分支。解决每隔trace_len个符号把所有状态度量减去当前最大值这只影响绝对大小不影响分支比较结果。前面的viterbi_mlse代码里已经内置了这段如果你是从别处抄来的判决器检查一下有没有这个减最大值的操作。6. 验证均衡器加维特比判决器好坏的三个手段先看收敛再看星座最后打BER调试这套链路时我一般按三步验证顺序不能乱。第一步先看LMS的err_sq曲线训练段尾部必须有底噪平台没有平台就不往下走。第二步看均衡器输出星座和维特比回溯前的参考值匹配情况画成一个二维散点图颜色区分不同状态看看旋转和聚集程度。第三步才打BER曲线而且至少打Eb/N0从6到16的几个点观察曲线斜率。一个更细的验证技巧是把维特比判决器的内部路径度量打印出来和发射端相位轨迹做对比。如果判决序列在某个位置连续错一段去看那个位置前后的相位变化大概率能找到状态跳变或抽头权重突变的原因。这个手法比只看BER图有效得多因为它能定位到是均衡器坏还是维特比坏。我自己早年调试GMSK时曾在回溯深度上栽过跟头误码平台一直压在1e-3上不去折腾了两天把trace_len从10改成50就掉了两个数量级。所以后来养成一个习惯任何带记忆的调制体制第一次跑通后先扫描一遍trace_len和抽头数做成网格图再谈优化。均衡器、维特比、定时同步三块互相牵扯只调一个参数是看不出真相的。按上面三步验证至少能帮你把每一步的边界划清楚剩下的是参数扫描的耐心活。希望帮到你。本文还有配套的精品资源点击获取