MATLAB实现CMA恒模算法:16QAM盲均衡仿真与代码解析
2026/9/8 21:30:46 网站建设 项目流程

简介:这是面向无线通信与数字信号处理学习者的MATLAB仿真源码,围绕QAM调制下的CMA盲均衡算法,演示从信号生成、信道模拟、均衡器迭代到解调判决的完整链路。资源为单个m脚本,压缩包仅2KB,轻量易用,适合通信工程专业学生或算法入门者直接运行、修改参数并观察均衡效果。已有2121人学习,实用性得到初步验证。脚本中给出16-QAM/32-QAM等调制方式下的CMA实现细节,包含误码率评估思路,可帮助读者理解盲均衡的迭代更新规则、多径衰落信道的影响以及MATLAB通信仿真流程,便于在此基础上扩展更高阶调制或对比其他均衡算法。

1. 为什么要写这个项目

1.1 多径效应与码间串扰

做无线通信、音频信号处理或者有线传输的同学,应该都跟“码间串扰”这个词打过交道。一句话概括:信道不是理想的,发送符号经过多径传播后,前一时刻的符号会拖到当前时刻,前后符号叠在一起,接收端没法正确判决。特别是在城区环境里的Wi-Fi、LTE这类宽带系统,多径时延展宽很容易超过符号周期,接收星座图直接糊成一团。

解决ISI最经典的方案是加均衡器,而均衡器又分两类:基于训练序列的自适应均衡(LMS、RLS等)和盲均衡。训练序列方案的前提是接收端知道对方发了什么,靠“答案”来反推信道,道理简单但费带宽。盲均衡则从头到尾不依赖训练序列,仅利用信号本身的统计特征就能把信道逆滤波器逼出来。这个项目里要讲的CMA恒模算法,就是盲均衡里面最经典、最常被拿来当入门教材的那一个。

1.2 为什么选QAM调制来搭配CMA

QAM(正交幅度调制)是调制家族里的“优等生”,同时调幅度和相位,频谱效率高,16QAM、64QAM乃至Wi-Fi 7里的4096QAM都是它的衍生。QAM信号在星座图上呈现多幅值、多相位分布,看起来不太符合“恒模”特征,但CMA照样能用,原因在于它的代价函数只关心均衡器输出信号的幅值与某个固定常数R的偏离程度,不依赖具体相位。

我当时写这个项目的起因很简单:教材上的CMA例子大多用PSK或者2QAM,视觉效果好但跟工程脱节。实际芯片里拿CMA去开眼(粗均衡)的场合,面对的恰恰是16QAM、64QAM这类高阶调制信号。所以我把16QAM作为仿真的基准,完整跑通“发送端调制→多径信道→接收端CMA均衡→星座图与误差曲线输出”这条链路。这份MATLAB代码对三类人最有用:

  • 通信工程专业学生,正在理解盲均衡理论,想动手验证代价函数与抽头更新之间的关系;
  • 刚转岗做基带物理层算法、需要快速搭均衡器仿真环境的工程师;
  • 在做水声通信、光纤通信、短波通信等场景中,需要用盲均衡做非合作接收或信道未知场景的系统设计人员。

2. CMA算法原理与核心推导

2.1 算法模型和代价函数

先把数学模型摆清楚。发送端产生符号序列a(n),信道是长度为Lh的冲激响应h(n),接收端收到的基带信号为:

x(n) = h(n) * a(n) + v(n)

其中v(n)是加性高斯白噪声。均衡器是一个长度为L的FIR滤波器,系数向量为w,输出为:

y(n) = w^H · x(n)

其中x(n) = [x(n), x(n-1), ..., x(n-L+1)]^T。核心目标是找一个w,使得y(n)尽量接近a(n)的某种尺度缩放版本。

CMA算法的设计思想非常巧妙。它定义一个代价函数:

J(w) = E[(|y(n)|^2 - R2)^2]

这个公式的直觉理解是:我们希望均衡器输出信号的模平方尽可能靠近一个常数R2。对于恒包络信号来说,R2就是信号功率;对于QAM来说,虽然星座点的模值不恒定,但在统计平均意义上,模平方的某个统计量是已知的。最小化这个代价函数,实际上是在“迫使”均衡器输出序列的瞬时功率围绕R2波动,波动越小,说明ISI被消除得越彻底。

需要注意,这只是一个“度量”,并不要求信号真的恒模。16QAM的星座点被约束在几个固定半径的圆上,而CMA做的是让输出能量往R2这个参考值收拢,所以均衡后的星座图会出现“环状分布”,四个象限的星座点虽然正确分离,但靠内圈和外圈的判决还是需要更精细的处理,这个后面会展开。

2.2 R2的来历与计算

R2不是随便给的,它的定义是:

R2 = E[|a(n)|^4] / E[|a(n)|^2]

这个比值来自Godard在1980年提出的Godard算法,后来被广泛称为CMA。它的物理意义是发射符号的四阶矩与二阶矩之比,本质上刻画了信号幅度的“分布特征”。发送端星座已知,R2可以预先计算。

我写代码时没有硬编码R2的值,而是动态算:

R2 = mean(abs(modData).^4) / mean(abs(modData).^2);

这样做的好处有两个:一是换调制阶数时不用查表改参数;二是避免我记错16QAM具体R2数值带来低级错误。如果你要手算,以标准16QAM星座点{±1±j, ±1±3j, ±3±j, ±3±3j}为例,E[|a|^2] = 10,E[|a|^4] = 132,所以R2 = 13.2。若信号归一化到单位平均功率,则对应约1.32。很多教程写得含含糊糊,其实就是归一化尺度不同。

2.3 抽头更新公式的直觉理解

对代价函数J(w)关于w求梯度,并用随机梯度下降(SGD)做在线更新,可以得到迭代公式:

w(n+1) = w(n) - μ · e(n) · x(n) · conj(y(n))

其中误差信号为:

e(n) = |y(n)|^2 - R2

注意这个误差不是传统意义上的“符号判决误差”,而是“模值误差”。它告诉算法:如果当前输出信号的模平方大于R2,就说明均衡器输出能量偏大,需要把抽头系数往下调;反之则向上调。这个“调”的方向由接收信号向量和当前输出相位共同决定,等价于在做一次带有相位反馈的能量控制。

实际编程中,抽头更新公式可以写成:

y = w' * x; e = abs(y)^2 - R2; w = w - mu * conj(x) * (y * e);

这里的conj(x)来自梯度推导的复共轭项,初学的时候很容易漏掉。漏掉之后的直接后果是算法发散,星座图完全打不开。我第一版代码就踩过这个坑,后来回头对照公式才发现是共轭问题。

3. MATLAB完整实现

3.1 仿真参数与信号生成

我建议代码从一开始就把可调参数放在一块,方便后面做蒙特卡洛实验或者参数扫描。下面这段是仿真头部:

clear; clc; close all; %% 仿真参数 M = 16; % QAM调制阶数 N = 20000; % 发送符号数 SNR = 20; % 信噪比(dB) L = 15; % 均衡器抽头数 mu = 0.001; % CMA步长 Nw = L; % 滤波器长度别名 delay = 3; % 判决延迟补偿 %% 发送信号生成 data = randi([0 M-1], N, 1); modData = qammod(data, M, 'gray'); scale = mean(abs(modData).^2); modData = modData / sqrt(scale); % 归一化平均功率为1

归一化那一步很多人会忽略。我这里主动把发射信号平均功率归一化到1,好处是后续加噪声时SNR的定义清晰,R2也变成归一化后的值,步长μ的量级比较好把握。实际调试中我发现,不归一化时16QAM信号平均功率10,同样μ值下收敛速度差异很大,归一化之后参数调整就直观多了。

3.2 多径信道与接收信号

构造一个带抽头延迟线的多径信道:

%% 多径信道 h = [1, 0.5, 0.2, -0.1, 0.05]'; h = h / norm(h); % 归一化信道能量 % 卷积发送信号 chOutput = filter(h, 1, modData); % 加噪声 noise = sqrt(0.5 * 10^(-SNR/10)) * (randn(N,1) + 1j*randn(N,1)); rxSignal = chOutput + noise;

信道系数我选了一个能量递减的多径序列,归一化处理保证总信道增益为1,方便和发射功率对比。这里用filter做卷积而不是直接上循环卷积,是因为信道均衡仿真是连续符号流场景,不能用OFDM那种块处理思维。

噪声功率的计算要注意:复基带噪声的实部和虚部方差各为N0/2,总噪声功率是N0。当信号归一化功率为1且信噪比是SNR dB时,N0 = 10^(-SNR/10),所以噪声每个分量的方差是N0/2。很多材料里噪声直接randn就会导致实际SNR比你设置的高3dB,我在这上面抠过很长时间。

3.3 CMA均衡器迭代核心

这是整个代码的灵魂。循环从L到N,逐个符号更新抽头:

%% CMA均衡 w = zeros(L,1); w(round(L/2)) = 1; % 中心抽头初始化 cmaOut = zeros(N,1); cmaErr = zeros(N,1); for n = L:N x = rxSignal(n:-1:n-L+1); y = w' * x; e = abs(y)^2 - R2; w = w - mu * conj(x) * (y * e); cmaOut(n) = y; cmaErr(n) = abs(e); end

这段循环看似简单,但有许多隐含细节值得展开。

初始化为什么选择中心抽头为1,其余为0?因为均衡器从一个“直通”状态开始迭代,只把输入信号延迟(L-1)/2个采样点,不做任何幅度和相位矫正。这比全零初始化的好处是:输出序列一开始就是接收信号本身,算法从最接近真实解的地方开始爬坡,收敛更稳定。全零初始化会导致梯度为零,压根迭代不起来。

抽头数量L=15对应均衡器的记忆深度为15个符号周期。信道只有5个抽头,理论上3到5个抽头就足以拟合逆滤波器,但实际均衡器需要留出一些冗余来对抗噪声和收敛误差,15个并不是随便取的。我试过L=7和L=21,前者收敛快但稳态误差略大,后者收敛慢但误码率和EVM稍好一些。工程上的经验是取信道估计长度的2到3倍。

步长μ=0.001这里看起来偏保守,但稳定性是第一位的。迭代过程中如果出现幅度爆炸,最可能的原因就是μ太大。量化地说,步长需要满足μ < 2/(L·Px),其中Px是均衡器输入信号功率。我代码里信号功率归一化后在1附近,0.001对于L=15来说在安全范围内。

3.4 绘图与结果可视化

做完迭代之后,需要把均衡前后的效果画出来:

%% 结果可视化 figure; subplot(1,3,1); plot(modData, '.'); axis equal; grid on; title('发送端星座图'); % 均衡前:对齐接收信号(跳过瞬态) subplot(1,3,2); rxPlot = rxSignal(1000:5000); plot(rxPlot, '.'); axis equal; grid on; title('接收端(均衡前)星座图'); % 均衡后 subplot(1,3,3); outPlot = cmaOut(1000:5000); plot(outPlot, '.'); axis equal; grid on; title('CMA均衡后星座图'); % 收敛曲线 figure; plot(1000:N, movmean(cmaErr(1000:N), 100)); xlabel('符号序号'); ylabel('模值误差幅度'); title('CMA收敛曲线');

绘图时故意去掉前面1000个符号,目的是避开收敛瞬态。这个细节很实际:如果从第1个符号就开始画,前面的误差曲线会高到把稳态部分压成一条直线,啥也看不清。用movmean做滑动平均则可以平滑掉误差信号的高频抖动,让收敛趋势更清楚。

4. 仿真实验结果与现象解读

4.1 均衡前后的星座图对比

在SNR=20dB、信道为[1,0.5,0.2,-0.1,0.05]、L=15、μ=0.001的默认参数下,仿真结果非常直观:均衡前星座图完全是一团模糊的亮斑,四个象限边界根本分辨不出16个点在哪;经过CMA均衡后,星座图呈现出清晰的四团点云,每团对应QAM星座的一个象限。

点云内部还会看到一种“往圆环上聚”的趋势。这是因为CMA自身代价函数只约束模值,不约束具体相位位置,所以16QAM四个内点(模值较小)和四个外点(模值较大)并不会精确收敛到标准星座位置,而是带径向偏差。具体表现是每个象限内部又有内外两个“晕环”。这个现象不是代码写错了,而是CMA的理论特性,后面要与DD-LMS配合解决。

4.2 收敛曲线与EVM变化

误差曲线显示,在头200到500个符号内,模值误差从较大值快速下降,随后进入缓变平台期。平台期不是一条直线,而是带锯齿状的波动,波动幅度取决于稳态剩余ISI和噪声水平。这里的“收敛速度”很大程度上由步长μ决定:μ=0.0005时基本要1000个符号才能进入平台期;μ=0.002时200个符号就降下来了,但稳态波动明显更大。

如果追求量化指标,可以算一下均衡后的误差矢量幅度(EVM):

% 粗同步后计算EVM outSync = cmaOut(1000:5000); % 简单幅度归一化 outNorm = outSync / sqrt(mean(abs(outSync).^2)); % 找到最近的星座点 demod = qamdemod(outNorm, M, 'gray'); refPoint = qammod(demod, M, 'gray') / sqrt(mean(abs(qammod(0:M-1, M, 'gray')).^2)); EVM = sqrt(mean(abs(outNorm - refPoint).^2)) * 100;

实测在默认参数下均衡后的EVM在15%到20%之间。顺便提一句,qamdemod和qammod在MATLAB里是自动归一化的,做EVM计算时要小心,建议在代码里显式把参考星座和实测信号都归一化到同一平均功率再比较,否则结果会差一个固定倍数。

4.3 算法局限性的直观体会

在做完默认参数仿真之后,我还做了一个小实验:把信道换成一个相位旋转信道,比如h = [1+0.3j, 0.2-0.1j],再跑一遍代码。结果很能说明问题:均衡后星座图依然是四团点云,但整体旋转了一个固定角度,误码率飙升。

这个现象的本质是CMA代价函数对输出信号的相位完全不敏感。无论星座点整体旋转多少度,|y|^2这个量都不会变,算法自然无法校正相位。工程中的标准做法是在CMA后面串联一个载波同步环(PLL),或者利用判决引导最小均方误差算法(DD-LMS)来做相位锁定。我最初写代码时忽略了这一点,一度以为均衡器坏了,后来翻书才意识到盲均衡不解决载波频偏和相位旋转。

5. 参数调优与踩坑记录

5.1 步长μ的经验法则

调μ是跑CMA最“玄学”的部分,但背后有规律可循。步长的上限由均衡器输入信号的功率决定:μ必须小于2/(L·Px),否则迭代过程会出现振荡甚至发散。我在L=15、信号功率约为1时,μ=0.005已经开始出现尾巴发散;μ=0.01则直接幅度爆炸。工程上常用的做法是先用接收信号功率对输入做归一化,再选择一个固定步长,比如0.001到0.002之间。

如果希望自适应能力强一点,可以考虑归一化步长NLMS版本的CMA。更新公式里把x(n)的范数放进去:

w = w - mu / (x' * x + 1e-6) * conj(x) * (y * e);

这样μ取0.1左右也能稳定工作,调参压力小很多。对仿真来说,固定步长已经足够;对实际系统,归一化步长更省心。

5.2 抽头数的选取

抽头数L直接决定均衡器能“记住”多长的信道记忆。选小了,残余ISI压不下去;选大了,收敛速度慢、稳态误差也更大。我给的参考原则是:先用信道探测或相关性估计粗略判断多径长度,然后取2到4倍。本项目中信道5个抽头,L=15是比较平衡的选择。

有一个常见误解:均衡器抽头越多越好。实际并非如此。均衡器本质上是一个逆滤波器,而逆滤波在放大信道深衰落频段的同时也会把噪声放大。φ过多后,稳态阶段的输出EVM反而变差。我的实测数据:L=7时单调收敛快,L=25时平台期噪声底明显抬高。

5.3 初始化与其他细节

中心抽头置1的初始化方式几乎是CMA的标配。但要注意,如果信道带来的群延迟较大,中心抽头对应的路径能量可能很弱,这会导致算法收敛到某个错误解。实操中可以尝试几个不同的中心位置,选收敛后EVM最小的那一个,代价只是多跑几次循环。

另一个细节是信号开始迭代的位置。我代码里n从L开始,因为x(n) = rxSignal(n:-1:n-L+1)需要取L个点,n小于L时越界。这时候在MATLAB中直接写rxSignal(n-L+1:n)然后用fliplr倒序,效果相同,但逻辑更绕,我建议保持原写法。

6. 工程进阶:CMA与DD-LMS的组合使用

6.1 CMA的短板

CMA最大的短板有两个:一是收敛后星座图只聚拢到“环状”而不是标准星座点,残余误差较大,对高阶QAM尤其明显;二是相位模糊问题无法解决。在64QAM、256QAM这类高阶调制里,单纯用CMA很难达到可用误码率。工程上CMA极少单独使用,它更适合做“粗均衡”,把星座图从一团浆糊先恢复成可判决的形状,然后交给更精细的算法处理。

6.2 DD-LMS及其优势

DD-LMS(Decision-Directed Least Mean Squares)利用星座判决输出作为参考信号,误差函数变为:

e_dd(n) = Q(y(n)) - y(n)

其中Q(·)表示最近星座点判决。这个误差是复平面上的矢量差,同时包含幅度误差和相位误差,因此DD-LMS既能纠正ISI又能纠正相位旋转。但它的前提是初始误码率不能太高,否则判决结果经常出错,误差方向错了,算法直接崩掉。这就是为什么需要CMA先开眼,把星座图粗恢复到一个可判决的程度,再切换DD-LMS做细调整。

6.3 切换策略与示例代码

切换条件一般用EVM或判决误差的长时间平均来判断。当EVM降到某个阈值(比如15%到20%)以下,就把均衡器的误差计算从CMA切换为DD-LMS。我给一个简短的切换示例:

switchEVM = 0.15; % 切换阈值 ddThreshold = 5000; % 最短迭代符号数 for n = L:N x = rxSignal(n:-1:n-L+1); y = w' * x; if n >= ddThreshold evmEst = sqrt(mean(abs(cmaErr(max(1,n-200):n)).^2)); else evmEst = 1; end if evmEst > switchEVM e = abs(y)^2 - R2; w = w - mu * conj(x) * (y * e); else d = qammod(qamdemod(y, M, 'gray'), M, 'gray'); e = d - y; w = w - mu_dd * conj(x) * e; end cmaOut(n) = y; end

这里我同时用了两个步长:CMA阶段μ较小,避免发散;DD-LMS阶段μ可以略大一些,因为判决误差在一定范围内是可靠的,收敛加速明显。工程实际中还会加一个锁定检测机制,一旦DD-LMS的EVM稳定在更低水平,就不再切换回CMA。

经过这一轮“CMA开眼 + DD-LMS细调”,16QAM在SNR=20dB的仿真条件下,均衡后EVM可以从15%左右进一步压到8%以下,星座图上的点云明显更紧凑,误码率下降一个数量级以上。这个结论在64QAM上也成立,只是收敛时间和步长灵敏度需要重新调试。

最后再分享一点个人体会:如果只跑通CMA代码就收手,你学到的只是一个公式;但如果你亲手改信道、改调制阶数、把μ故意调大看发散形态、再体验CMA和DD-LMS切换的微妙手感,整个盲均衡体系的脉络才算真正吃透。这套写法只用了不到一百行MATLAB,性价比非常高,值得花一晚上慢慢折腾。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询