简介:这份PDF为重庆邮电大学移通学院电子信息工程专业《FM调制解调电路的设计》课程设计报告,面向通信原理、信号处理方向的学生与自学者,围绕FM调制解调系统的建模、仿真与性能分析展开。资源包仅含1个pdf文件,大小约1.08MB,便于直接查阅与存档。文档系统梳理了通信系统的一般模型、FM调制原理与非相干解调过程,并给出基于MATLAB 7.0的调制解调实现思路,包括基带信号、载波信号、已调信号及噪声叠加后信号的时域波形绘制,误码率与信噪比关系对比,以及M文件代码片段。已有644人学习下载,可作为课程设计参考、答辩复盘或MATLAB通信仿真入门样例,帮助读者理解频谱搬移、调频灵敏度、高斯白噪声信道等关键概念,并掌握从参数设置到结果验证的完整流程。
1. FM 调制解调系统设计与仿真:先确定你仿的是哪一段链路
做电子系统设计课设或无线对讲机项目时,最容易卡住的地方不是画框图,而是框图上的参数填不出来。载波频率填多少、频偏给多大、中频滤波器带宽设几个 kHz、鉴频器后面那级低通截至在哪,这几个数字互相咬合,改一个另外几个就得跟着动。FM 调制解调系统设计与仿真这件事,本质是在焊第一块板子之前,用一条纯软件的链路把这些数字定下来,让波形、频谱、信噪比先对得上,再搬到 STM32、DSP 或者电台模块上。
它适合三类人:通信原理课设要做波形比对的学生、准备做基于单片机的无线对讲机系统设计的硬件工程师、以及习惯在 Python 或 MATLAB 里先跑通信仿真再动手的射频从业者。仿真替代不了实测,但它能把「频偏给到 75 kHz 会不会超出中频带宽」「去加重时间常数不匹配会损失多少高频能量」这类问题提前暴露,省掉几轮改板。
2. FM 调制解调的数学模型与参数换算:频偏、调制指数、卡森带宽
2.1 从瞬时频率到调制指数:三个公式定住整条链路
角度调制的出发点只有一句话:载波的瞬时频率随调制信号线性变化。写成式子就是
- 瞬时频率:f(t) = fc + kf · m(t)
- 已调波:s(t) = A · cos(2πfc·t + 2πkf·∫m(τ)dτ)
- 调制指数:β = Δf / fm
第一式里的 kf 是调频灵敏度,单位 Hz/V 或 rad/s/V,取决于你是按频率还是按角频率定义。第二式里的积分是这条链路唯一一个容易写错的地方——相位是频率的积分,不是频率本身。第三式里的 Δf 是峰值频偏,fm 是调制信号的最高频率,两者之比 β 决定了解调增益和带宽占用。
调制度是另一个常被混淆的量:它等于实际频偏除以该信道允许的最大频偏,取值 0 到 1。调制度 100% 意味着频偏刚好顶到上限,再大就压到邻道。仿真里如果不做归一化,一个幅度为 2 的正弦会把频偏推到设定值的两倍,波形看上去没错,频谱却已经超限。
卡森带宽给出了工程上够用的估计:B ≈ 2(Δf + fm) = 2fm(β + 1)。它把调频信号 98% 以上的功率圈进来,剩下的是可接受的边带泄漏。仿真时带通滤波器的带宽就按这个值留 10% 余量取。
2.2 调频广播与对讲机的典型参数对照表
不同业务用到的频偏和音频带宽差一个数量级,仿真前先选一张表对照,比自己拍脑袋强。下面这组数字是行业里常见的取值:
| 场景 | 载波 / 中频 | 峰值频偏 Δf | 音频带宽 fm | 调制指数 β | 卡森带宽 B |
|---|---|---|---|---|---|
| 调频广播 | 88–108 MHz / 中频 10.7 MHz | 75 kHz | 15 kHz | 5.0 | 180 kHz |
| 窄带对讲 | 400 MHz / 中频 455 kHz | 2.5 kHz | 3 kHz | 0.83 | 11 kHz |
| 数据链路 | 直接基带仿真 | 25 kHz | 10 kHz | 2.5 | 70 kHz |
广播场景里 β 远大于 1,叫宽带调频,解调后信噪比改善近似正比于 β²;对讲场景 β 小于 1,属于窄带调频,改善有限但占用带宽小。仿真前把这个 β 算出来,后面滤波器带宽、采样率、去加重时间常数才有依据。
提示:如果仿真对象是带 STM32 的收音机方案,中频通常取 10.7 MHz 后再下变频到几百 kHz,采样率不必按载波频率选,按中频带宽选就够。
2.3 预加重与去加重:容易被仿真忽略的一环
调频解调输出的噪声功率谱随频率上升,高频段信噪比明显差。广播和语音通信都在发射端加预加重、接收端加去加重,用提升高频再压回来的方式把高频噪声压下去。广播常用的时间常数是 50 µs,语音对讲常用 300 µs 到 750 µs 不等。
这两个网络是一阶高通和一阶低通,时间常数相同才互补。仿真里只做调制解调不加去加重,会得到一条高频偏暗的音频曲线,误以为解调器有故障。去加重的数字实现是一阶 IIR:
y[n] = α·y[n-1] + (1 − α)·x[n],α = exp(−1 / (fs·τ))
预加重是它的逆运算,系数用同一个 α。τ 就是上面说的时间常数,fs 是音频域采样率。τ 取错会直接表现为高频增益偏差。
3. 用 Python 搭最小可跑的 FM 调制解调仿真链路
3.1 采样率与仿真时长的确定规则
采样率按卡森带宽取,不按载波取,这是通信仿真里省算力的关键。用正交下变频到基带的做法时,采样率只需要覆盖基带带宽的 2 倍以上,一般取 4 到 10 倍余量。如果为了看波形好看直接用载波频率的十倍采样,几百毫秒仿真就能把内存吃满。
拿广播参数举例:卡森带宽 180 kHz,取 fs = 1 MHz,2 秒仿真就是 200 万个样点,单精度大约 16 MB,笔记本上跑几轮扫描毫无压力。仿真时长按最低音频频率的若干个周期取,测 3 kHz 单音时 20 ms 已经能看清波形;测 300 Hz 的低音至少要 100 ms,否则 FFT 的频率分辨率不够,看频谱会糊成一片。
采样时长和分辨率的关系是 Δf = fs / N。想分辨 100 Hz 以内的边带结构,N 至少要到 fs / 100,1 MHz 采样下就是 1 万个点起步。
3.2 调制端代码:积分、限幅与频偏映射
下面这段是调制核心,三个参数决定全部行为:
import numpy as np from scipy.signal import butter, lfilter FS = 1.0e6 # 仿真采样率,覆盖卡森带宽 2 倍以上 FC = 1.0e5 # 仿真载波,便于正交下变频 DF = 75e3 # 峰值频偏 FM_A = 15e3 # 音频最高频率,决定低通截止 TAU = 50e-6 # 去加重时间常数 def fm_mod(m, fs, fc, df): """m 已归一化到 ±1,df 为峰值频偏,单位 Hz""" kf = 2 * np.pi * df # 调频灵敏度 rad/s per unit phase = kf * np.cumsum(m) / fs # 离散积分 = ∫m dτ t = np.arange(len(m)) / fs return np.cos(2 * np.pi * fc * t + phase) N = int(FS * 0.02) # 20 ms,约 60 个 3 kHz 周期 t = np.arange(N) / FS m = 0.8 * np.sin(2 * np.pi * 3e3 * t) # 调制度 80%,留 20% 余量 tx = fm_mod(m, FS, FC, DF)cumsum(m)/fs是离散积分,等价于梯形积分的左端点形式,误差量级是 O(1/fs),在 1 MHz 采样下可忽略。kf用 2π 乘频偏是把 Hz 换算成 rad/s,这一步漏掉会让频偏差 6.28 倍,频谱上看就是带宽只有理论值的六分之一。m乘 0.8 是控制调制度,不是可有可无的装饰。
3.3 解调端代码:差分鉴频与低通滤波
解调用正交下变频加差分相位鉴频,不需要专门的鉴频器模型,适合快速验证:
def fm_demod(r, fs, fc): n = np.arange(len(r)) base = r * np.exp(-1j * 2 * np.pi * fc * n / fs) # 下变频到基带 dphi = np.angle(base[1:] * np.conj(base[:-1])) # 相邻样点相位差 inst = dphi * fs / (2 * np.pi) # 瞬时频率,Hz b, a = butter(4, FM_A / (fs / 2)) # 截止 = 音频带宽 audio = lfilter(b, a, inst) return audio - np.mean(audio) def deemphasis(x, fs, tau): alpha = np.exp(-1 / (fs * tau)) y = np.zeros_like(x) for i in range(1, len(x)): y[i] = alpha * y[i-1] + (1 - alpha) * x[i] return y snr_db = 20 sig_p = np.mean(tx ** 2) rx = tx + np.random.randn(len(tx)) * np.sqrt(sig_p / 10 ** (snr_db / 10)) audio = deemphasis(fm_demod(rx, FS, FC), FS, TAU)base[1:] * conj(base[:-1])取相邻样点相位差,直接给出瞬时频率,不需要 unwrap,也不怕相位跳变。低通用 4 阶巴特沃斯,截止设成音频带宽,阶数再高会引入明显群时延而看不出额外收益。最后减均值是把残余载波偏置去掉,否则音频上会叠一个直流台阶。
deemphasis里的 α 由 fs 和 τ 共同决定,改采样率不改 τ 会让去加重转折频率跑偏。循环版在 20 ms 数据上够用,长信号换成scipy.signal.lfilter的等价系数实现,速度差一个数量级。
3.4 跑通后先看这三张图
第一张是时域波形,把调制信号和去加重后的输出画在同一个坐标里,应有固定时延和幅度缩放,形状一致;第二张是已调波的功率谱,主瓣宽度应该接近卡森带宽,带外滚降平滑,如果有明显尖刺说明积分或限幅出了问题;第三张是解调输出的频谱,加去加重后高频段应该被压下来,与预加重前的原始信号形状吻合。
三张图对不上,先别怀疑算法,回头核对采样率、频偏单位和去加重时间常数这三个数,八成问题在这里。
4. FM 解调器的三类实现与工程参数整定
4.1 差分鉴频、锁相环与过零检测的取舍
仿真验证过的算法要落地时,三条路线各有适用面:
| 实现方式 | 计算量 | 抗噪门限 | 适合场景 |
|---|---|---|---|
| 差分相位鉴频 | 低 | 中 | 软件无线电、Python 快速验证 |
| 锁相环 PLL 解调 | 中 | 低(门限好) | 弱信号接收、窄带对讲 |
| 过零检测 | 极低 | 高 | 低成本单片机、方波化中频 |
差分相位鉴频在信噪比 10 dB 以上表现稳定,结构简单,是仿真阶段的首选。锁相环在低信噪比下能工作到更低的门限,代价是环路带宽和锁定时间要权衡,窄环路抗噪好但跟踪不了快速频偏变化。过零检测把中频限幅成方波后数过零点,对幅度不敏感,但对噪声引起的虚假过零没有免疫力,实际使用要配带通滤波。
注意:三类解调器在仿真里的对比要在同一信噪比、同一去加重条件下做,否则看到的差异可能来自后级滤波而不是解调方式本身。
4.2 从仿真搬到 STM32 时要注意的采样与定点问题
搬到单片机上,第一件事是把 ADC 采样率降下来。中频 455 kHz 直接采样不现实,常见做法是先用模拟混频降到几十 kHz,再进 ADC,或者用带通采样把中频折叠到低频。ADC 采样率定在 100 kHz 以内,STM32 系列完全可以跟上,配合 DMA 双缓冲,处理不用抢 CPU。
第二件事是定点。Q15 格式下相位累加和差分鉴频都容易溢出:
// Q15 定点差分鉴频,输入 iq_buf 为交替存放的 I/Q 样点 int32_t prev_i = 0, prev_q = 0; for (uint32_t k = 0; k < N; k += 2) { int32_t i = iq_buf[k], q = iq_buf[k + 1]; // 叉积近似相位差:i*prev_q - q*prev_i,随相位差单调 int32_t cross = (i * prev_q - q * prev_i) >> 15; // 点积用于判断象限,避免叉积在小角度外失真 int32_t dot = (i * prev_i + q * prev_q) >> 15; int32_t dphi = (dot > 0) ? cross : (cross > 0 ? 16384 : -16384); audio_out[k >> 1] = dphi; // 送后级 IIR 去加重 prev_i = i; prev_q = q; }cross在小相位差下近似正比于相位差,dot用来判断是否跨象限,否则叉积在接近 ±90 度时不再单调。>> 15每乘一次就要做,Q15 乘 Q15 结果是 Q30,不移位下一步必溢出。去加重用一阶 IIR,系数按前文的 α 预先算成 Q15 常数烧进代码,运行时不调用浮点。
4.3 立体声导频与静噪门限的处理位置
立体声广播在 19 kHz 处插一个导频,38 kHz 副载波承载差信号,解调后要先做 19 kHz 带通或锁相提取导频,再同步解出副载波。这一步在仿真里容易被跳过,只做单声道验证。如果目标是收音机整机,导频提取的相位误差会直接变成左右声道串音,仿真的串音指标要按导频相位误差扫描一遍。
静噪门限处理放在去加重之后、音频功放之前。判据用带外噪声能量:解调后信号幅度下降、高频噪声上升,超过门限就把音频静音。门限设太紧会在弱信号时频繁断续,太松又失去意义,仿真里扫几个信噪比点,找出噪声抬升的拐点作为门限依据。
5. FM 仿真结果的验证与排错:信噪比扫描、频谱与发散定位
5.1 用信噪比扫描验证解调增益
单点跑通不足以说明链路正确,要扫一条输出信噪比随输入信噪比变化的曲线。宽带调频在门限以上应有明显的信噪比改善,改善量近似为 3β²(β+1),β 取前文算出的调制指数。扫描代码如下:
import numpy as np from scipy.signal import welch def out_snr(audio, ref, fs): """参考信号 ref 与解调输出比对,取带内功率比""" err = audio[:len(ref)] - ref[:len(audio)] * np.std(audio[:len(ref)]) / np.std(ref) f, pa = welch(audio[:len(ref)], fs, nperseg=1024) _, pe = welch(err, fs, nperseg=1024) band = f < FM_A return 10 * np.log10(np.sum(pa[band]) / np.sum(pe[band])) for snr_db in range(0, 41, 5): rx = tx + np.random.randn(len(tx)) * np.sqrt(sig_p / 10 ** (snr_db / 10)) audio = deemphasis(fm_demod(rx, FS, FC), FS, TAU) print(snr_db, round(out_snr(audio, m, FS), 2))err是幅度对齐后的残差,band把比较限制在音频带内。曲线在门限以上应近似线性上升,斜率大约 1,改善量体现在截距上。如果低信噪比段曲线快速塌下来,说明进入门限以下,这是调频的固有特性,不是代码错误。
5.2 仿真发散与波形畸变的对照排查表
调试中最耗时间的不是写代码,而是判断现象属于哪一类。下面这张表覆盖了大部分偶发问题:
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 输出幅度持续增长后溢出 | 积分项累积无界、去加重 α 大于 1 | 检查cumsum是否做了归一化,核对 α 表达式 |
| 频谱出现对称尖刺 | 调制信号未归一化,频偏超限 | 打印max(abs(m)),确认调制度 |
| 解调音频高频缺失 | 去加重时间常数与预加重不匹配 | 对比预加重前后频谱斜率 |
| 输出全为噪声 | 下变频频率与载波偏差过大 | 用 FFT 找载波峰,校正 FC |
| 低频段出现规律起伏 | 低通滤波器阶数过高导致群时延 | 降到 2 至 4 阶再看 |
发散类问题的共同点是某个量随时间单调增长,仿真时长一拉长就暴露。波形畸变类的共同点是频谱上有规律结构,把中间每一级的频谱都画出来,问题落在哪一级就一目了然。验证顺序固定为:先看载波频谱是否干净,再看解调输出是否恢复原信号形状,最后扫信噪比确认门限位置落在预期范围内。
本文还有配套的精品资源,点击获取