简介这是一份面向通信原理学习者和MATLAB使用者的FM调制解调仿真资料针对立体声广播场景解决从原理理解到代码实现的完整验证问题适合正在学习模拟调制、需要动手复现FM系统的本科高年级学生与工程入门人员。包内共2个文件包含1个m脚本和1个m4a音频文件前者承载完整的立体声FM调制与解调流程后者可用于试听或对照验证处理结果压缩包整体约88KB体量轻便、便于快速运行与二次修改。代码配备详细注释调制参数可自由调整方便读者替换参数后观察效果差异时域与频域展示完整能帮助理解基带信号、载波调制与解调恢复各环节的波形特征。该资源已有868人学习下载可作为课程实验、仿真练习或自学参考的实用素材。1. 一段“左右拉不开”的波形促使我排查 19 kHz 导频接手 A 同学那份 MATLAB FM 系统仿真时我起初以为只是滤波器没调好。左右声道送入两段完全不同的音频播放出来却仍是单声道人声固定在中间位置前后拉不开。查了两小时才发现整条链路里完全没有 19 kHz 导频和 38 kHz 副载波那一段立体声编码被跳过解调端只能拿到 LR。这份 FM 调制解调仿真资源补的正是这段完整过程预加重、和差矩阵、DSB-SC 副载波调制、调频、AWGN 信道、鉴频、导频恢复、和差还原、去加重最后用分离度和信噪比把每一步量化。做通信原理课程设计的人可以拿它把课本上的贝塞尔函数和频谱图对应到波形想往上叠多径、RDS 这类功能的熟手也能把它当作基础框架。2. 链路总览与参数定标把 FM 立体声拆成四段能单独验证的子链路整条链路最麻烦的地方是任何一段出错最终表现都是“声音不对”。你听到失真可能是编码端幅度超了也可能是鉴频器进了非线性区还可能是解码端本地载波相位歪了。所以我拆这类仿真包时第一件事是把它切成能单独跑、单独看的四段每段都有自己的验证手段出问题能立刻定位到哪一段。2.1 信号流两路音频到左右声道复现要经过什么按导频制也叫 Zenith-GE 制式走发射端是这么几步。两路音频先做预加重提升高频再过一个和差矩阵变成 SLR 和 DL−RS 留在基带 0~15 kHzD 搬到 38 kHz 副载波上做双边带抑制载波调制占据 23~53 kHz然后插一根 19 kHz 导频代表“我是立体声台副载波相位以此为准”三块拼起来就是 MPX 复合基带信号最后整体调频搬到射频载波上。接收端反着来鉴频器把瞬时频率还原成 MPX窄带滤波提取 19 kHz 导频倍频得到 38 kHz 本地载波用它与 MPX 相乘解调出 D低通取出 S再做和差运算 LSD、RS−D最后去加重、降采样回音频率。这条链上有三个必须自检的中间点我一般在脚本里都会存一版波形和频谱MPX 频谱是否呈现“基带 19 kHz 单峰 23~53 kHz 双边带”这三块鉴频输出是否和原始 MPX 基本重合解码出的 L/R 在只有左声道输入时右声道是否足够小。三个点都对剩下的调参才有意义。2.2 采样率、载频与频偏一组不能随意设定的参数参数表这类东西抄错一个数整篇都白跑。下面这组是我反复试下来比较稳的取值也是这类仿真最常采用的一组参数取值为什么是这个数音频采样率 fs_a48 kHz覆盖 15 kHz 音频留足过渡带仿真采样率 fs480 kHz见下面的带宽推算中频载波 fc120 kHz避开直流频谱画出来干净峰值频偏 Δf75 kHz广播标准调制度 100%导频 fp19 kHz制式规定不能改副载波 fsc38 kHz严格等于 2×fp预加重时间常数 τ50 μs国内调频广播常用部分地区用 75 μs音频带宽15 kHz主信道与差信道带宽相同信道间隔200 kHz卡森带宽约 180 kHz留保护带采样率最容易出错。调频后信号占据 fc±(Δffm)取 fc120 kHz、Δf75 kHz、fm15 kHz最高到 210 kHz最低到 30 kHz所以 fs 至少要 420 kHz。很多人图省事用 400 kHzNyquist 只有 200 kHz载频一抬到 120 kHz 立刻混叠频谱上会多出一堆说不清来源的杂散。提示如果非要用 400 kHz就把 fc 压到 100 kHz此时最高边到 190 kHz只剩 10 kHz 余量滤波器的过渡带必须做得非常陡稍有泄漏就会在解调输出里听到混叠噪声。另外 fc 不要取 fs 的整数分之一比如 fs400 kHz、fc100 kHz两者刚好是 4 倍关系采样后的频谱会周期性地重叠在一起做频谱分析时很难看清副载波边带。避开整数比图会干净很多。2.3 目录结构这类仿真包我一般怎么分文件一个大 main.m 从头写到尾跑通没问题改起来非常难受。我习惯按链路分段分文件每个文件只做一件事函数签名统一成「输入信号 参数结构体输出信号 可选调试量」fm_stereo_sim/ ├── main.m % 顶层脚本串链路、出图、算指标 ├── params.m % 所有参数集中在这改一处全链路生效 ├── gen_audio.m % 生成或读取双声道测试信号 ├── stereo_encode.m % 预加重 和差矩阵 DSB-SC 导频 ├── fm_modulate.m % 相位积分调频 ├── channel_awgn.m % 信道加噪与信噪比标定 ├── fm_demodulate.m % 鉴频输出归一化 MPX ├── stereo_decode.m % 导频恢复 相干解调 和差 去加重 └── metrics.m % 分离度、SNR、THD参数结构体这一层别省。我试过把 fs、fc、Δf 散在各个函数里后来想把载频从 100 kHz 换成 120 kHz翻了六个文件才改干净还漏了一处滤波器归一化频率没跟着变结果带通中心跑到 19 kHz 以外导频直接提取不到。集中之后改参数只动 params.m 一处代价是每个函数多一个入参非常划算。3. 发射端拆解预加重、和差矩阵与 38 kHz 副载波怎么拼出 MPX发射端是整个仿真的地基。这里幅度一旦超了后面鉴频器再好也救不回来——调频是非线性过程过调制度会产生无法用滤波去掉的失真。所以这一段我会写得比接收端更谨慎每一步都留一个可观测的量。3.1 预加重与和差矩阵先把 L/R 变成 S 和 D预加重不是可有可无的装饰。调频鉴频之后噪声功率随频率平方增长高频段的信噪比会明显变差所以在发射端把高频抬起来、接收端再压回去这一抬一压噪声被压了两次音频没变。时间常数 50 μs 对应的转折频率约 3.2 kHz也就是说 3.2 kHz 以上开始以 6 dB/倍频程提升。fs_a 48e3; % 音频采样率 tau 50e-6; % 预加重时间常数 % H(s) 1 s*tau用双线性变换离散化 % 直接做差分近似在高频段相位误差偏大双线性更稳 [b_pre, a_pre] bilinear([tau 1], 1, fs_a); % 音频先统一到仿真采样率后面所有模块共用 fs L resample(L0, fs, fs_a); R resample(R0, fs, fs_a); Lp filter(b_pre, a_pre, L); % 预加重 Rp filter(b_pre, a_pre, R); S (Lp Rp) / 2; % 和信号留在基带 0~15 kHz D (Lp - Rp) / 2; % 差信号准备搬到 38 kHzbilinear([tau 1], 1, fs_a)里分子系数是[τ, 1]对应 s 域的一阶零点在 −1/τ分母是常数 1所以离散化后是一个一阶高通型响应直流增益为 1高频增益随频率线性上升。resample自带抗混叠滤波别用interp1硬插值那种做法在 15 kHz 附近的镜像会直接混进主信道。矩阵那里除 2 是为了让 S、D 的峰值不超过 1后面 MPX 归一化时才有余量。如果这里不除 2两路满幅音频相加峰值到 2等到 MPX 归一化时会整体压下去信噪比吃亏。3.2 DSB-SC 调制与导频插入MPX 的三块频谱差信号搬到 38 kHz 用的是双边带抑制载波不是普通 AM。抑制载波的原因很直接载波本身不携带信息却要占掉宝贵的频偏资源而且会在解调端引入直流分量破坏分离度。fp 19e3; fsc 2 * fp; % 副载波必须是导频的二次谐波 t (0:N-1). / fs; pilot cos(2*pi*fp*t); sub cos(2*pi*fsc*t); % 与导频同源不要另起一个相位 mpx S D .* sub; % 主信道 副信道 mpx 0.9 * mpx / max(abs(mpx)); % 归一化给导频留 10% 的频偏 mpx mpx 0.1 * pilot;sub和pilot必须同源这不是代码风格问题是制式的硬约束。导频的全部意义就是告诉接收端“副载波的相位是 2φ”接收端把导频平方一下2cos²(x) 1 cos(2x)自动得到 2φ 相位的 38 kHz。如果你在发射端另起一个相位随机的 38 kHz接收端恢复出来的载波和它对不上相干解调出来的 D 就会带一个 cos(Δφ) 的衰减因子极端情况下直接归零。归一化到 0.9 再叠加 0.1 的导频是为了保证 MPX 峰值不超 1。广播规范里导频占总频偏的 8%~10%取 10% 是常见做法。如果这里贪图分离度把导频压到 3%接收端的窄带滤波器就不好提取了。编码完了一定要看一眼频谱这是最省时间的自检[Pxx, f] pwelch(mpx, hann(4096), 2048, 8192, fs); plot(f/1e3, 10*log10(Pxx)); xline(19); xline(38); % 标出导频和副载波位置 xlabel(频率 (kHz)); ylabel(功率谱 (dB));正确的图应该是三块0~15 kHz 一块连续的基带19 kHz 一根又细又高的单峰23~53 kHz 一块以 38 kHz 为中心、中间塌陷的双边带。15~19 kHz 和 19~23 kHz 这两段是保护带必须是空的。如果 15~23 kHz 之间鼓起一片说明和信号没滤干净或者导频 waveform 不是纯正弦。3.3 调频实现为什么我用 cumsum 而不是 fmmod调频公式本身简单角频率是载频加上调制信号的线性项相位就是它的积分。难点全在数值积分上。fc 120e3; % 中频载波 df 75e3; % 峰值频偏 kf df; % mpx 峰值已归一化到 1kf 数值上等于峰值频偏 mpx_dc mpx - mean(mpx); % 先去掉直流否则积分后变成斜坡 int_m cumsum(mpx_dc) / fs; % 数值积分 ∫m(τ)dτ s_tx cos(2*pi*fc*t 2*pi*kf*int_m);mean(mpx)那一步很多人会漏。MPX 里只要有一点残余直流cumsum就会把它积成一个随时间线性增长的斜坡等效于载频在缓慢漂移鉴频输出里会出现一条低频漂移的基线去加重之后就是一阵“咚咚”的闷响。我在第一次跑的时候就遇到了这个问题盯着时域波形看了半天最后发现是导频的 cos 在截断时不是整数个周期留下了直流量。调频灵敏度kf直接取 75e3是因为 mpx 峰值已经被归一化到 1所以瞬时频偏峰值恰好等于 kf。如果你跳过了 3.2 节的归一化这里就必须改成kf df / max(abs(mpx))否则一超频偏鉴频输出会限幅THD 立刻被拉起来。不用fmmod的理由有三个它内部同样是积分但初相位不好控它不方便在积分前加去直流这一步还有就是调试时拿不到中间相位出了问题只能当成无法观察的过程。自己写三行什么都看得见。4. 接收端拆解鉴频、导频恢复与和差还原的完整链路接收端的信息量比发射端大因为它要在一个已经混了噪声的信号里把三块频谱重新分开。这一段我按信号流顺序拆成四小节每一节的输出都是下一节的输入可以单独冻结下来看。4.1 AWGN 信道与信噪比标定snr_if 20; % 中频带内信噪比dB s_rx awgn(s_tx, snr_if, measured);measured这个参数不能省。不带它的时候awgn假设输入信号功率为 0 dBW如果你的s_tx是单位幅度余弦实际功率只有 −3 dBW 左右加出来的噪声功率会比预期高最后算出的信噪比整体偏了 3 dB 还找不到原因。更容易出错的是信噪比的定义。这里设置的 snr_if 是仿真带宽fs/2内的信噪比而解调后音频带宽只有 15 kHz两者相差10log10(240/15) ≈ 12 dB。也就是说中频 20 dB 的信噪比落到音频带上大约是 32 dB 加上调频带来的处理增益。做理论对比的时候如果不换算曲线会整体平移十几 dB看着像“仿真错了”其实只是定义没对齐。提示要跟教科书上的 C/N 曲线对比就把目标载噪比先折算成仿真带内信噪比再喂给awgn并在图例里写清楚是哪种定义不然后面自己都分不清哪条是哪条。4.2 鉴频器三种写法相位差分、Hilbert 与 PLL 的取舍鉴频的本质是求瞬时频率也就是相位的导数。离散域里最稳的是相位差分法% 相邻样点相乘取共轭角度就是相位差 d angle(s_rx(2:end) .* conj(s_rx(1:end-1))); fm d * fs / (2*pi); % 换算成瞬时频率Hz fm fm - mean(fm); % 减掉载频对应的直流 mpx_hat fm / kf; % 除以调频灵敏度回到归一化 MPXangle对幅度不敏感这是它最大的好处——信道带来的幅度起伏、滤波器带来的增益变化全都不影响结果。代价是它对相位跳变很敏感一旦相邻样点相位差接近 ±πangle会在 ±π 处来回翻输出一片锯齿。Hilbert 变换的写法更适合做理论分析z hilbert(s_rx); % 构造解析信号 ph unwrap(angle(z)); % 解卷绕得到连续相位 fm [0; diff(ph)] * fs / (2*pi); % 差分求瞬时频率它的问题是unwrap的可靠性和每样点相位增量直接挂钩。载频 120 kHz、采样率 480 kHz 时每样点基础相位增量是 π/2加上 75 kHz 频偏最多到 0.8π还在安全区。但你要是把 fc 抬到 150 kHz每样点增量就超过 πunwrap会往反方向绕输出变成一段一段的斜坡看着像信号被“折叠”了。这个坑在 5.2 节还会展开。PLL 是工程上真正在用的方案仿真里也能写核心就是一个相位检测器加一个环路滤波器% 二阶环Kp/Ki 决定锁定速度和稳态相差 Kp 0.05; Ki 0.001; phase_hat 0; err_int 0; baseband zeros(size(s_rx)); for n 1:length(s_rx) err s_rx(n) * sin(2*pi*fc*t(n) phase_hat); err_int err_int Ki * err; phase_hat phase_hat Kp * err err_int; baseband(n) err; % 环路滤波后即为解调输出 end选哪个做单次仿真、参数不极端相位差分三行搞定够用。要做低信噪比下的门限效应分析或者载频会漂移的场景PLL 才值得写。Hilbert 我一般只拿来当交叉验证两路结果对不上就说明有一路出问题了。4.3 导频恢复与 38 kHz 相干解调这一段是立体声解码的核心也是分离度能不能上去的分水岭。% 1) 窄带带通抠出 19 kHz 导频 bp19 fir1(512, [18.4e3 19.6e3]/(fs/2), bandpass); p filtfilt(bp19, 1, mpx_hat); % 2) 平方产生 38 kHz相位自动锁定 p2 p.^2; % 2cos^2 1 cos(2x) bp38 fir1(512, [37e3 39e3]/(fs/2), bandpass); c38 filtfilt(bp38, 1, p2); % 3) 归一化到 ±1否则解调出来的差信号幅度极小 c38 c38 / max(abs(c38)); % 4) 相干解调出差信号系数 2 补 cos*cos 的 1/2 lp15 fir1(512, 15e3/(fs/2)); D_hat 2 * filtfilt(lp15, 1, mpx_hat .* c38); % 5) 低通取出和信号 S_hat filtfilt(lp15, 1, mpx_hat);第 2 步是整段最精巧的地方。导频是cos(2πfp t φ)平方之后是0.5 0.5cos(2π·2fp t 2φ)滤掉直流剩下的就是 38 kHz、相位 2φ和发射端那个sub完全同相。相位差为零cos(Δφ)1差信号满幅解出来。如果改成本地振荡器cos(2π·38e3·t)只要信道或者滤波器引入一点群延迟Δφ 就不是零D 会被压掉一截同时泄漏到和路里分离度直接掉到 10 dB 以下。第 3 步的归一化也容易被忽略。导频幅度只有 0.1平方之后幅度约 0.005不归一化的话后面D_hat小得可怜和 S_hat 完全不是一个量级和差矩阵一算L 和 R 几乎相等听起来就是单声道——这和开头 A 同学遇到的现象一模一样。4.4 和差矩阵、去加重与分离度评估% 和差还原 L_hat S_hat D_hat; R_hat S_hat - D_hat; % 去加重H(s) 1/(1s*tau) [b_dea, a_dea] bilinear(1, [tau 1], fs_a); L_out filter(b_dea, a_dea, resample(L_hat, fs_a, fs)); R_out filter(b_dea, a_dea, resample(R_hat, fs_a, fs));和差矩阵的系数取决于 3.1 节怎么定义 S 和 D。当时除过 2所以这里直接加减就是原始 L 和 R。如果那一步没除 2这里要补上 0.5否则输出会大一倍播放时直接削顶THD 高得离谱还找不到原因。去加重的bilinear(1, [tau 1], fs_a)是预加重那个传递函数的倒数本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?