简介:这套MATLAB调制解调仿真代码面向通信系统学习者,适合正在学习数字通信原理、准备相关实验或完成课程设计的高校学生与工程技术人员。资源以BPSK、QPSK、16QAM、64QAM四种常见数字调制方式为对象,通过m脚本演示从二进制数据流到基带调制、信道噪声叠加、解调判决再到误码率统计的完整流程,能够直观对比不同调制方式在抗噪声与频谱效率上的差异。整个rar压缩包仅4KB,共3个文件,包括两个核心m脚本和一份txt说明,其中脚本覆盖调制函数、加性高斯白噪声信道模型、解调判决及性能评估等主要环节,结构精简,便于快速阅读和复用。已有352人学习/下载,适合用来辅助通信原理课程仿真、考试复习或作为毕业设计中的算法参考模块。通过这套代码,读者可深入理解相位键控、正交幅度调制以及复数基带信号处理思路,也可在此基础上扩展更高阶调制或分析不同信噪比下的误码率曲线,为后续通信系统设计打下基础。
1. mod-demod.rar 里这四种调制,到底在比什么
打开这个 mod-demod.rar,里面八成是 BPSK、QPSK、16QAM、64QAM 四套调制解调脚本各跑一遍,最后画 BER 曲线或星座图,_m 后缀说明它是 MATLAB 工程。这类包在通信基带仿真的从业者手里很常见,但别急着双击运行——标题列出的四种调制方式,真正想比的是同一个问题:带宽省下来的代价,在判决那一刻用什么换。反直觉的结论是,纯高斯信道下 BPSK 和 QPSK 的误码率几乎相同,而 64QAM 虽然每个符号装 6 比特、最省带宽,却最先对幅度压缩和相位噪声投降。这篇文章从星座映射、成形滤波、载波同步一路讲到导频与 SNR 估计,最终给一套能直接复现的 MATLAB 调制解调链路,也覆盖 GMSK、AD9361 这类实际硬件目标落地时的改法。
2. 星座映射与判决边界:16QAM、QPSK、BPSK、64QAM 的差别从数学上先分清楚
2.1 四种调制的星座坐标与归一化因子别用错
星座图本质上就是把比特组合映射到复平面上的一组坐标。BPSK 只有两个点,信息装在实轴的符号上;QPSK 扩展到四个点,把信息拆到同相和正交两个维度;16QAM 和 64QAM 则同时在幅度和相位上编码。判决边界也随之变化:QPSK 只看角度,QAM 还得看幅度落在哪个环上。幅度层次越密,边界到原点的相对距离越小,同样噪声下误判概率越高。这就是 64QAM 比 QPSK 省带宽却更脆的根本原因。
四种方式的关键参数对比如下:
| 调制方式 | 每符号比特 | 星座点数 | 平均能量 | 平均能量归一化因子 | 判决特征 |
|---|---|---|---|---|---|
| BPSK | 1 | 2 | 1 | 1 | 一维实轴硬判决 |
| QPSK | 2 | 4 | 2 | sqrt(2) | 两个 BPSK 的二维复用 |
| 16QAM | 4 | 16 | 10 | sqrt(10) | 幅度 + 相位联合判决 |
| 64QAM | 6 | 64 | 42 | sqrt(42) | 幅度层次最密,边界最窄 |
归一化因子是这套包里最容易埋雷的地方。以 64QAM 为例,一维 8 个坐标取值 ±1、±3、±5、±7,单维度平均能量 21,二维就是 42,所以星座坐标整体要除以 sqrt(42)。16QAM 是 sqrt(10),QPSK 是 sqrt(2)。很多自己手写映射的工程,忘记归一化直接送进 awgn 加噪声,出来的 BER 曲线会比理论值偏好几个 dB,原因不是链路错,而是 SNR 定义对不上。
2.2 MATLAB 里 qammod/qamdemod 的可运行做法
如果只是想在 MATLAB 里跑通这四种调制,qammod 和 qamdemod 这对函数就够了。需要注意 qammod 并不是只管 QAM,M=4 时它输出的星座点和 QPSK 等价,所以一个接口能覆盖标题里的全部四种方式。
M = 16; % 可选 2 / 4 / 16 / 64 data_bits = randi([0 1], 6000, 1); % 6000 个随机比特 tx_mod = qammod(data_bits, M, 'gray', ... 'InputType', 'bit', 'UnitAveragePower', true); rx_bits = qamdemod(tx_mod, M, 'gray', ... 'OutputType', 'bit'); isequal(data_bits, rx_bits) % 理想信道下必须返回 1这里几个参数要和团队约定写死。'gray' 指定格雷映射,相邻星座点只差 1 个比特,误判时损失最小;'InputType' 和 'OutputType' 用 'bit' 可以省掉手动 bi2de/de2bi 的转换;'UnitAveragePower' 为 true 时,函数自动做 2.1 节里的归一化,不需要自己除因子。最容易出错的是比特顺序:qammod 默认按二进制序排列,如果发射端用了 'left-msb',接收端必须一致,否则链路误码率会固定在百分之几出不来。实际项目里我习惯两端的 bit order 参数都显式写上,不依赖默认值。
2.3 自己实现查表映射时的常见坑
系统对象是另一条路:comm.PSKModulator 和 comm.QAMModulator 对应调制模块,comm.PSKDemodulator 和 comm.QAMDemodulator 对应解调,Simulink 里的同款模块也是它们的图形化封装。不过脚本验证阶段用 qammod 闭环更轻,扫参数也更快。
如果目标平台是 FPGA 或嵌入式,一般做法是用查表实现。QPSK 手写映射就是一个典型的格雷码表:
qpsk_table = [ 1+1j, -1+1j, -1-1j, 1-1j ] ./ sqrt(2); bits2idx = [0 1; 1 1; 1 0; 0 0]; % 按行给出 00 01 11 10 的格雷序查表法的坑在于索引顺序和格雷码排列。MATLAB 的默认结果和手写表格很容易差一个象限旋转或比特重排,表现是星座看起来对了、BER 却恒为 0.5,这种问题定位时优先检查映射表本身。此外,硬件实现里查表通常拆成两个 ROM 分别存 I 路和 Q 路坐标,这时要注意坐标单位是否一致,别一路乘了归一化因子另一路没乘。
3. 从比特流到误码统计:用 MATLAB 搭一条完整的调制解调链路
3.1 链路结构与采样率先定下来
单载波调制解调的完整链路按这个顺序排:随机比特生成、星座映射、上采样、根升余弦成形滤波、加噪声、匹配滤波、符号采样、星座判决、误码统计。这里的采样率策略决定了后面所有参数:把符号速率归一化为 1,sps 表示每个符号的采样点数,仿真采样率就等于 sps。sps 取 8 是常用折中,既能看清成形滤波后的波形,又不至于让循环扫点太慢;sps 取 4 也可以,但留给定时同步的调整余量就小了。
这节链路虽然简单,却是后面 Sync 和估计算法的载体。很多人直接把随机比特扔进 awgn 再 qamdemod,省掉成形滤波和匹配滤波,理论 BER 曲线倒是能对上,但一旦加频偏或者采样偏差,整条链路的行为就和实物对不上了。拿 AD9361 这类射频前端来说,基带出来的信号必然经过脉冲成形,仿真里省了这一步,后面接硬件的代码基本要推翻重写。
3.2 根升余弦成形滤波:rcosdesign 的滚降系数怎么设
成形滤波用根升余弦,收发两端各一个,级联之后等效升余弦,满足奈奎斯特第一准则,符号间无串扰。滚降系数 beta 直接决定带宽和时域拖尾:beta=0.35 是无线通信里最常见的取值,带宽为符号速率的 1.35 倍;beta 越小越省带宽,但滤波器的时域拖尾变长,对定时误差越敏感。
beta = 0.35; sps = 8; % 每符号 8 个采样点 span = 8; % 滤波器覆盖 8 个符号周期 rrc = rcosdesign(beta, span, sps, 'sqrt'); sym_up = upsample(tx_mod, sps); % 每符号插 7 个零 tx_signal = conv(sym_up, rrc, 'same'); % 脉冲成形rcosdesign 返回的滤波器长度是 span*sps+1,这里就是 65 个抽头。'sqrt' 表示返回根升余弦,发射和接收用同一组系数,级联后无 ISI。upsample 之后符号率变成 sps 倍,卷积后每个符号的能量展到相邻采样点上。用 'same' 模式是为了和输入长度对齐,但代价是滤波器边缘效应,所以后续帧结构里要留出保护间隔,或者干脆把前 span 个符号当作训练序列的一部分,接收端再截掉。
3.3 匹配滤波、符号采样与延迟对齐
接收端做同样一组卷积就是匹配滤波,等效于在噪声中最大化符号判决的信噪比。采样时每个符号只取一个点,位置在符号正中间。滤波器群延迟是 span*sps/2 个采样点,对应 span/2 个符号,这段延迟必须在采样后对齐掉,否则误码率下不去。
rx_mf = conv(rx_signal, rrc, 'same'); % 匹配滤波 rx_sym = rx_mf(1:sps:end); % 按符号率抽取 % 用已知导频做延迟对齐,比数延迟更稳 [c, lags] = xcorr(rx_mf, tx_pilot); [~, max_idx] = max(abs(c)); symbol_offset = round(lags(max_idx) / sps); rx_sym = rx_sym(abs(symbol_offset)+1 : end);xcorr 找延迟是工程上更稳的做法。滤波器延迟在理论上可以算,但实际链路里发射机和接收机各有一次卷积,还有上采样相位偏差,数出来的延迟往往差一两个采样点。发一段已知导频序列,接收后做互相关取峰值位置,换算成符号偏移,比手工算可靠得多。注意这个 offset 可能是负的,取绝对值后要从前面截掉。
3.4 对应场景:Simulink 里跑 OFDM 模块 vs 这个单载波脚本
如果你要验证的是 OFDM 而不是单载波,Simulink 里有现成的 OFDM 调制解调模块,配置 FFT 长度、循环前缀和导频间隔就能工作,OFDM 子载波上的调制方式依然可以是 BPSK 到 64QAM,星座映射的结论完全复用。但这里标题给的是单载波调制解调包,区别在于:单载波链路里成形滤波、定时同步、载波恢复这些模块必须显式处理,OFDM 把这些问题分摊到了子载波和循环前缀上;在 Simulink 的模块框图上扫 Eb/N0 参数不如脚本里 for 循环来得方便。常见做法是先在 MATLAB 脚本里把四种调制方式的单载波 BER 曲线跑熟,再决定要不要为 OFDM 场景切到 Simulink 模型。
4. 载波同步、定时恢复与导频估计:让调制解调链路扛住非理想条件
4.1 仿真与实物的分界线:同步模块什么时候必须加
纯 AWGN 仿真里不需要任何同步:符号采样位置是已知的,载波相位也是已知的,qamdemod 直接判决就行。但一旦接收链路换成 AD9361、USRP 这类实际射频前端,三个非理想因素会同时进来:本振频偏导致星座点连续旋转,采样时钟偏差导致最佳采样点缓慢漂移,相位噪声让星座点糊成一片。哪种调制最先崩?永远是星座最密的 64QAM。四种调制共用一套同步参数时,64QAM 能容忍的剩余频偏和相位噪声预算通常只有 QPSK 的十分之一量级。
4.2 Gardner 定时恢复与判决辅助载波相位估计
定时恢复用 Gardner 算法不需要载波相位信息,对 QAM 和 PSK 都适用,实现量小,适合作为第一级同步。它利用每个符号中间点的采样值做误差检测,收敛后把采样点推向眼图张开最大的位置。
% 输入 rx_sync 为 2 倍过采样序列,偶数位置为当前符号点 n = 2:2:length(rx_sync) - 1; ted = real(rx_sync(n) .* (conj(rx_sync(n-1)) - conj(rx_sync(n+1)))); % 一阶环更新采样时刻 mu = 0; mu_step = 0.001; for k = 1:length(ted) mu = mu + mu_step * ted(k); mu = mod(mu, 1); % 限制在 [0,1) 内 end这段代码是 Gardner 误差检测的核心表达式,不是完整环路。误差量反映了符号点两侧采样值的差异,环路据此微调内插位置。实际工程会用比特率定时误差检测器配合 Farrow 结构的多项式内插来实现,但原理一致。注意 Gardner 在 BPSK 和 QPSK 下收敛很快,16QAM 和 64QAM 分别需要更高精度的环路带宽设置,否则星座点越密抖动越明显。
载波相位估计用判决辅助的方式,先硬判决拿到最近星座点,再估计公共相位旋转:
function rx_corr = da_phase_comp(rx_sym, M) dec = qamdemod(rx_sym, M, 'gray', 'UnitAveragePower', true); phase_est = angle(sum(rx_sym .* conj(dec))); rx_corr = rx_sym .* exp(-1j * phase_est); endphase_est 是所有符号相位误差的平均值,硬判决出错时贡献会被其他正确符号稀释,符号数量上千时估计误差落在可接受范围内。这个方案在突发帧的包头估计里很常用,配合训练序列做一次性补偿,比闭环 Costas 环更简单,适合 MATLAB 原型验证。
4.3 导频设计与 SNR 估计的工程做法
导频除了做信道估计,还能直接估 SNR,这是判断解调链路是否健康的第一步。块状导频每隔固定间隔插入一个已知符号,接收端用这些导频做最小二乘信道估计,再反推噪声功率。
pilot_pos = 1:100:nSymbols; % 每 100 个符号插一个导频 tx_pilot = (1+1j) / sqrt(2); % 单导频符号,功率归一化为 1 rx_p = rx_sym(pilot_pos); % 接收端取导频位置 h_est = mean(rx_p .* conj(tx_pilot)); % LS 信道估计 sig_pw = abs(h_est)^2 * mean(abs(tx_pilot).^2); noise_pw = mean(abs(rx_p).^2) - sig_pw; SNR_dB = 10 * log10(sig_pw / noise_pw);这里 sig_pw 是信号分量功率,noise_pw 是总接收功率减去信号功率。需要注意的是,LS 估计假设导频处的信道在一个符号内不变,慢衰落信道下成立,频选信道里导频密度要加密,比如每 10 个符号一组。SNR 估值如果和 awgn 设置的理论值差超过 0.5 dB,优先检查噪声功率的计算里有没有把成形滤波器的增益算漏。
4.4 目标换成 GMSK 或 AD9361 硬件前端时,这套链路怎么改
GMSK 调制解调原理和 QAM 完全不是一条路线。GMSK 属于连续相位调制,信号包络恒定,信息不在星座点的幅度上,所以 QAM 的判决器放上去没有意义,常见做法是用 1 比特差分解调,或者上维特比序列检测。标题这个包如果只是加一个 GMSK 选项,不要试图复用 qamdemod 那套切片逻辑。
换成 AD9361 这类射频前端时,硬件输出的是直流基带 IQ,进同步模块之前先做两步预处理:减去直流偏置,再做 I/Q 不平衡校正。直流偏置用均值估计就能去掉,I/Q 不平衡需要发单音或利用接收数据的统计特性做 Gram-Schmidt 正交化。预处理做完,再进这一章说明的匹配滤波、定时恢复和相位补偿,链路的代码结构基本不用动。
5. 用星座图和 BER 曲线验证四种调制方式的真实表现
5.1 频偏和相位噪声在星座图上的判读
星座图是用来看调制解调健康度最直接的工具。给链路加一个固定频偏,QPSK 的四个点会匀速旋转,因为每个符号经历的相位变化量恒定;点数少,扫一眼就能看出旋转方向和速度。64QAM 加同样频偏,点的轨迹变成一个粗圆环,边界判读变难,从圆环半径大致可以估计剩余频偏占符号速率的比例。
foff = 200; % 200 Hz 频偏 T = 1 / symbol_rate; % 符号周期 phase = exp(1j * 2 * pi * foff * (0:length(rx_sym)-1) * T); rx_rot = rx_sym .* phase(:); scatterplot(rx_rot);这段代码模拟了频偏对星座图的影响。如果 foff*T 小于 0.01,QPSK 还能看到四个团簇;64QAM 已经糊成一圈。工程上从星座图反推频偏的方法是数单位时间内星座点划过多少圈,这个值等于频偏除以符号速率,用来判断当前残留频偏是否在同步环路的容忍范围之内。
5.2 一键扫 SNR 的四组 BER 对比脚本
验证调制方式差异最直观的输出是四条 BER 曲线。把 2.2 到 3.3 的链路封装成一个函数 run_link(M, EbN0dB),外面套循环扫描,固定随机种子保证可复现:
EbN0dB = 0:2:16; M_list = [2 4 16 64]; figure; hold on; for i = 1:numel(M_list) ber = zeros(size(EbN0dB)); for k = 1:numel(EbN0dB) rng(42); % 固定噪声种子,回归可复现 ber(k) = run_link(M_list(i), EbN0dB(k)); end semilogy(EbN0dB, ber, 'o-', 'LineWidth', 1.2); end legend('BPSK', 'QPSK', '16QAM', '64QAM'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER');这组曲线跑完,BPSK 和 QPSK 会重合在同一条曲线上,16QAM 在 8~9 dB 处开始拉开差距,64QAM 再落后几个 dB。这个差异不是设备好坏的问题,是调制方式本身的数学性质决定的。保存这组曲线作为基线,后面改链路代码时重新跑一次,对比偏差,就能快速发现回归问题。
5.3 验证调制解调模块的几件小事
链路调试时固定几件事可以省大量时间。第一,仿真入口固定 rng 种子,否则每次跑出来的 BER 都有抖动,没法判断改动效果。第二,无噪声通道下解调结果必须和发射比特完全一致,这一步过不了说明调制映射或延迟对齐还有问题。第三,BER 曲线和理论值的偏差控制在 0.5 dB 以内,偏差更大时常见原因是 SNR 定义不一致、滤波器延迟没对齐、或者归一化因子用错。第四,用 EVM 做单符号质量门限,64QAM 的 EVM 超过 12% 基本不能可靠解调,16QAM 可以放宽到 20%。
evm = sqrt(mean(abs(rx_sym - tx_ref).^2) / mean(abs(tx_ref).^2)) * 100;EVM 的计算公式里 tx_ref 是归一化后的发射星座点,rx_sym 是补偿后的接收符号。这个指标和星座图配合使用,比单看 BER 能更快定位问题出在同步、滤波还是 mapping。把这四件事固化到链路代码的注释和自检脚本里,后续不管是换调制方式还是接 AD9361 前端,回归验证都有据可依。
本文还有配套的精品资源,点击获取