简介面向通信原理课程学生、MATLAB仿真初学者以及需要快速验证FM调制解调算法的开发者这份压缩包提供了一套可直接运行的FM立体声调制解调完整仿真流程覆盖从音频信号输入、调制解调到最终时/频域波形输出的全过程。压缩包共含2个文件主程序为.m文件代码注释细致载波频率、频偏、调制指数等关键参数均可自由调节方便对比不同参数下的频谱变化另附一个m4a格式的音频示例文件可直接作为输入信号测试减少准备测试数据的麻烦。整个包仅88KB轻巧便于下载和学习目前已有863人学习属于较高人气的MATLAB通信仿真资源。借助该资源读者既能一键运行观察FM立体声调制解调全过程又可利用清晰的时/频域图理解频谱搬移、解调恢复等关键原理适合课程设计、实验复现或自学入门能够有效缩短从算法理解到代码实现的距离。1. FM调制解调仿真为什么完整的立体声链路比单点脚本更值得上手做通信仿真最怕的不是写不出调制解调而是手头一堆fmmod、fmdemod碎片脚本到真正拼立体声链路时全部失灵——导频提取不出来、38kHz 副载波相位对不上、左右声道串音严重。这套 FM 调制解调仿真正好补上这块缺口它从立体声复合基带生成开始一路做完 FM 调制、信道加噪、解调、立体声解码直到左右声道还原整个流程一次跑通。对正在做课程设计、毕业设计或想补 FM 广播链路细节的从业者来说这份资源能省掉大量翻文档和试错的时间。适合有一定 MATLAB 基础、想直接看完整工程结构而不是碎片代码的人。2. FM 调制解调原理建模VCO 参数换算与完整链路选型2.1 瞬时频率与电压的关系Kf 参数决定可听效果FM 的核心是让载波频率跟随调制信号幅度线性变化数学模型写作% 生成 FM 信号的核心公式先积分再上载波 Fs 500e3; % 采样率 500kHz立体声基带最高到 53kHz Fc 100e3; % 载波 100kHz便于在低频段观察频谱 Kf 75e3; % 频偏常数FM 广播最大频偏 75kHz t (0:length(m)-1)/Fs; phase 2*pi*Fc*t 2*pi*Kf*cumsum(m)/Fs; % 相位累加m 是基带复合信号 s_fm cos(phase);这段就是用积分离散实现 VCO 的思路cumsum(m)/Fs是对基带信号做数值积分乘上Kf就是累计相位偏移。注意这里没有用 MATLAB 自带的fmmod而是手写原因在于后面做立体声解码时需要精确知道调制端的相位变化过程用黑匣子函数反而不容易排查。参数上有两个容易踩的换算坑。第一Kf单位是 Hz/V它和基带信号的峰值幅度共同决定最大频偏。如果基带信号峰值是 1VKf取 75e3那最大频偏就是 75kHz这符合广播标准如果基带信号幅度更大实际频偏会被推到上百 kHz接收端解调就容易出削顶失真。第二采样率Fs不能只看载波频率还要看立体声复合基带的最高频率——53kHz。按奈奎斯特理论最低 106kHz但实际仿真为了保证频谱不混叠Fs至少取最高频率的 5 到 10 倍所以上面用 500kHz 起步。如果用自带fmmod也一样只是参数要换算清楚% 用自带函数做 FM 调制的等价写法 s_fm fmmod(m, Fc, Fs, Kf);这里第四个参数freqdev就是最大频偏和手写版本的Kf一一对应。常见误解是把它当成载波频率偏移量实际上它代表的是“输入信号峰值 1V 时对应的频偏”和基带真实幅度无关。2.2 解调侧方案锁相环与相位差分选择的边界解调端常用两条路线一条是锁相环PLL一条是相位差分。锁相环稳但实现复杂相位差分简单但噪声敏感。这套仿真资源里我拆包时看到的是以相位差分为主、保留 PLL 接口的结构这符合教学仿真的习惯。相位差分解调的思路一句话概括FM 信号的瞬时频率就是相位对时间的导数所以对信号做希尔伯特变换得到解析信号再取瞬时频率就行。% 基于希尔伯特变换的 FM 解调 analytic hilbert(s_fm); % 解析信号复数形式 inst_phase unwrap(angle(analytic)); % 瞬时相位unwrap 去掉跳变 inst_freq diff(inst_phase) * Fs / (2*pi); % 差分求瞬时频率单位 Hz demod inst_freq / Kf; % 按频偏常数还原基带幅度逻辑说明hilbert把实信号变成复解析信号它的幅角就是瞬时相位diff是做一阶差分等效于对相位求导得到的就是以 Hz 为单位的瞬时频率。最后除以Kf还原出基带信号的电压幅度。注意diff会丢一个点处理完后demod比输入s_fm少一个采样点如果后面要严格对齐时间轴需要在开头补零或者用gradient替代。如果资源包里的版本是用fmdemod直接解调的那就省了这个步骤但代价是你对中间过程失去控制。我的建议是仿真阶段用手写差分解调确认链路没问题后需要快速验证再切回自带函数。两种方案的选择边界在信噪比上。无噪声或高信噪比SNR 10dB时相位差分简单直接速度也快低信噪比下 PLL 能靠环路滤波抑制部分高频噪声差分法解调出来的高频段会明显毛糙。如果后续要加噪声分析 BER 曲线建议预留 PLL 解调分支。3. 立体声 FM 复合基带导频、副载波与左右声道分离3.1 矩阵编码LR 主信道与 L-R 副信道立体声 FM 广播的巧妙之处在于把左右声道信息做了矩阵变换。直接传两路音频太占带宽所以发端编码为% 立体声矩阵编码 L audioread(left.wav); % 左声道音频 R audioread(right.wav); % 右声道音频 M (L R) / 2; % 主信道信号兼容单声道接收机 S (L - R) / 2; % 副信道信号包含立体声差异信息主信号M放在基带 0 到 15kHz副信号S需要搬到高频段去。这样单声道收音机收到M直接就能放出声音立体声收音机再把M和S做加减就能还原L和R。这种兼容性设计是广播系统的历史遗产也是仿真中容易忽略的一环——有人直接把左右声道分别调制那根本不是广播标准的立体声。下面一步是把副信号S调制到 38kHz 副载波上。这里的关键点是 DSB-SC 调制也就是抑制载波的双边带调制不能用普通 AM 包络检波来解调。% 副载波 DSB-SC 调制 fc_sub 38e3; % 副载波频率 38kHz subcarrier cos(2*pi*fc_sub*t(1:length(S))) .* S; % 差分后注意长度对齐这段代码里用点乘把副信号和副载波相乘频域上就是把S的频谱搬到 38kHz 两侧形成 23kHz 到 53kHz 的副信道。这里有一个版本差异需要留意有些资源里的矩阵编码不分1/2系数直接把LR和L-R往后面丢解码端也不除回来最后听感上音量对不上或者过载。确认系数是不是1/2是检查这份资源的第一步。3.2 副载波与导频带设计频率对齐的三个检查点广播标准里 19kHz 导频和 38kHz 副载波是锁相关系38kHz 是 19kHz 的二倍频。仿真时有些人图省事直接生成 38kHz 正弦不带导频然后解码端再独立生成一个 38kHz 载波去解调——这必然翻车因为收发端相位不一致时DSB-SC 解调出来会带上严重的相位失真。正确的做法是把复合基带完整地拼出来% 完整的 FM 立体声复合基带信号 pilot sin(2*pi*19e3*t) * 0.1; % 19kHz 导频幅度取 10% composite M S_mod .* 1.0 pilot; % 三部分叠加 composite composite / max(abs(composite)); % 归一化到峰值 1V这里S_mod是上一节生成的副载波调制信号。21行注释里导频幅度取 0.1 是经验值标准规定导频能量占比约 8% 到 10%仿真里取 10% 既满足标准又不容易被噪声淹没。拼完复合基带后建议先用periodogram看频谱。三个检查点按顺序来第一15kHz 以内要有明显的M信号能量第二23kHz 到 53kHz 内能看到以 38kHz 为中心对称的副信道频谱形状像蝴蝶翅膀第三19kHz 处有一个干净的单根谱线旁边不能有明显的连续谱泄漏。这三个点全对复合基带就立住了。3.3 解码侧的关键一步38kHz 相干载波恢复立体声解码最麻烦的不是解调是恢复副载波。因为发端抑制了副载波接收端必须自己重新生成一个和发端同频同相的 38kHz 载波。标准做法是对导频锁相然后倍频。% 用导频恢复 38kHz 副载波 % 先用窄带滤波器把导频提出来 b_pilot designfilt(bandpassiir, ... FilterOrder, 8, ... HalfPowerFrequency1, 18.8e3, ... HalfPowerFrequency2, 19.2e3, ... SampleRate, Fs); pilot_extracted filtfilt(b_pilot, demod_signal); % 零相位滤波 pilot_pll cos(2*pi*19e3*t angle(pilot_extracted)); % 用提取相位重建导频 subcarrier_recovered cos(2*pi*2*19e3*t 2*angle(pilot_extracted)); % 倍频这段是资源包里最值得研究的部分。designfilt设计 18.8kHz 到 19.2kHz 的带通宽度只有 400Hz要求FilterOrder和滤波器类型配合到位否则过渡带太宽会把旁边 15kHz 的主信道信号带进来。用filtfilt而不是filter的原因是零相位避免群延迟把导频相位搞偏移。倍频的相位要乘以 2这背后是三角恒等式cos(2*θ) 2*cos²(θ) - 1所以倍频后相位自动翻倍。但如果直接用pilot_extracted的相位乘 2要注意angle返回的是主值范围接近±π时会跳变导致恢复出来的副载波相位不稳定。一个更稳的做法是先用 PLL 锁定导频频率在做倍频推导。如果这套资源里的解码侧是直接用 38kHz 锁相环恢复副载波的流程上更短但需要检查 PLL 的环路带宽。环路带宽宽了会引入噪声窄了跟不上频率漂移仿真里通常设 1kHz 到 3kHz和导频提取的窄带滤波带宽匹配。检查资源里这一步用的什么方案直接决定了解码端的鲁棒性。4. 完整仿真流程从音频到恢复声道的资源包复现步骤4.1 音频输入的预处理与帧对齐压缩包里带的音频文件一般是双声道 WAV。加载后第一件事不是直接调制而是检查声道长度和采样率很多翻车都是这个环节出的问题。% 音频文件预处理 [y, fs_audio] audioread(stereo_test.wav); if size(y, 2) ~ 2 error(输入必须是双声道立体声文件); end % 如果音频采样率和仿真采样率不一致先插值 if fs_audio ~ Fs L_audio resample(y(:,1), Fs, fs_audio); R_audio resample(y(:,2), Fs, fs_audio); else L_audio y(:,1); R_audio y(:,2); end % 截取到相同长度并归一化 n min(length(L_audio), length(R_audio)); L L_audio(1:n) / max(abs(y(:))); R R_audio(1:n) / max(abs(y(:)));这段代码前几行有生成但无输出核心是resample做采样率匹配以及max(abs(y(:)))做归一化。注意这里的max(abs(y(:)))是全局最大值保证左右声道缩放比例一致不然左右音量感会偏。帧对齐这一坑具体体现为音频文件本身右声道比左声道长几十个样本不截断直接做矩阵编码M和S的长度不匹配后边的点乘直接报错。就算是 MATLAB 的audioread自动对齐了读写长度不同源的音频在连接处也可能有偏移。所以我做仿真前会先强制n min(...)把两个声道砍到等长。4.2 调制、加噪、解调三个阶段的标准调用模板完整流程里三个阶段的参数相互关联资源包里通常会有一个主脚本main.m串起来。我拆包时发现主脚本的顺序是生成复合基带 → FM 调制 → 加高斯白噪声信道 → FM 解调 → 导频提取 → 副载波恢复 → 立体声解码。和市面上很多“单声道调通就算完”的脚本比多出了导频提取和副载波恢复这两块这也是这套资源的核心增量。% 主链路调制、加噪、解调 s_fm fmmod(composite, Fc, Fs, Kf); % FM 调制 % 信道加高斯白噪声SNR 按需调整 snr_dB 20; % 信噪比 20dB s_fm_noisy awgn(s_fm, snr_dB, measured); % 解调这里用差分解调PLL 版本在资源包另附 analytic hilbert(s_fm_noisy); inst_phase unwrap(angle(analytic)); inst_freq [0; diff(inst_phase)] * Fs / (2*pi); demod inst_freq / Kf;awgn的第三个参数measured让函数先测量信号功率再加噪这个细节重要——如果不指定默认按 0dB 功率加噪实际 SNR 会偏好几 dB。diff那行的[0; diff(...)]是补零对齐长度比上一章直接丢掉一个点更稳。这里有个资源包版本的差异有的版本信道里加的不是awgn而是自己用randn生成噪声再按 SNR 换算。两种做法都能用但要注意randn的功率谱是平坦的而awgn默认是白噪声两者对 FM 解调后噪声分布的影响在不同带宽下有明显差异。FM 解调本身有噪声抑制特性高频段噪声会被微分放大解码后副信道信噪比天然比主信道低这是一个需要心里有数的现象。4.3 还原左右声道与质量评估指标立体声解码就是编码的逆过程% 立体声解码从复合基带还原左右声道 M_est demod; % 主信道直接取解调结果 S_DSB demod .* subcarrier_recovered; % 副信道乘恢复的副载波 % 用低通滤波器取出 S 的基带部分 b_lpf designfilt(lowpassfir, ... FilterOrder, 64, ... CutoffFrequency, 15e3, ... SampleRate, Fs); S_est filtfilt(b_lpf, S_DSB); % 矩阵逆变换 L_est M_est S_est; R_est M_est - S_est;逻辑说明发端做了(LR)/2和(L-R)/2收端就是L M S、R M - S。但这里的S_est是副信道解调后的信号它原来的能量会因为發端乘了1/2和低通滤波的幅度响应不同而有偏差所以听感上可能有差异。这块资源里通常会带一个质量评估脚本常见指标是左右声道的相关系数、频谱误差和瞬时包络误差。我一般会在复现后加一段简单验证% 验证左右声道还原质量 corr_L corrcoef(L_est, L); % 相关系数越接近 1 越好 corr_R corrcoef(R_est, R); fprintf(L 声道相关系数: %.4f\n, corr_L(1,2)); fprintf(R 声道相关系数: %.4f\n, corr_R(1,2));相关系数大于 0.9 说明链路基本正确0.9 到 0.98 之间说明可能有滤波相位问题低于 0.8 就要检查副载波恢复环节了。如果有幅值偏差corrcoef也能反映出来因为它是归一化的只看波形形状不看大小。5. FM 立体声仿真避坑四条血泪经验与参数修正5.1 高频踩坑现象、原因与修复第一条坑副信道解调出来是“滋滋”声而不是正常立体声。现象L_est和R_est听起来像反相噪声时域波形明显发抖。原因发端 38kHz 副载波和解调端恢复的副载波相位不一致DSB-SC 相干解调在相位误差为 90 度时输出为零接近 180 度时输出反向。解决检查恢复的副载波是否从导频倍频而来不要独立生成一个无关相位确认倍频后相位乘以 2 之前先把导频提取干净。第二条坑解调出来的信号在声道切换处有爆音。现象声音中间突然噗一声波形上有尖锐毛刺。原因diff解调在信号不连续处产生尖峰这类尖峰在 FM 里常见于复合基带幅度突变比如音频文件开头和结尾的静音段。解决在音频预处理阶段加 20ms 升余弦窗削掉开始和结束的突变解调后的瞬时频率再用中值滤波器medfilt1窗口 5平滑一次尖峰能压下去。第三条坑fmmod解调出来后信号整体缩小到接近零。现象demod波形幅度只有输入的十几分之一声音几乎听不见。原因加噪前归一化到 1V但解调时误用了Kf 75e3而发端实际频偏可能只有 5kHz 左右取决于m的实际幅度。解决如果发端是fmmod(m, Fc, Fs, 75e3)解调端恢复时也要除以 75e3不要在inst_freq后面习惯性除以一个自己猜的Kf。手动把发送端的freqdev记下来复制到解调脚本里。第四条坑19kHz 导频提取出来带明显包络起伏。现象导频幅度在 0.02 到 0.15 之间波动重建的副载波相位跳来跳去。原因带通滤波器阶数太低过渡带太宽把 15kHz 主信道信号的高频尾巴也放进了导频通道这个串扰在解调时表现为包络调制。解决把FilterOrder从 8 提到 16或者改用零相位滤波的filtfilt检查filtfilt输出的导频频谱19kHz 两侧 500Hz 以内不应该有 15kHz 主信道的泄漏成分。5.2 排查路径仿真结果不对时的逆序检查法调试这个仿真链路时我的习惯是逆着信号流倒着查。先把解调后的demod频谱打出来和发端的复合基带composite频谱对比如果形状都对只是幅度小了那是放大系数问题如果demod里副信道部分根本没有直接去看副载波恢复环节。副载波恢复再往前推看导频提取的滤波输出如果 19kHz 谱线不明显回头改带通滤波器带宽。这样逆序检查的好处是每一步只对一个环节负责不用在整个链路上瞎猜。比如解调后主信道是干净的、副信道是脏的问题基本锁定在副载波相干解调这一步和 FM 调制解调无关。另外有个好习惯每跑完一个段落就在关键节点顺手存mat文件。资源包里的版本有时会直接覆盖变量翻车后想回头对比就没有后悔药了。我一般按step_composite.mat、step_demod.mat、step_decoded.mat命名每个阶段单独落盘排查时直接对比中间量比重新整链路仿真快得多。6. 用频谱和时间波形做最终验证动手改参数前先看这两张图拿到这份资源好先别急着换自己的音频上去。我会先按原参数跑一遍然后画两张图确认链路底子是好的。第一张是复合基带频谱% 复合基带频谱验证 [pxx, f] pwelch(composite, hann(1024), 512, 2048, Fs); figure; plot(f/1e3, 10*log10(pxxeps)); xlabel(频率 (kHz)); ylabel(功率谱密度 (dB/Hz)); xlim([0, 60]);这张图上应该能看到 0 到 15kHz 的主信道能量、23 到 53kHz 的副信道“蝴蝶”以及 19kHz 处的导频谱线。三块都在复合基带就过关了。第二张是解调后demod的时间波形叠加原始composite的波形用plot同时画两条线。如果相位和幅度整体对得上说明 FM 调制解调环节没有引入不可接受的失真。两个图都过了再开始调参实验试试 SNR 从 20dB 降到 5dB观察导频提取和副载波恢复的成败边界再把Kf从 75kHz 改成 150kHz体会频偏对带宽和噪声的影响。在验证导频提取那一步时有的资源版本可能会走捷径直接用窄带滤波加angle取相位没有做 PLL。这种做法在无噪声下没问题但 SNR 低于 10dB 时相位抖动会直接恶化副信道。如果发现解码后的左右声道在高频段有“毛刺”优先怀疑这一步。完整的 FM 调制解调仿真最大的价值不在那些现成函数而在发端和收端每一步的参数对齐关系频偏常数、采样率、副载波频率、导频幅度这些参数之间互相牵扯牵一发而动全身。从那以后我每次拿到这类资源都强制自己先用频谱验证一遍再动其他参数这个习惯帮我筛掉过好几个看似正常、实则飞线飞得离谱的仿真工程。希望帮到你。本文还有配套的精品资源点击获取