简介:围绕FM调制解调完整流程整理的MATLAB仿真材料,面向通信原理、信号处理课程的学习者,以及需要复现立体声调频实验的读者。它把立体声调频系统的调制与解调过程放进可运行脚本中,重点解决从公式理解到代码实现、从参数调整到时频域观察、从调制端到解调端验证之间的衔接问题。压缩包共2个文件,包含1个MATLAB源程序和1个音频示例,整体约88KB,体积轻便,便于在本地快速打开、运行与调试。已有868人学习。源程序注释较详细,调制参数可调,也支持自由更换参数,并配有较完整的时域与频域展示;读者可据此对照波形和频谱,理解载波、基带、立体声复合信号及左右声道恢复等环节,适合用作课程设计参考、实验复现、报告撰写和自学排查思路的起点。
1. 从一段19kHz导频说起:这份FM立体声仿真包能跑出什么
很多人做 MATLAB FM系统仿真,单声道调通以后觉得不过如此;一换成立体声就卡住——波形看着像模像样,声像却糊成一团,左右声道互相串。差别就在复合基带里那几根“暗线”:19kHz导频、挂在38kHz副载波上的差信号、以及收发两端必须严格配对的预加重与去加重网络。这份资源把整条链路拆成了完整过程,从基带合成、调频、信道加到正交鉴频、导频恢复、矩阵解码、去加重,每一步都有对应的脚本和参数落点,不是只给一张频谱图让你猜。它适合正在做通信原理课程设计、需要一份能改参数就跑出曲线的读者,也适合想把“分离度为什么上不去”这件事一次性搞明白的工程师。下面按信号骨架、调制端、解调端、翻车现场、验证方法五段拆,代码可以直接往自己的工程里搬。
2. 立体声FM的信号骨架:和差信号、导频与预加重怎么摆
2.1 单声道FM与立体声FM的差别不只在声道数
单声道调频广播的基带就是一路音频,占住0到15kHz,接收端一个鉴频器加低通就能出声。立体声要多传一路,但必须兼容只支持单声道的老接收机,这个兼容性约束决定了整个基带结构:复合信号里必须存在一路线性的L+R,老机器把它当单声道节目听,不会因为多出来的副载波而明显变调。L+R留下之后,L-R没地方放了,只能搬到一个更高的频段上去,而且是抑制载波的DSB-SC——如果直接调幅,载波会白白吃掉功率,还会在解调端引入不需要的直流分量。
搬到哪里?38kHz。为什么是38k不是别的数?因为它必须是导频频率的两倍,而导频频率的选择要满足两个条件:高于音频最高频率15kHz,避免与L+R频谱重叠;又不能太高,否则复合基带带宽膨胀,调频所需的Carson带宽跟着涨。19kHz落在15k和38k之间,是折中后的结果,往上57kHz还能挂一个RDS数据副载波,同样是19k的整数倍,整套频率规划非常整齐。
接收端要相干解调38kHz的DSB-SC,就必须知道载波相位,于是发送端在19kHz处插一根低幅度的纯导频。这根导频的幅度通常是复合信号峰值的8%到10%,够接收端锁相,又不至于分走太多频偏预算。导频不是“可选项”,它是立体声解码的相位基准,导频一丢,L-R就解不出来,接收机自动退回单声道。
所以立体声FM的复合基带可以写成一个三项相加的形式:M + S·cos(2π·38k·t) + Ap·cos(2π·19k·t),其中M=(L+R)/2、S=(L-R)/2,Ap是导频幅度。三项的频谱互不重叠,接收端用三个不同带宽的滤波器就能各取所需。这套结构在纸面上很干净,真正落到MATLAB里,麻烦全在幅度预算和相位对齐上。
2.2 复合基带的拼装顺序与幅度预算
拼装顺序有一个容易搞反的地方:预加重到底加在L/R上,还是加在合成后的复合基带上?正确做法是在合成之前,对L和R分别做预加重。原因很直接——预加重是为了补偿传输过程中高频段的信噪比劣化,而L和R是两路独立的节目信号,各自的频谱特性不一样,先加重再合成,接收端矩阵还原之后再去加重,两路才能各自恢复平坦响应。如果把预加重放在复合基带上,38kHz副载波附近的能量会被一起抬起来,导频和边带的比例被破坏,解调端恢复出的L-R幅度就不对了。这个顺序错误在仿真里非常常见,波形上不容易一眼看出,只表现为分离度莫名掉到20dB以下。
幅度预算是第二个必须算清楚的东西。调频器的输入峰值直接决定最大频偏,复合基带一旦超过归一化上限,就会进入过调制,鉴频输出的波形顶部被削平,听起来是严重的失真。三项叠加的最坏情况是|M| + |S| + Ap,所以生成信号之后要检查这个上界。很多示例代码图省事,直接给L和R各0.5的幅度,M和S峰值都到0.5,加上0.1的导频就是1.1,已经越界了,跑出来的频谱在边带处能看到明显的折叠。
fs = 500e3; % 采样率,后面 2.3 节会解释为什么是 500k fc = 100e3; % 仿真载波,不用真实 10.7MHz 中频 kf = 75e3; % 频偏常数,Hz/V,对应最大频偏 75kHz Ap = 0.1; % 导频幅度,占复合信号峰值约 10% tau = 50e-6; % 预加重时间常数,国内广播常用 50us t = (0:1/fs:0.05-1/fs).'; % 50ms,够看清 1kHz 的几十个周期 % 两路测试音,刻意用不同频率,方便在输出端肉眼分辨串扰 L = 0.30*sin(2*pi*1e3*t) + 0.10*sin(2*pi*3e3*t); R = 0.30*sin(2*pi*1.5e3*t) + 0.10*sin(2*pi*4e3*t); % 预加重必须在合成之前,逐路处理 Lp = preemphasis(L, fs, tau); Rp = preemphasis(R, fs, tau); M = (Lp + Rp) / 2; S = (Lp - Rp) / 2; pilot = Ap * cos(2*pi*19e3*t); sub = cos(2*pi*38e3*t); mpx = M + S .* sub + pilot; % 峰值检查:超过 1 就过调制,这里按实测峰值缩回去 pk = max(abs(mpx)); if pk > 1 mpx = mpx / pk; warning('复合基带峰值 %.3f,已归一化,导频比例随之改变', pk); end这段代码里,preemphasis是自定义函数,下面马上给。M和S都用0.5的系数,是因为L和R各自峰值约0.4,加起来除二之后M峰值0.4、S峰值0.4,再加0.1的导频刚好0.9,留了一点余量。如果你的音频素材是真实语音,峰均比更大,归一化那一步会被频繁触发,导频的相对幅度就被压小了,所以真实素材建议先把L/R整体缩到0.25左右再进来。
mpx的峰值检查不能省。归一化会同时缩放三项,导频幅度跟着变,接收端的导频带通滤波器增益就要相应调整;更稳妥的做法是只缩放M和S,让导频保持固定幅度,这样接收端的参数不用跟着变。
2.3 采样率、载波与滤波器带宽的一组可用参数
采样率不是随便填一个数就行,它要同时满足三件事:能容纳调频后的Carson带宽、留出抗混叠余量、又不至于让50ms的仿真跑上几分钟。按Carson公式,B = 2(Δf + fm),Δf取75kHz,fm取复合基带最高频率53kHz(38k副载波加上15k最高边带),算出来B约256kHz。通带仿真要求fs > 2(fc + B/2),fc取100kHz时下限是456kHz,取500kHz刚好卡在安全线上,再低就会出现边带折叠。
如果不想跑通带,也可以直接用基带等效模型:跳过载波,对复合基带做鉴频,采样率只要大于2×53k = 106kHz,取200kHz就够,计算量能降一大截。基带等效的好处是跑长时长的误码率统计很快,坏处是看不到载波泄漏、镜像干扰这类通带才有的现象,排查真实问题时容易漏掉线索。我的习惯是先用基带等效把算法逻辑跑通,再切到通带模型确认频谱干净。
| 参数 | 取值 | 依据 |
|---|---|---|
| 复合基带最高频率 | 53kHz | 38k + 15k 上边带 |
| 最大频偏 Δf | 75kHz | 广播调频通用上限 |
| Carson 带宽 | 约256kHz | 2(Δf + fm) |
| 仿真载波 fc | 100kHz | 远高于基带,便于观察 |
| 采样率 fs | 500kHz | > 2(fc + B/2) = 456kHz |
| 导频频率 | 19kHz | 38k 副载波的一半 |
| 预加重时间常数 | 50µs | 对应转折频率约3.2kHz |
滤波器带宽也要跟着这套参数走。解调端的低通不能只切15kHz,因为正交鉴频输出的谐波会落在更高处,实际要留到20kHz左右再用一级15kHz的陡降滤波器做最终分离;导频带通则要窄,18.7k到19.3k足够了,太宽会把L+R的高频尾巴和38k的下边带一起卷进来,后面恢复出的38k载波上就带着音频成分,解调出来全是串扰。
3. 调制端仿真:从复合基带到调频波形的完整链路
3.1 预加重放在合成之前,位置错了后面全错
预加重的物理意义是把音频高频段提前抬起来,让它在调频传输、尤其在中频滤波器之后仍然高出噪声底,接收端再按相反曲线压回去,噪声的高频部分被一起压掉,主观听感上的“嘶嘶”声就小很多。一阶预加重的传递函数是H(s) = (1 + sτ)/(1 + sτ/Amax),分母那项是实际电路的高频增益上限,不加分母的话高频增益会随频率无限增长,仿真里跑到奈奎斯特附近直接发散。
function y = preemphasis(x, fs, tau) % 一阶预加重,高频最大提升 Amax 倍(约12dB) Amax = 4; [b, a] = bilinear([tau 1], [tau/Amax 1], fs); y = filter(b, a, x); end function y = deemphasis(x, fs, tau) % 一阶去加重,与上面的预加重互为逆响应 [b, a] = bilinear(1, [tau 1], fs); y = filter(b, a, x); endbilinear做双线性变换,分子[tau 1]对应tau*s + 1,分母[tau/Amax 1]对应(tau/Amax)*s + 1,两个极点都落在左半平面,离散化之后稳定。tau取50µs时,转折频率 1/(2πτ) 约3.2kHz,也就是3.2kHz以上开始以大约6dB/倍频程抬升,到高频端被Amax截住,最大提升约12dB。
这两个函数必须成对使用,而且顺序不能错:发送端先预加重再合成MPX,接收端先解码出L/R再去加重。有一个很隐蔽的问题——filter是因果滤波,会引入群延迟,预加重和去加重各引入一份,如果不在接收端做延迟补偿,解出来的L和R相对原始信号会有整体偏移。做波形对比时要记得对齐,或者干脆两边都用filtfilt做零相位处理,代价是仿真不能实时流式跑,但离线分析完全够用。
预加重之后L和R的峰值会变大,特别是含有3k、4k这类靠近转折频率成分的测试音,峰值可能涨到原来的1.5倍以上。这就是2.2节里峰值检查必须放在合成之后的原因,先合成再归一化虽然会改变导频比例,至少不会过调制;更好的做法是在预加重之后先估一次峰值,把L/R缩到安全区再合成。
3.2 调频指数与频偏常数的换算
调频的数学表达是 s(t) = cos(2π·fc·t + 2π·kf·∫mpx(τ)dτ),其中kf是频偏常数,单位Hz/V,物理含义是基带信号每1V的瞬时幅度能撬动多少赫兹的频率偏移。广播调频规定最大频偏75kHz,所以kf的取值要保证mpx峰值1.0时刚好推动75kHz,也就是kf = 75e3。
很多人会把调频指数β和kf搞混。β = Δf/fm,是一个随调制频率变化的无量纲量,单音测试时,1kHz信号在75kHz频偏下β=75,15kHz信号在同样频偏下β=5。β决定的是边带数量和频谱展宽程度,kf决定的是“电压到频率”的换算比例,两者通过调制信号的频率和幅度联系起来。仿真里设置的是kf,观察到的频谱形状反映的是β,调参时不要拿β直接往代码里填。
% 相位积分,cumsum 是矩形法数值积分,步长 1/fs phase = 2*pi*fc*t + 2*pi*kf*cumsum(mpx)/fs; s_tx = cos(phase); % 加高斯白噪声,30dB 只是一个起点 snr_db = 30; s_rx = awgn(s_tx, snr_db, 'measured'); % 频谱自检:看 100k 附近是否对称、边带是否完整 Nfft = 2^16; [Pxx, f] = pwelch(s_rx, hann(Nfft), Nfft/2, Nfft, fs, 'centered'); figure; plot(f/1e3, 10*log10(Pxx)); xlabel('频率 / kHz'); ylabel('功率 / dB'); xlim([-300 300]); grid on;cumsum(mpx)/fs是矩形法积分,步长足够小的时候误差可以忽略,但它有一个副作用:积分结果带直流累积。如果mpx有非零均值,积分项会随时间线性增长,相位一路飘走,解调出来就是一个缓慢下漂的基线。所以进调频器之前必须确认mpx的均值为零,测试音是正弦时天然满足,真实语音要先做高通去直流。
awgn的第三个参数用'measured',它会先测信号功率再按指定SNR加噪,比手动算噪声方差省事。SNR的定义是信号总功率比噪声总功率,这里的信号是调频波,功率恒定,所以不同SNR下的对比是公平的。如果你要观察FM的门限效应,就把snr_db从40扫到0,每隔5dB存一次解调输出,后面第6章会用这条曲线判断仿真是否合理。
3.3 信道建模:AWGN之外还该加什么
只加AWGN的仿真能验证算法逻辑,但验证不了“这套参数在真实信道下能不能用”。真实调频信道至少还有两样东西值得加进来:多径和中频滤波器的群时延失真。多径在调频里表现为幅度起伏和相位跳变,严重时解调输出出现“咔哒”声,这就是FM固有的门限效应在作祟;中频滤波器如果带宽不够或者相位非线性,边带被削掉一部分,解调后的音频高频段会明显发闷。
多径的仿真写法很简单,两径模型就够说明问题:一路直达,一路延迟并衰减。
% 两径模型:延迟 2us,幅度 -6dB delay_samp = round(2e-6 * fs); h = [1 zeros(1, delay_samp-1) 0.5]; s_multipath = filter(h, 1, s_tx); % 中频带通,模拟接收机前端 [b_if, a_if] = butter(4, [60e3 140e3]/(fs/2), 'bandpass'); s_rx = filtfilt(b_if, a_if, s_multipath); s_rx = awgn(s_rx, snr_db, 'measured');filter(h,1,...)做的是卷积,延迟支路的幅度0.5对应-6dB,两径叠加后某些频点会深衰落,调频的恒包络特性在这里被破坏,幅度上出现凹陷。filtfilt用零相位方式做带通,避免引入额外群延迟干扰后面的相位对齐分析;如果要做实时流式仿真,就得换回filter并记录延迟。
中频带通的通带取60k到140k,围绕100k载波对称,宽度80kHz,比Carson带宽256kHz窄不少——这是故意的,真实接收机的中频滤波器通常不会开到理论带宽,正是这种“窄”导致了边带截断和高频失真,仿真里保留这个特性,后面分析音频高频响应时才对得上。
加完这些再去看解调输出,你会发现纯AWGN下听不出问题的30dB,在两径加窄带中频的条件下,某些时刻仍然会出现明显的失真尖峰。这就是为什么很多仿真报告里写着“SNR=30dB时分离度40dB”,实际装到设备上却达不到,差别就在信道模型是否完整。
3.4 发射端自检:频谱上必须看到的四根线
调频波的频谱不像AM那样直观,但复合基带的结构会在频谱上留下清晰的印记。正确的发射端频谱,围绕载波应该能看到:中心的主瓣、紧邻主瓣的L+R低频成分、偏移19kHz处的导频谱线、偏移38kHz处的L-R边带。导频那根线是单频,在频谱上非常尖锐,一眼就能认出来;如果它跟旁边的边带混在一起分不开,说明幅度太大或者频率规划错了。
% 只看基带,把载波搬回零频 bb = s_rx .* exp(-1j*2*pi*fc*t); [b_lp, a_lp] = butter(6, 60e3/(fs/2)); bb = filtfilt(b_lp, a_lp, bb); [Pbb, fbb] = pwelch(bb, hann(Nfft), Nfft/2, Nfft, fs, 'centered'); figure; plot(fbb/1e3, 10*log10(Pbb)); xlabel('基带频率 / kHz'); ylabel('功率 / dB'); xlim([-60 60]); grid on;把载波搬回零频再低通,得到的就是复合基带的复包络,19k、38k的谱线位置一目了然。exp(-1j*2*pi*fc*t)做复混频,负频率侧会被搬到-2fc处,60kHz低通正好把它滤掉,留下0附近的基带。这一步是排查发射端问题最快的手段:导频位置偏移,说明19k的生成用了错误的采样率换算;38k边带不对称,说明S信号里混进了直流或者M/S的幅度不平衡。
自检还要看一眼时域包络。调频是恒包络调制,理想情况下abs(s_tx)应该是一条直线,方差接近零。实测包络如果出现规律性起伏,通常是相位积分的步长不够或者kf过大导致瞬时频率越过奈奎斯特,这两种情况在频谱上未必立刻显形,但包络一看就露馅。
4. 解调端仿真:正交鉴频、导频恢复与立体声解码
4.1 正交鉴频:从I/Q两路到瞬时频率
调频解调的本质是把频率变化还原成幅度变化。模拟电路里用鉴频器,数字仿真里最直接的做法是正交下变频加相位差分:先把信号分别乘上cos和sin搬到零频,低通滤掉二倍频,得到I和Q两路基带,再用atan2求相位,相邻样点的相位差除以2π再乘fs就是瞬时频率。
% 正交下变频 I = s_rx .* cos(2*pi*fc*t); Q = s_rx .* sin(2*pi*fc*t); % 低通,截止频率要盖住 Carson 带宽的一半再多留一点 [b_lp, a_lp] = butter(6, 150e3/(fs/2)); I = filtfilt(b_lp, a_lp, I); Q = filtfilt(b_lp, a_lp, Q); % 瞬时相位与频率 phi_hat = unwrap(atan2(Q, I)); inst_f = [0; diff(phi_hat)] * fs / (2*pi); % 去掉载波中心频率对应的直流,再除以 kf 还原复合基带 inst_f = inst_f - mean(inst_f); mpx_hat = inst_f / kf;几个参数要盯住。低通截止取150kHz,比Carson带宽的一半128kHz略宽,目的是保住边带,同时压掉2fc处的混频产物。截止再窄一点,高频边带被削,解出来的音频高频损失;再宽一点,二倍频漏进来,相位差分时变成高频噪声,听感上是持续的沙沙声。
unwrap不能省。atan2的值域是(-π, π],相位跨越±π时会跳变,不展开的话差分出来是巨大的尖峰,解调波形上每隔一段时间冒一根刺。unwrap默认按π的跳变判断,调频信号每样点的相位增量远小于π,不会误判。
diff之后长度少一个样点,前面补零对齐。mean(inst_f)减掉的是载波频率对应的常量偏置,理论上正交下变频之后这个偏置应该接近零,但滤波器的不理想和数值误差会留下一点残余,不去掉的话mpx_hat整体上漂,后面的低通和矩阵还原都会带着一个直流分量。
这里用的是相位差分法,还有一种做法是对s_rx求导再取包络,实现更简单但抗噪性能差,噪声会被微分放大。资源里两种都给了脚本,日常分析我用相位差分,做门限对比时用求导法,因为它的噪声特性更接近模拟鉴频器,曲线形状对得上教科书。
4.2 19kHz导频提取与38kHz载波再生的相位对齐
拿到mpx_hat之后,第一件事是把19kHz导频滤出来。导频是纯单频,用窄带带通就够,带宽取±300Hz,窄到不会把旁边的L+R高频成分卷进来,又宽到能容忍一点频率偏移。
% 19kHz 导频提取 [b_p, a_p] = butter(4, [18.7e3 19.3e3]/(fs/2), 'bandpass'); pilot_rx = filtfilt(b_p, a_p, mpx_hat); % 归一化后倍频得到 38kHz 载波:cos(2θ) = 2cos²θ - 1 pilot_n = pilot_rx / max(abs(pilot_rx)); carrier38 = 2*pilot_n.^2 - 1; % 相干解调 L-R diff_raw = mpx_hat .* carrier38; [b_a, a_a] = butter(6, 15e3/(fs/2)); diff_sig = filtfilt(b_a, a_a, 2*diff_raw);2*pilot_n.^2 - 1是这套仿真里最省事的一个技巧。导频是cos(2π·19k·t),平方之后产生直流和cos(2π·38k·t)两项,减掉直流、乘2归一化,正好得到单位幅度的38k载波,而且相位天然与发送端对齐,不需要额外的锁相环。代价是它对导频的幅度波动敏感,pilot_n归一化时如果导频里混了噪声,平方会把噪声也放大,所以带通滤波器的阻带衰减要够,四阶巴特沃斯在偏离中心1kHz处大约有40dB衰减,够用。
如果要用锁相环,思路是把导频和本地19k振荡器做鉴相,误差经环路滤波后控制振荡器频率,锁定后倍频。PLL的好处是能跟踪导频的缓慢频偏,坏处是锁定时间、环路带宽这些参数要调,初学阶段容易把自己绕进去。资源里两种实现都有,建议先用倍频法把链路跑通,再换PLL对比锁定过程。
导频恢复里最容易被忽略的是滤波器延迟。filtfilt是零相位,不引入延迟,所以这里用它;如果换成filter,带通会引入几十个样点的群延迟,这个延迟作用在导频上,倍频出来的38k载波就相对信号里的38k分量偏了相位,相干解调时L-R的幅度按cos(Δφ)衰减,分离度直接掉。这是第5章要展开的一个坑,这里先记住结论:导频这条路要么零相位滤波,要么显式补偿延迟。
4.3 和差矩阵还原与去加重
L+R直接从mpx_hat低通15kHz得到,L-R从上一节解出来,剩下就是加减法:L = (M+S),R = (M-S),注意前面定义M=(L+R)/2、S=(L-R)/2,所以还原时是L = M+S、R = M-S,系数不要多乘2。
% L+R 通道 [b_sum, a_sum] = butter(6, 15e3/(fs/2)); sum_sig = filtfilt(b_sum, a_sum, mpx_hat); % 矩阵还原 L_hat = sum_sig + diff_sig; R_hat = sum_sig - diff_sig; % 去加重放在最后,逐路处理 L_out = deemphasis(L_hat, fs, tau); R_out = deemphasis(R_hat, fs, tau);diff_sig那一步乘了2,是因为相干解调时mpx_hat .* carrier38产生的L-R分量幅度是S的一半(cos×cos展开后有一项系数1/2),乘2把它补回来。这个系数如果漏了,解出来的L和R各自幅度只有正确值的一半,听起来是音量小,不容易发现是系数问题。判别方法很简单:给一个只有L声道的测试信号,看L_out的峰峰值是否与原始L一致,差一倍就是这里的问题。
去加重必须在矩阵还原之后。前面说过预加重是对L和R分别做的,所以逆运算也要落到各自的通道上。如果图省事在mpx_hat上直接去加重,38k附近的导频和边带会被一起压掉,解出来的L-R严重失真,这是顺序错误的第二个高发点。
去加重滤波器是IIR,因果实现会引入相位失真,虽然人耳对相位不敏感,但做波形对比和分离度计算时会有影响。deemphasis里用的是filter,如果要严谨对比,把它换成filtfilt,代价是离线处理。资源里的脚本默认用filter,因为更接近真实接收机的单向处理。
4.4 输出对齐与延迟补偿
整条链路走下来,L_out和原始的L在时间轴上对不齐,差多少取决于各级滤波器的群延迟总和。做主观试听无所谓,做分离度、THD这些指标计算时必须对齐,否则算出来的误差里混着延迟,数字完全不可信。
对齐的办法有两种。一是互相关找峰值位置,把输出整体平移回去;二是把整条链路的延迟预先算出来,在生成测试信号时就给原始L/R加同样的延迟。第一种更通用,第二种更适合需要反复跑参数的场景。
% 用互相关找 L 通道的整体延迟 [c, lag] = xcorr(L_out, L, 'coeff'); [~, idx] = max(abs(c)); delay = lag(idx); % 对齐后截取有效段 if delay >= 0 L_aligned = L_out(delay+1:end); L_ref = L(1:end-delay); else L_aligned = L_out(1:end+delay); L_ref = L(-delay+1:end); endxcorr的第三个参数'coeff'做归一化,峰值接近1说明两路确实相关,找出来的lag可信。延迟为正表示输出滞后,截掉输出前面delay个样点再与原始信号对齐。截取之后两路长度要一致,后面算误差才有意义。
对齐做完,L通道的残差应该只剩噪声和失真,R通道在同等情况下的残差反映的是串扰。这两个量的比值就是分离度,第6章会给具体算法。延迟补偿这步看着琐碎,但它是把“波形看着挺像”变成“指标可量化”的分水岭,跳过这步,后面所有的对比都是自欺欺人。
5. 常见翻车现场:从频谱镜像到声道反相的排查清单
5.1 分离度只有十几dB:导频相位与滤波器延迟
现象:解调出来的L和R都能出声,示波器上看波形也对,但算分离度只有15到20dB,怎么调预加重参数都上不去。把R通道静音,L通道的输出里仍然能听到明显的R信号残留。
原因:导频恢复这条路的相位与信号里的38k分量没对齐。最常见的触发点是带通滤波器用了filter而不是filtfilt,四阶巴特沃斯在19kHz附近的群延迟有好几十个样点,作用在导频上,倍频出来的38k载波就偏了相位。相干解调对相位误差的容忍度很低,Δφ到30度时L-R幅度已经掉到0.87,串扰明显;到60度时L-R只剩一半,分离度直接崩到10dB以下。第二个触发点是导频带通的中心频率没对准,19kHz附近如果有几十赫兹的偏差,倍频后变成上百赫兹,相干解调的积分窗口里相位一直在转,等效于持续失配。
解决:导频带通一律用filtfilt;如果必须用因果滤波,就把滤波器的群延迟算出来,对倍频后的载波做同样的延迟补偿。中心频率偏差用频谱看一眼导频峰的准确位置,MATLAB里可以直接找pwelch输出的峰值索引换算成频率,偏多少就在本振里补多少。补偿完之后分离度应该能上到35dB以上,剩下的是噪声和预加重残差决定的。
5.2 低频段出现“嗡嗡”声:积分直流与载波泄漏
现象:解调输出的音频里叠加了一个低频嗡声,频率大概在几十赫兹,跟测试音的频率不成整数倍关系。频谱上看,15kHz低通之后的信号在0附近有一个明显的尖峰,把音频低频段抬了起来。
原因:两个来源叠加。一是相位积分cumsum(mpx)/fs带进了直流,如果mpx均值不为零,积分项线性增长,鉴频输出的基线缓慢漂移,表现为极低频的起伏。二是正交下变频后的低通没把载波泄漏滤干净,s_rx .* cos(2πfc t)展开后除了基带还有2fc项,低通截止150kHz时2fc=200kHz刚好在过渡带边缘,衰减不够,残余的载波分量在相位差分时变成低频噪声。
解决:进调频器之前对mpx做一次去均值,mpx = mpx - mean(mpx),这一步在真实系统里对应发送端的高通滤波。低通截止从150kHz收到130kHz,或者把滤波器阶数从6提到8,让2fc处的衰减多20dB。做完这两步,0附近的尖峰应该消失,低频嗡声听感上会明显减轻。如果还有残留,检查一下inst_f - mean(inst_f)这步有没有执行,漏掉它同样会留下直流。
5.3 波形削顶:幅度预算与预加重增益
现象:小音量时一切正常,测试音幅度一调大,解调输出的波形顶部就平了,频谱上出现大量谐波,THD从1%跳到10%以上。严重时L和R同时失真,分离度反而看起来“变好”了,因为两边都被削成了方波。
原因:复合基带超过了归一化上限,调频器进入过调制。预加重把高频成分抬了最多12dB,如果L/R的原始幅度是按未加重的峰值定的,加重之后峰值轻松翻倍,M+S+导频轻松越过1.0。代码里那个归一化步骤如果只做了一次,而且是在合成之前做的,就完全挡不住这个越界。
解决:把峰值检查放在预加重之后、合成之前,先估Lp和Rp的联合峰值,按|Lp+Rp|/2 + |Lp-Rp|/2 + Ap ≤ 1反推允许的缩放系数,再统一缩放。更省事的办法是直接用mpx/max(abs(mpx)),但要记住这会改变导频比例,接收端的导频带通增益要跟着调。我一般在脚本开头就把L/R的幅度定在0.25,给预加重和导频留足余量,跑真实语音素材时再按实测峰值微调。
5.4 高频噪声爆炸:采样率与去加重顺序
现象:解调输出的高频段噪声特别大,15kHz以上像开了闸,频谱上高频部分抬得很高,去加重之后仍然压不下去。把SNR提高到50dB,高频噪声只降了一点,不符合预期。
原因:去加重放在了错误的位置,或者采样率不够导致高频混叠。去加重如果加在复合基带而不是矩阵还原之后,38k副载波附近的噪声会被当成音频信号一起处理,倍频解调时噪声被搬到基带,去加重滤波器对这部分噪声无能为力,因为它本来就不在预加重的逆响应覆盖范围内。采样率不够则是另一个独立问题,fs=500kHz时奈奎斯特是250kHz,调频边带在256kHz处已经开始折叠,折叠回来的高频噪声正好落在音频带内,去加重压不掉。
解决:先确认去加重在矩阵还原之后,逐路处理。再检查采样率,把fs提到600kHz或800kHz跑一遍,如果高频噪声明显下降,说明之前是混叠。混叠这种问题在时域波形上看不出来,频谱上表现为高频底噪异常平坦,跟热噪声的滚降形状不一样。确认是混叠之后,要么提采样率,要么在正交下变频之后加一级更陡的抗混叠低通,把Carson带宽之外的成分提前砍掉。
6. 把仿真跑成可信结论:从单点波形到指标的验证习惯
波形好看不代表仿真对,这是我踩过最多次的坑。判断一套FM立体声仿真是否可信,至少要看三个指标:分离度、总谐波失真、以及门限效应曲线。分离度的算法在4.4节对齐之后就能直接写,把R通道置零,看L通道输出里残留了多少R的成分,取对数。THD用thd函数对单音测试信号算,注意要先截取稳态段,跳过滤波器建立的暂态。门限效应曲线更费事,把SNR从40dB扫到0dB,每个点存一次解调输出的信噪比,画出来应该能看到明显的拐点,拐点位置在10dB附近,低于拐点输出信噪比急剧恶化。如果你的曲线是一条平滑下降的直线,没有拐点,说明信道模型太理想,或者鉴频器实现里少了某个非线性环节。
snr_list = 40:-5:0; snr_out = zeros(size(snr_list)); for k = 1:numel(snr_list) s_rx = awgn(s_tx, snr_list(k), 'measured'); % ... 走完整条解调链路 ... err = L_out_aligned - L_ref; snr_out(k) = 10*log10(var(L_ref)/var(err)); end figure; plot(snr_list, snr_out, 'o-'); xlabel('信道 SNR / dB'); ylabel('解调输出 SNR / dB'); grid on;这段循环跑一次大概几十秒,换来的是对整套参数可信度的一个判断。曲线在低SNR段如果出现输出SNR高于输入SNR的情况,八成是对齐没做好,误差里混进了延迟;如果有拐点但位置偏得离谱,检查一下kf和滤波器带宽是否匹配。
还有一个习惯:每次改完参数,先看复合基带的频谱,再看解调输出的频谱,最后才看时域波形。频谱能同时暴露幅度、频率、相位三类问题,时域波形只告诉你“看起来不对”。从那以后我每次动预加重时间常数或者滤波器阶数,都强制先把这两张频谱图打出来对一遍,再谈指标。希望帮到你。
本文还有配套的精品资源,点击获取