简介:这是一套基于Matlab GUI的AM调制系统完整源码,面向通信原理、数字信号调制方向的初学者与课程设计者,可帮助直观理解调幅信号的包络变化、频谱搬移及调制深度对波形的影响。压缩包共5个文件,包含2个M脚本——主程序am_mod.m与谱分析辅助函数plotspec.m,1个FIG图形界面文件以及2张运行结果图,整体仅204KB,文件结构精简、便于二次修改与学习。现已累计171人学习使用,代码在Matlab 2019b环境下验证可运行,直接替换信号参数即可观察不同调制效果,也适合在此基础上拓展为AM解调或双边带等仿真实验。GUI界面兼顾交互操作与可视化输出,运行结果图中清晰展示时域波形与频谱对照关系,可作为数字信号处理课程设计、实验报告或毕业设计的快速参考模板。
1. 数字信号调制里的AM调制系统,为什么值得用MATLAB GUI做一遍
通信原理课里,AM调制通常是学生遇到的第一道"算得出来、看不见"的坎:调制指数从 0.8 调到 1.2,时域包络差多少;两个边带在频谱上隔多远;加噪之后包络检波什么时候开始变糊——手推公式很难建立这种直觉。把 AM调制 做成 MATLAB GUI 交互系统,本质是给数字信号调制 的每个环节装一个可拖旋钮:载波频率、调制信号频率、调制指数、信噪比,改动立即反映在波形和频谱上。这类课程设计源码包不管标着什么期数,结构基本都是 .fig 界面加 .m 回调文件。适合课程设计学生、实验课助教、想快速回补调制解调基础的工程师。接下来按调制端原理、GUI 数据流、解调对比、验证技巧四层往下写。
2. AM调制原理与参数设定:先把调制端信号做对
2.1 如何看懂时域表达式与调制指数
DSB-AM 的表达式是 s(t) = Ac[1 + m·x(t)]·cos(2πfc·t),x(t) 是归一化基带信号。单音调制时 m = Am/Ac,即调制信号幅度与载波幅度之比。数学上要求 0 < m ≤ 1 包络才处于"可用"状态;m = 1 时载波包络最低点正好贴零,属于临界状态;m > 1 之后 (1 + m·x) 会穿过零并变号,包络发生翻转,后面用包络检波解出来的波形就是错的。这也是 AM调制 系统里最值得在 GUI 里演示的现象:同一段调制代码,把 m 从 0.8 拖到 1.5,肉眼就能看见包络翻车。
2.2 采样率、载波频率与观测时长怎么搭配
MATLAB 里所有信号都是离散序列,动手前先定 fs,再定 fc 和 fm。常见做法是 fc 取 8~20 kHz,fm 取 0.5~2 kHz,fs 取 100 kHz 或更高,并且让 fc/fm ≥ 8,否则后面低通滤载波时会把边带一起削掉。FFT 频率分辨率 Δf = fs/N,N 是参与 FFT 的采样点数。要分清 fc±fm 两根边带,至少需要 Δf < fm/2;取 1 秒数据、fs = 100 kHz 时 N = 1e5,Δf = 1 Hz,完全够用。如果只取 10 ms 数据,Δf 涨到 100 Hz,fm = 1 kHz 时谱线会糊成一片。参数之间是联动的,改任何一个都要回头检查另外两个是否还成立。
表 2-1 一套可以直接照抄的AM调制初始参数
| 参数 | 建议值 | 约束与原因 |
|---|---|---|
| fs | 100 kHz | 至少 10 倍 fc,留足滤波器过渡带 |
| fc | 10 kHz | 小于 fs/2,落在显示友好的频段 |
| fm | 1 kHz | 远小于 fc,满足窄带调制关系 |
| m | 0.3 / 0.8 / 1.0 / 1.2 | 覆盖正常、临界、过调制三种场景 |
| 观测时长 | 1 s | 保证 FFT 分辨率 1 Hz,边带不与载波混叠 |
2.3 调制端最小实现:能被 GUI 复用的核心代码
% AM调制最小实现(单音信号) fs = 100e3; % 采样率 100 kHz fc = 10e3; % 载波 10 kHz fm = 1e3; % 调制信号 1 kHz t = (0:fs-1)/fs; % 1 秒时长,采样点数 = fs Ac = 1; % 载波幅度,先归一化 ma = 0.8; % 调制指数,0.8 为正常态 mt = cos(2*pi*fm*t); % 单音基带信号 s = Ac * (1 + ma*mt) .* cos(2*pi*fc*t); % AM已调信号,逐点相乘 % 单边幅度谱,dB 坐标 S = fft(s); P = abs(S(1:fs/2)); % 只取正频率半段 f = (0:fs/2-1); % 频率轴 0~50 kHz plot(f, 20*log10(P + eps)); % 加 eps 防止 log(0)三段代码各管一件事。第一段定时间轴:t 是长度 fs 的行向量,1 秒 10 万个点。第二段完成调制:(1 + ma*mt)是包络,外层乘高频载波,点乘保证逐元素运算。第三段画频谱:fft 默认输出 0~fs 频率范围,单边谱取前一半,频率轴对应 0~50 kHz;用 dB 显示才能同时看见载波和边带的相对高度。这里特意让 N = fs,频率分辨率 1 Hz,10 kHz 载波和 9/11 kHz 边带都落在整数频点上,不加窗也不泄漏。如果把时长改成 0.3 秒,就会出现频谱泄漏,这正好是第 5 章要讲的验证场景。
2.4 调制效率:总功率分给了谁
AM 信号总功率 P_AM = Pc(1 + m²/2)。以 m = 1 为例,总功率是载波功率的 1.5 倍,两个边带加起来只拿到 0.5 份,最大调制效率只有三分之一。这个结论直接影响 GUI 设计:如果界面上放了功率指示条,就应该用这个公式计算理论值,而不是拿 sum(s.^2)/length(s) 去对比——那是总功率,不是携带信息的边带功率。效率低正是 AM 后来被各种抑制载波方案取代的根本原因,做课程设计时把这个数字展示出来,比单纯画波形更能说明问题。
3. MATLAB GUI 回调里的数据流:界面参数怎么变成波形
3.1 GUIDE 还是 App Designer:课程设计源码的选型现实
这类课程设计源码包多数是 GUIDE 工程,形态是 .fig 界面文件加同名 .m 回调文件。GUIDE 的好处是把控件创建代码和回调函数摊开,Tag 找控件、handles 传数据,机制一目了然,实验课老师维护也方便。缺点是这个工具不再有功能更新,MATLAB 新版本主推 App Designer,回调写法更接近事件驱动。选型建议很直接:要最快在现有 .fig 上改参数、加按钮,直接用 GUIDE;从零开始做新工程、面向新版本,走 App Designer。不要为了"新"把时间花在迁移上,课程设计的评分点在调制解调正确性,不在界面框架。
3.2 控件命名与 handles 结构体
界面控件至少要有:三个参数编辑框(fs/fc/fm)、一个调制指数滑杆、一个"调制"按钮、两个 axes,可选加一个噪声编辑框。控件 Tag 建议统一命名:edit_fs、edit_fc、edit_fm、slider_m、btn_mod、axes_time、axes_freq。Tag 定下之后,回调代码只依赖 Tag 找对象,之后调整界面布局不会牵连代码。GUIDE 工程里所有回调的第一个参数是 hObject,第二个是 eventdata,第三个是 handles——handles 就是一个结构体,里面装着当前界面所有控件的句柄,也用来在回调之间传数据。任何回调里改了 handles 的字段,都必须执行 guidata(hObject, handles) 存回去,否则下一次回调读到的还是旧值。
3.3 按钮回调:读参数、调制、画图一次走完
function btn_mod_Callback(hObject, eventdata, handles) % 1. 从编辑框读数值,str2double 兼容非数字输入 fs = str2double(handles.edit_fs.String); fc = str2double(handles.edit_fc.String); fm = str2double(handles.edit_fm.String); ma = str2double(get(handles.slider_m, 'Value')); % 旧式写法更兼容 % 2. 生成调制信号,公式与第 2 章完全一致 t = (0:fs-1)/fs; mt = cos(2*pi*fm*t); s = 1 * (1 + ma*mt) .* cos(2*pi*fc*t); % 3. 数据暂存到 handles,供解调、保存等后续回调复用 handles.s = s; handles.fs = fs; handles.fc = fc; handles.fm = fm; handles.t = t; guidata(hObject, handles); % 必须写回,否则其他回调读不到 % 4. 调私有函数统一绘图,避免回调里堆长代码 plot_am(handles); end function plot_am(handles) % 先 cla 清空旧曲线,再 plot 到指定 axes axes(handles.axes_time); cla; hold on; plot(handles.t, handles.s, 'LineWidth', 1); xlim([0 5/handles.fm]); % 只画前 5 个基带周期,包络更清楚 axes(handles.axes_freq); cla; hold on; S = fft(handles.s); f = (0:handles.fs/2-1); plot(f, 20*log10(abs(S(1:handles.fs/2)) + eps)); xlim([0 2*handles.fc]); % 只看载波附近,边带可分辨 endhandles.edit_fs.String是 R2014b 之后支持的属性访问写法,get(handles.edit_fs, 'String')是传统写法,两者在 GUIDE 工程里混用没问题,但在旧版 MATLAB 上跑就要统一改成 get 写法。时域图 xlim 取 5 个基带周期,是为了让包络形态肉眼可读;如果画整秒数据,10 万个点挤在一起只能看见一条实心带。频谱 xlim 取到 2fc,载波和两根边带占据图的右侧一半,视觉上更清楚。后面要加解调按钮,直接从 handles.s 取信号即可,不需要重新生成——这就是 handles 暂存数据的目的。
3.4 滑杆连续调节与回调风暴
Slider 的 Value 在拖动过程中会连续触发回调,每次回调都重新生成 10 万点信号再重画,界面会明显卡顿。处理办法是节流:只在值变化超过阈值时才重算。阈值取 0.01,对应滑杆 0~2 范围内 200 个有效挡位,肉眼已经看不出跳变。
function slider_m_Callback(hObject, eventdata, handles) new_m = get(hObject, 'Value'); if abs(new_m - handles.last_m) < 0.01 return; % 变化小于阈值,跳过重画 end handles.last_m = new_m; guidata(hObject, handles); btn_mod_Callback(hObject, eventdata, handles); % 复用按钮逻辑 end注意 handles.last_m 必须在 OpeningFcn 里初始化,否则第一次拖动滑杆时回调读到不存在的字段,直接报 Undefined field 错误。这也是 GUIDE 工程里最典型的崩溃来源:字段忘了初始化,或者改了 handles 忘了 guidata 写回。排查这类问题就盯两条:字段在哪赋值、有没有写回。
4. AM调制解调链路与加噪对比:包络检波和相干解调谁先扛不住
4.1 包络检波:两行代码拿到基带
包络检波的依据是 AM 信号包络恰好就是 Ac(1 + m·x(t))。MATLAB 里最直接的包络提取方式是 abs(hilbert(rx)):hilbert 构造解析信号,取模得到瞬时包络,再做低通去除残余的高频纹波并去掉直流。代码分三步:
% 包络检波:hilbert 取包络 + Butterworth 低通 env = abs(hilbert(rx)); % 瞬时包络 env = env - mean(env); % 去掉直流(载波泄漏分量) [b, a] = butter(4, handles.fm/(handles.fs/2)); % 截止频率取基带信号频率 y_env = filter(b, a, env); % 低通输出基带波形 % 从包络反算调制指数,用于验证整条链路 Vmax = max(y_env); Vmin = min(y_env); m_est = (Vmax - Vmin) / (Vmax + Vmin);butter 的四阶低通在通带内足够平,截止频率压到 fm 会把包络的边角磨圆一点,属于正常现象。需要注意:如果 m > 1 导致过调制,包络翻转后 Vmax 和 Vmin 的逻辑失效,m_est 会算出一个离谱的值。这个现象本身就是绝佳的教学演示——设定与测量对不上时,先检查是不是过调制。
4.2 相干解调:把载波再乘回来
相干解调的思想是把已调信号再乘一次同频同相载波,产生一个 2fc 分量和一个基带分量,低通滤掉高频就恢复基带。关键前提是本地载波必须与发送端同频同相,相位差 φ 会让输出幅度乘 cos φ,φ = 90° 时输出直接消失。GUI 里值得加一个"本地载波相位"滑杆,学生拖动相位就能直观看到解调输出的衰减,这比任何教材里的公式都直观。
% 相干解调:混频 + 低通 phi = 0; % 本地载波相位差 mix = rx .* cos(2*pi*handles.fc*handles.t + phi); % 混频 [b2, a2] = butter(4, (handles.fm + 500)/(handles.fs/2)); % 过渡带放宽 500 Hz y_co = 2 * filter(b2, a2, mix); % 乘 2 补偿混频损耗混频后基带分量的幅度会减半,乘 2 是补偿这一步损耗。低通截止频率取 fm + 500 Hz 而不是正好 fm,是为了给滤波器留出过渡带,避免基带高频被削。如果只想快速验证结果,MATLAB 通信工具箱有 amdemod(rx, fc, fs, 'am') 一行搞定;但课程设计里建议把混频和滤波手写出来,评分点往往就在中间信号上,直接用现成函数反而讲不清楚解调原理。
4.3 加噪:SNR 的换算口径
% 按指定全局信噪比加高斯白噪声 snr_db = 15; % 单位 dB p_sig = mean(rx_ideal.^2); % 已调信号平均功率 p_noise = p_sig / 10^(snr_db/10); % 反推噪声功率 rx = rx_ideal + sqrt(p_noise) * randn(size(rx_ideal));这里的 SNR 是"已调信号总功率 / 噪声功率",不是解调后的基带信噪比。AM 信号里载波占掉了大部分功率,总功率 SNR 看着不低,解调后的基带信噪比可能仍然很差——这正是 AM 功率效率低的直观体现。randn 每次运行生成的噪声都不同,想复现同一画面做对比实验,先执行 rng(1) 固定随机种子,保证两次演示加的是同一段噪声。
4.4 两套解调方案的适用边界
| 对比项 | 包络检波 | 相干解调 |
|---|---|---|
| 核心操作 | abs(hilbert()) + 低通 | 乘本地载波 + 低通 |
| 载波同步 | 不需要 | 需要,相位差决定输出幅度 |
| 过调制 m > 1 | 解调失败 | 仍可解调,但带宽无收益 |
| 低信噪比 | 门限效应明显 | 噪声表现相对稳定 |
| 代码量 | 2 行核心 | 3 行核心 |
做演示时不要只跑 m = 0.8 的正常场景。把过调制和低信噪比两行分别拖出来,先固定 m = 1.2 看包络检波怎么失效,再把 SNR 降到 10 dB 以下观察包络噪声变粗,两套方案的边界条件就都清楚了。
5. 验证 AM调制 系统正确性的三个落地技巧
5.1 用解调端反算调制指数
第 4.1 节里的 m_est 可以直接当作整条链路的校验器:调制端设 m = 0.7,解调端包络反算出 0.65~0.75,说明从调制到解调的环路打通了;偏差过大,先查低通截止频率是不是压得太狠,再看是不是触发过调制。建议在 GUI 里加一个只读文本框实时显示 m_est,学生拖滑杆时能直接看到设定值与测量值的差距,这个对照比任何曲线都更能说明问题。
5.2 用频谱整数频点判断泄漏
载波 10 kHz、边带 9/11 kHz 在 fs = 100 kHz、时长 1 s 时正好落在整数频率点上,FFT 输出没有任何泄漏。如果看到载波两侧出现密集的"裙边",第一个怀疑对象是观测时长被改短了:取 0.3 s 时 Δf = 3.33 Hz,10 kHz 不再是整数频点,泄漏裙边就会铺开。调试方法很简单,把时长改回 1 s,裙边消失即确诊;不想改时长,给信号加 hann 窗能压住泄漏,但代价是主瓣变宽,边带分辨率下降。
5.3 三类常见报错与对应修法
| 报错现象 | 原因 | 修法 |
|---|---|---|
| Undefined field 'last_m' | OpeningFcn 里没初始化字段 | 在 OpeningFcn 中给 last_m 赋默认值 |
| axes 上新旧曲线叠在一起 | 绘图前没 cla,或 hold on 没管住 | 每次绘图前先 cla,再指定当前 axes |
| 滤波后波形幅度明显偏小 | 混频损耗没补偿 | 相干解调输出乘 2 后再显示 |
最后给一条通用调试路径:从调制端固定一组参数,先不解调,只画发射波形确认包络形态正确;再加解调,先不加噪声,确认恢复波形与基带重合;最后才加噪声扫 SNR。每一级只引入一个变量,AM调制 系统里绝大多数问题都能在这一条链路上定位到具体模块。
本文还有配套的精品资源,点击获取