简介:恆模算法(CMA)是盲均衡領域經典的自適應算法,可用於消除信道失真、恢復接收信號。該資源以MATLAB程序實現恆模算法,主要用於通信信道的盲均衡仿真,代碼結構清晰,用戶可直接修改參數以測試算法性能,適合通信工程、盲均衡方向的研究生、工程技術人員及需要快速搭建仿真環境的初學者。包內包含主函數main.m、信道輸出模塊chout.m、代價函數計算J_CM.m、算法主體cma.m等3個MATLAB源文件,另有Basic_of_CMA.pdf理論說明與使用說明文檔.md,共5個文件,壓縮包僅158KB,輕量易於下載,已有138人學習下載。用戶替換數據即可運行,附帶運行結果效果圖,可在MATLAB 2020b環境下直接驗證,便於理解算法收斂過程與參數影響,是學習恆模算法與盲均衡的實用參考。
1. 恒模算法盲均衡:不依赖训练序列也能把星座图拉回正轨的 MATLAB 方案
接收机做均衡,第一反应是发一段训练序列交给 LMS 或 RLS 去收敛系数,但在实际系统里总有场景让这条路走不通:连续业务挤不出训练时隙、突发帧来不及做符号同步、信道快速变化导致训练序列失效。恒模算法(CMA)的价值就在于不需要任何训练序列,只利用恒模信号的统计特性,就能把被多径信道搅散的星座图重新聚拢回来。用 MATLAB 实现它非常轻,核心更新公式只有十几行,真正的难点是信道建模、参数选择和收敛判断这三个环节。这篇笔记按一次盲均衡仿真从零到跑通的顺序展开,给出可以直接运行的代码结构和参数调整经验,覆盖 QPSK 与 16QAM 两类常用调制方式,适合正在做信道均衡或通信系统仿真的工程师。
2. 恒模算法的底层逻辑:为什么只看模值就能逼近信道逆
2.1 从多径卷积说起:CMA回避了什么问题
发射符号序列记为 a(n),经过冲激响应为 h(n) 的多径信道后,接收信号是 x(n) = a(n) * h(n) + v(n)。这里的卷积把当前符号与前后若干个延迟副本叠加在一起,形成符号间干扰(ISI)。均衡器的目标是用一个 FIR 滤波器 w 去补偿信道,使 w(n) * h(n) 逼近一个延迟冲激 δ(n-d),这样均衡器输出 y(n) 就近似等于 a(n-d)。
传统自适应均衡用 LMS 做这件事,误差信号 e(n) = d(n) - y(n),其中 d(n) 来自训练序列。问题是盲场景下没有 d(n),那怎么判断 w 是否在变好?CMA 的回答是:不管相位,只看模值。对于 QPSK 这类恒模信号,发射符号的模值始终是同一个常数。经过多径信道卷积后,由于符号叠加,接收信号模值被严重展宽。均衡器每调整一次 w,就检查输出 y(n) 的模值离理想恒模值近了还是远了,用这个差值来修正系数。
这个观察对 MPSK 和部分 QAM 都成立,只是恒模程度不同。工程选型上,CMA 适合两类场景:一类是完全没有训练序列可用的接收链路,另一类是训练序列被干扰或未同步的劣化链路。它不要求符号定时严格对齐,收敛过程对相位误差不敏感,这让它可以作为接收机里最早启动的一个模块,先把眼图睁开,后续再做载波恢复。
2.2 代价函数推导:梯度更新与经典 LMS 形式差在哪
CMA 把盲均衡问题变成一个无监督最优化问题。定义代价函数为模值误差的平方期望:
J(w) = E[(|y(n)|^2 - R)²]
其中 R 是发射信号的恒模值,稍后详细讲怎么算。用随机梯度下降替代期望运算,得到逐符号更新公式。先写出核心推导:|y|² = wᴴuuᴴw,对 w 的共轭求导后,瞬时梯度是 (|y|² - R)y*u。于是权向量更新为:
w(n+1) = w(n) - μ · (|y(n)|² - R) · y*(n) · u(n)
形式上与 LMS 几乎一样,区别在于 LMS 使用判决误差 e(n) = d(n) - y(n),而 CMA 把参考信号 d(n) 换成了模值约束,定义了一个伪误差 e_CMA = (|y|² - R)y。如果你在代码里看这个更新,会发现它根本不需要知道星座点的精确位置,只需要一个数 R。这就是 CMA 被称为盲均衡算法的根本原因。
可以用一个极小的 MATLAB 片段去观察代价函数的地形。假设均衡器只有两个抽头,扫描 w 的实虚部组合,把每个组合下的 J 值画成曲面。在扫描前先准备一小段经过信道的接收数据 x_ch:
% 扫描两个抽头权值,观察 CMA 代价函数地形 w1 = -2 : 0.05 : 2; w2 = -2 : 0.05 : 2; Jmap = zeros(length(w1), length(w2)); R = 1; % QPSK 恒模值 x_tmp = x_ch(1:200); % 接收序列前200个点 for i = 1:length(w1) for j = 1:length(w2) w_tmp = [w1(i); w2(j)]; y_tmp = w_tmp.' * x_tmp(1:2); % 两个权值对两个输入样本 Jmap(i,j) = mean((abs(y_tmp).^2 - R).^2); end end mesh(w1, w2, Jmap); xlabel('w1'); ylabel('w2'); zlabel('J(w)');这段代码的用途不是跑均衡,而是帮助理解为什么 CMA 的初始化不能随意给。代价函数在非零权值区域存在陡峭的谷底,但也存在非凸的局部极小区域。两个抽头时地形已经不平坦,实际系统里均衡器有十几个抽头,高维代价曲面更复杂。这就是为什么后面强调中心抽头初始化。顺便注意,代码里 x_tmp(1:2) 每次只取前两个样本,等价于用恒定输入计算瞬时代价,这只做一个直观演示,真实验证收敛还是要跑完整迭代。
2.3 与 LMS、判决引导 DD 的选型对比:盲阶段 CMA 最稳
选型不能只看名字,要放在具体工作环境下看。LMS 优缺点最明确:收敛快、稳态误差低,但必须有参考信号。判决引导算法(DD)用均衡器输出的判决符号当参考,误码率高时判决本身频繁出错,误差方向一错,权向量直接跑飞。CMA 介于两者之间,它不需要判决结果,只用模值统计量,因此初始误码率很高的时候也稳定。工程里常见组合是先把 CMA 跑起来,等眼图张开、误码率降到 1% 以下,再切换到 DD 做精细收敛,这个组合策略会在最后一章给出具体写法。
三种算法的适用性和代价用一个表来收敛:
| 算法 | 是否需要训练序列 | 高误码阶段稳定性 | 稳态精度 | 典型场景 |
|---|---|---|---|---|
| LMS | 需要 | 高,但用不了 | 高 | 有训练序列的常规链路 |
| DD | 不需要 | 差,容易跑飞 | 高 | 已经粗均衡后做精细调整 |
| CMA | 不需要 | 好 | 中等 | 盲均衡初始化、无训练序列场景 |
选择 CMA 做盲均衡不是因为它精度最高,而是因为它在没有任何先验信息的阶段最不容易翻车。接受它"粗收敛、精度一般"的定位,然后靠 DD 补齐精度,这是大多数工程实现的做法。
3. 用 MATLAB 实现 CMA 盲均衡:完整仿真链路与代码拆解
3.1 第一步:搭发射端和多径信道,先让目标信号可控
写均衡算法之前,先有一个能重复试验的信号环境。这里用 QPSK 做演示,因为它的恒模特性最干净,R 恒等于 1,收敛行为直观。信道选用轻度多径系数 [1, 0.3, 0.1],这三个抽头会让星座图明显发散但又不至于完全不可恢复。
% 发射端:QPSK 调制,归一化平均功率 N = 20000; % 总符号数 bits = randi([0 1], N, 1); % 随机二进制比特 sym = (2*bits(1:2:end) - 1) + 1j*(2*bits(2:2:end) - 1); sym = sym / sqrt(2); % 归一化到单位平均功率 % 多径信道与加性高斯白噪声 h_ch = [1, 0.3, 0.1].'; % 轻度ISI信道 x_ch = filter(h_ch, 1, sym); % 信道卷积 SNR_dB = 20; noise_pow = 10^(-SNR_dB/10); x_ch = x_ch + sqrt(noise_pow/2) * (randn(size(x_ch)) + 1j*randn(size(x_ch)));参数说明:bits 随机生成长度为 N 的比特流,每两个比特映射成一个 QPSK 符号,符号取 (1+j1)/√2 四个归一化星座点之一。除以 √2 是为了让平均功率为 1,这样步长 μ 的取值可以在不同 SNR 条件下横向对比。h_ch 是三个抽头的 FIR 信道,30% 和 10% 的副径强度模拟典型的频率选择性衰落,足够制造明显的 ISI。SNR_dB 设为 20,这个信噪比下 CMA 能稳定收敛,适合初版调通。如果你想看算法在恶劣条件下的极限,可以逐步降到 12 dB,但建议先把 20 dB 这条链路完整跑通再降。
3.2 第二步:写 CMA 核心迭代循环,二十行搞定
均衡器采用单个复数 FIR 滤波器,抽头数 M 设为 11,奇数个抽头方便做中心抽头初始化。权向量初始化时把中间抽头置 1,其余置 0,这意味着均衡器初始状态是一个纯延迟,不对信号做任何整形,然后让 CMA 逐步调整。
% CMA 均衡器初始化 M = 11; % 抽头数 mu = 0.005; % 步长 R = 1; % QPSK 恒模值 w = zeros(M, 1); w((M+1)/2) = 1; % 中心抽头置1,其余为0 % 预分配输出 y_out = zeros(length(x_ch), 1); err_sq = zeros(length(x_ch), 1); % 主循环:逐个符号更新 for n = M:length(x_ch) x_buf = x_ch(n - M + 1 : n); % 取当前M个接收样本 y = w' * x_buf; % 均衡器输出 e = (abs(y)^2 - R) * conj(y); % CMA 伪误差 w = w - mu * e * x_buf; % 梯度下降更新 y_out(n) = y; err_sq(n) = abs(e)^2; end代码逻辑分三层:先构造输入缓冲向量 x_buf,它包含了从 n-M+1 到 n 的 M 个接收样本;再做内积得到 y;最后用模值误差构造伪误差并更新权值。注意 e 是一个复数标量,e * x_buf 的结果是 M×1 的修正向量。这里的 x_buf 必须按时间顺序排列,不能倒序,否则抽头延迟方向会反,误差曲线很难收敛。err_sq 记录每个符号的误差平方,用于画收敛曲线。主循环从第 M 个符号开始,因为前 M-1 个符号凑不满输入缓冲。
参数选型方面,mu 取 0.005 是 QPSK + 归一化信号功率下的经验值,信号功率为 1 时,这个步长能让收敛时间在几千个符号量级,稳态误差也不会太大。如果接收信号没做功率归一化,mu 也要跟着缩放,否则同样的代码在不同 SNR 下会表现差异巨大。R 取 1 是因为 QPSK 四个星座点的模值平方恒等于 1。
3.3 第三步:验证收敛曲线、星座图和误码率
写完核心循环,紧接着要做三件事。第一看收敛曲线判断算法是否收敛;第二看均衡后星座图判断 ISI 是否被抑制;第三统计误码率,把收敛结果量化。
% 收敛曲线:加一个平滑窗口便于观察 figure; semilogy(smooth(err_sq(200:end), 200)); xlabel('迭代次数'); ylabel('均方误差'); grid on; title('CMA 收敛曲线'); % 星座图:丢弃前200个未收敛点,取后半段 figure; plot(real(y_out(5000:2:end)), imag(y_out(5000:2:end)), '.'); axis equal; grid on; xlabel('I'); ylabel('Q'); title('CMA 均衡后星座图'); % 误码率统计:遍历输出,找最近星座点判决 ref = [1+1j, 1-1j, -1+1j, -1-1j] / sqrt(2); y_test = y_out(5000:end); y_dec = zeros(size(y_test)); for k = 1:length(y_test) [~, idx] = min(abs(y_test(k) - ref)); y_dec(k) = ref(idx); end % 注意:收敛后存在任意相位旋转,需要先做相位校正误码率计算有个隐含坑:CMA 收敛后的星座可能整体旋转了一个任意角度,直接比判决符号会得到 50% 的错误率,但这不代表均衡失败。你需要先估计旋转角,比如把均衡输出的平均相位与发射符号的平均相位对比,或者在发射端做差分编码。这个坑在第 5 章还会展开。实际仿真中一个简单的做法是取后段星座图,用最小二乘估计旋转角 θ,把 y_out 旋转回去再判决。当 SNR 为 20 dB、信道为 [1,0.3,0.1] 时,收敛后的误码率应该达到 10⁻⁴ 量级才算正常。如果这个量级达不到,优先回去检查 mu 和抽头初始化,而不是怀疑算法。
4. CMA 参数怎么改:从能跑到跑好的四个关键旋钮
4.1 步长因子 μ:先决定数量级,再抠收敛速度
mu 是 CMA 里最敏感的参数,直接影响两个对立指标:收敛速度与稳态误差。mu 偏大,前期下降快,但接近收敛后在最优解附近来回振荡,误差曲线出现明显的"尾巴";mu 再大一点,直接发散,误差曲线在某一跳突然出现 NaN。mu 偏小,收敛需要几万甚至几十万符号,仿真时间不可接受。
实践做法是先把接收序列归一化到单位平均功率,然后把 mu 从 0.1 开始按 10 倍步进往下试:0.1 发散、0.01 发散或大尾巴、0.001 稳定收敛但偏慢、0.005 是 20 dB QPSK 下的甜点。16QAM 情况下由于多模特性,稳态误差本身更大,mu 要更保守,通常取 0.0005 到 0.002。调参时不要只看最终星座图,要盯着 err_sq 曲线判断:理想曲线先快速下降,然后在一个平台附近小幅波动。如果曲线出现明显的周期性上冲,说明 mu 过大,降一个数量级再跑。
4.2 均衡器抽头数 M:不是越多越好,要看信道长度
FIR 均衡器的抽头数必须覆盖信道冲激响应的主要能量范围。假设信道有 L 个非零抽头,均衡器至少要有 L 个抽头才能真正补偿,工程上一般取 M 为信道长度的 2 到 3 倍。前面的例子信道是 3 抽头,M 取 11 就是一个舒适区间,既能补偿 ISI,又不会因为抽头太多引入过多噪声。
抽头数过少的表现很典型:误差曲线能下降,但星座图上的四个点周围始终拖着一圈"光晕",怎么调 mu 都去不掉,这说明均衡器自由度不够,残余 ISI 无法被建模。抽头数过多则会让稳态噪声变大,因为多余的抽头没有信号分量,只累积了加性噪声。判断 M 是否合适的办法是看均衡器与信道卷积的有效冲激响应:用 conv(w, h_ch) 算一下,如果除了中心峰之外仍有明显副峰,说明阶数不足或收敛不彻底。
4.3 恒模值 R:QPSK 直接取 1,16QAM 必须按星座算
R 是 CMA 代价函数的参考值,定义是发射信号的四阶矩与二阶矩之比:R = E[|a|⁴] / E[|a|²]。QPSK 做了归一化后每个符号模值平方恒为 1,所以 R = 1,没什么可纠结的。但换到 16QAM,情况立刻不同,因为 16QAM 有 3 种不同幅度的星座点,根本没有"恒模"可言,CMA 只是强迫所有输出逼近一个统计平均的模值。
16QAM 的具体计算:归一化星座点幅度平方取值 {0.2, 1.0, 1.8},出现次数比例为 {4, 8, 4},可以算得 E[|a|²] = 1.0,E[|a|⁴] = (4×0.04 + 8×1.0 + 4×3.24)/16 = 1.32,所以 R = 1.32。如果你用未归一化的星座图,幅度平方为 {2, 10, 18},这个比值算出来是 13.2。网上很多代码直接写 13.2 或 1.32,都能跑,关键是必须和星座图的功率归一化匹配。源程序里如果换调制方式却忘了改 R,误差曲线会在一个偏高的平台上抖,误码率下不去,这是最常见的改参翻车原因。
4.4 数据长度与迭代区间:给算法留足收敛时间
CMA 是逐符号随机梯度更新,收敛需要足够的样本数。经验上,QPSK 在 20 dB 和 11 抽头条件下,至少需要 5000 个符号才能把星座图收敛到肉眼可接受的程度,推荐跑 20000 个符号并丢弃前 2000 个再统计性能。16QAM 因为稳态误差更大,建议数据长度翻倍。
不要用太短的序列验证 CMA。很多调试者为了跑得快,只发 2000 个符号,发现星座图是一团散点就断言算法不行,实际是数据量不够。反过来,如果你想快速观察参数变化的影响,可以把序列缩短到 5000 个符号作为粗筛,但最终结论必须用长序列确认。还有一种做法是分段做:前 5000 个符号用较大 mu 加速收敛,后 15000 个符号用较小 mu 降低稳态误差,这个调度策略在后面的实战中很实用。
5. CMA 盲均衡常见问题排查:翻车现象与对应解法
5.1 现象:误差曲线一直不降,星座图始终一团散点
原因通常是初始化错误。最常见的是把权向量全部初始化为 0。CMA 的更新量正比于误差 e,而误差又正比于输出 y,全零初始化时 y = 0,e = (0 - R) × 0 = 0,梯度恒为 0,算法永远不会启动。此时误差曲线从第一条迭代开始就是一条水平直线,且直线高度完全取决于 R。解决方法是改成中心抽头初始化:长度 M 的权向量,把第 (M+1)/2 个抽头设为 1,其余为 0。这个初始化等价于把接收信号原样延迟通过,梯度方向有了指向,算法才会动起来。
判断是不是初始化问题,可以打印前 20 次迭代的 w 变化量,如果范数一直是 0,基本坐实。
5.2 现象:星座图"收敛"了,但整体旋转了一个固定角度
CMA 的代价函数只约束模值,对相位没有任何约束,因此收敛后的星座会任意旋转。QPSK 可能旋转 45 度、90 度、135 度而不改变代价函数值,误码统计里会出现错判。这个不是 bug,而是算法特性。解决思路有两种:一是在发射端用差分编码,接收端做差分判决,相位旋转对差分判决的影响很小;二是额外加一级锁相环或载波恢复模块,在 CMA 收敛之后估计旋转角并校正。仿真验证时最简单的办法是截取一段已收敛的输出,与发射符号做最小二乘估计出旋转角,再把星座旋转回去统计误码,这样能单独验证均衡器本身性能。
5.3 现象:16QAM 始终不如 QPSK 收敛干净,误差曲线在几个值之间跳
16QAM 不是恒模信号,它的模值有三种可能取值。CMA 用单一 R 去约束多模信号,注定了稳态误差不可能为零。误差曲线会在各个模值对应的误差之间来回跳跃,这是正常现象,但跳跃幅度太大就说明 mu 偏大或参数不匹配。解决手段有两条路:最直接的是把 mu 调低,接受更慢的收敛,换取更低稳态误差;更彻底的是换算法,用多模算法(MMA)替代 CMA,MMA 对 QAM 信号的幅度分别约束,稳态性能明显改善。MMA 的代价函数引入了两个模值 R1 和 R2,分别对应同相和正交维度的统计量,对高阶 QAM 更友好,代价是实现复杂度稍高。
5.4 现象:加大步长后前期收敛加快,但跑到一半突然发散
这是随机梯度算法最常见的失控模式。mu 增大后,梯度更新量也随之增大,当权向量接近最优解时,更新步长超出了代价函数谷底的吸引域,权值被"弹出"谷底,误差曲线出现陡然上冲并持续增大。解决办法不是简单把 mu 调回去,而是做变步长调度:前 2000 个符号用较大的 mu 快速收敛,然后用当前权值作为新起点,换较小的 mu 继续迭代。工程上叫"退火步长"或两段式调度。MATLAB 里直接在循环中按迭代次数切换 mu 即可:
% 两段式步长调度示例 for n = M : length(x_ch) if n < 5000 mu_cur = 0.01; % 加速段 else mu_cur = 0.001; % 稳态段 end x_buf = x_ch(n - M + 1 : n); y = w' * x_buf; e = (abs(y)^2 - R) * conj(y); w = w - mu_cur * e * x_buf; end两段式步长特别适合调试阶段,你可以快速跑完一个结果,再回头看稳态细节。实际系统中如果信道是时变的,这种做法也对应了自适应的基本诉求:大扰动时快收敛,小扰动时精细跟踪。
5.5 现象:换了信道模型后完全不收敛,怀疑算法写错了
同一个算法在一个信道上收敛,换一个冲激响应更复杂的信道就失灵,这种情况要重新审视信道特征。一种可能是信道的频率响应存在接近零点的深谷,均衡器用有限抽头去逼近这个信道逆时,需要极大的增益,梯度更新方向失调。另一种可能是信道有效长度超过了均衡器抽头数,卷积后无法完全消除副峰。解决办法是先把均衡器抽头数加大到信道长度的 3 倍以上试跑,如果仍然不收敛,再检查代价函数是否陷入局部极小。
对付局部极小,工程上有两个通用技巧:一是换初始化,比如把中心抽头改成两个相邻抽头同时置 1,相当于给一个初始延迟偏置,有时能绕开停滞点;二是多次随机初始化,取最终代价最低的那次结果。代价函数的非凸性意味着 CMA 本身不保证全局最优,所有盲均衡算法在这个问题上都一样,只能通过初始化策略和结构设计去降低落入局部极小的概率。
6. 实用化技巧:CMA 转 DD 的双模式切换与收敛质量验收
CMA 收敛后稳态误差偏高,直接用于正式判决会让误码率停留在较高水平。工程上最常见的做法是在 CMA 完成后切换判决引导模式,用判决符号作为参考继续迭代,把稳态误差进一步压低。关键是切换时机必须守住一个原则:只有误码率已经降到 1% 以下才能切,否则判决错误会污染权向量更新,导致刚收敛好的结果又被破坏。
切换判据可以用输出星座的模值方差做代理判断,不需要真的统计误码率。当 CMA 收敛良好时,输出符号的模值集中在 R 附近,模值方差很小;当未收敛时,模值散布很大。每 100 个符号计算一次模值方差,连续几次低于阈值就切到 DD。DD 阶段的步长要比 CMA 更小,因为此时只需要微调,不需要大步搜索。
% 双模式切换:CMA 粗收敛后转 DD 精细收敛 mu_dd = 0.0005; var_th = 0.1; dd_mode = false; for n = M : length(x_ch) x_buf = x_ch(n - M + 1 : n); y = w' * x_buf; if ~dd_mode e = (abs(y)^2 - R) * conj(y); w = w - mu * e * x_buf; % 每200个符号判断一次模值方差 if mod(n, 200) == 0 y_seg = y_out(n-199 : n); if var(abs(y_seg).^2) < var_th dd_mode = true; end end else % 判决引导:以最近星座点为期望输出 [~, idx] = min(abs(y - ref)); d = ref(idx); e_dd = d - y; w = w - mu_dd * conj(e_dd) * x_buf; end y_out(n) = y; end最后验证均衡器质量有一个很直观的技巧:把收敛后的权向量与已知信道冲激响应做卷积,观察有效冲激响应的形态。理想情况下 w * h_ch 应该接近单一主峰,其余位置接近 0。用这段代码快速检查:
h_eff = conv(w, h_ch); [~, peak] = max(abs(h_eff)); h_eff = h_eff / h_eff(peak); % 归一化到主峰 % 主峰之外的能量占比越小,说明均衡越彻底 residual = sum(abs(h_eff).^2) - 1; fprintf('残余ISI能量占比: %.4f\n', residual);这个技巧比看星座图更客观,残差能量占比在 0.05 以下说明均衡效果很好,超过 0.1 就要考虑调整参数。这是我在模拟项目X里验证收发链路时最常用的收尾动作,每次跑完都要算一下这个数,形成习惯后判断算法收敛质量会快很多。双模式切换和残差验证这两个动作配合起来,基本覆盖了从盲初始化到精细收敛再到质量验收的完整闭环,希望帮到你少走一些弯路。
本文还有配套的精品资源,点击获取