简介:配套郑君里《信号与系统》教材的MATLAB综合实验代码包,面向电子工程、通信、自动化等专业学生及信号处理初学者,帮助通过仿真实验理解时域与频域分析、系统响应、滤波器设计等抽象概念。压缩包共114个文件,以103个m脚本为主体,配合8个mdl模型、2个fig交互界面和1个tif结果图,整体仅160KB,轻量便携;目前已有123人学习下载。代码覆盖基础信号生成与处理、线性时不变系统特性分析、卷积运算、拉普拉斯变换与Z变换应用、频率响应分析及数字滤波器设计等核心实验,各章节脚本按教材知识点组织,运行后可直观呈现信号波形、系统响应曲线与频谱特征。代码注释简洁,配有输入样例,既能辅助课堂理论教学,也能提升读者使用MATLAB开展工程仿真的动手能力,是一份理论与实践结合的实用学习资料。
1. 这个代码包到底是什么
1.1 教材、课程与这个 zip 的来龙去脉
郑君里先生的《信号与系统》教材,在国内通信、电子、自动化这类专业里几乎是人手一套。上中下三册的知识体系,把连续时间信号分析、离散时间信号处理、傅里叶变换、拉普拉斯变换、z 变换整个链路串得明明白白。但教材终归是纸面上的数学推导,真正要把“信号的频谱长什么样”“系统在什么条件下稳定”这类问题变成直观理解,光看书远远不够。
谷源涛老师长期在清华大学讲授这门课,他配套整理的这套 MATLAB 综合实验代码,就是把教材里的核心知识点逐步“翻译”成可运行的 MATLAB 脚本。压缩包里既有实验说明文档,也有按实验组织好的 m 文件,覆盖了信号生成、卷积计算、傅里叶变换、拉普拉斯变换、抽样定理等典型实验模块。你可以把这份代码当成两个东西来用:一是学信号与系统时的可视化教具,二是自己动手写信号处理代码的参考起点。
这篇文章不打算复述课件内容,而是想从一个实际用过这套代码的人的角度,讲清楚三件事:这套代码的设计逻辑是什么、核心实验怎么跑通、跑的过程中会遇到哪些坑。无论你是刚开始学这门课的学生,还是想用 MATLAB 快速验证信号处理算法的工程师,顺着这篇文章的思路走,都能少走不少弯路。
1.2 打开 zip 之前,你该知道的三件事
先说拿到文件之后的第一印象。解压之后,你大概率会看到一批以实验序号命名的文件夹或脚本文件,比如 exp1、exp2 这种命名方式,每个文件夹里是配套的 .m 脚本和必要的说明文档。我第一次打开时最直观的感受是:代码不是按教材章节严格排列的,而是按“实验任务”组织的,一个实验文件往往会同时用到多个章节的概念。
所以在开始跑代码之前,有三件准备工作建议先做。
第一,确认 MATLAB 版本。这套代码早期主要针对 MATLAB R2010 到 R2016 左右版本编写,里面会用到 ezplot、fplot 这类老式绘图函数。新版 MATLAB(R2020 之后)虽然大部分还能兼容,但个别函数会提示推荐替换,不影响运行,只是会有 warning。建议安装 MATLAB R2020a 或者更新的版本,配合 Signal Processing Toolbox 和 Symbolic Math Toolbox 这两个工具箱,绝大多数实验都能直接跑通。
第二,把整个目录添加到 MATLAB 路径里。很多新手直接在命令行窗口输入脚本名,结果报“未定义函数或变量”,就是因为当前工作目录不对。正确做法是在 MATLAB 主界面左侧的“当前文件夹”窗口里定位到解压目录,右键选择“添加到路径”,或者用命令 addpath(genpath('你的路径')) 一次性把子文件夹全部加进去。
第三,先翻一下 README 或者实验说明文档。这套代码虽然以 m 文件为主,但每个实验的说明文字里往往藏着关键参数,比如信号的时域范围、采样点数、截止频率等。直接改参数去跑,比对着代码盲猜要高效得多。如果你解压后发现说明文档是 PDF 或者 Word 格式,建议先通读一遍再动手,省得后面反复踩坑。
2. 综合实验的整体设计思路
2.1 实验目录背后的知识地图
把整个代码包通读一遍,你会发现这些实验不是随便拼凑的,而是按信号与系统课程的主线知识在推进。最前面的实验基本围绕信号本身展开:单位冲激信号、单位阶跃信号、实指数信号、正弦信号、抽样信号,通过 MATLAB 把这些信号画出来,观察它们的时域波形和数学表达式之间的对应关系。紧接着是信号的基本运算,时移、反转、尺度变换,这些在教材里用公式一句话就带过了,但在代码里你能直观看到 y(t)=f(2t) 到底把波形压缩成什么样。
再往后就进入系统分析的核心了。卷积积分实验会引导你用 conv 函数或者自定义卷积函数,比较连续信号卷积和离散信号卷积的差异;傅里叶变换实验会画出一系列常见信号的幅度谱和相位谱,用代码验证时域和频域的对称关系;抽样定理实验更是经典中的经典——不同抽样频率下信号恢复的效果对比,混叠现象一目了然。
最后一部分是变换域分析。拉普拉斯变换实验利用符号计算求系统的零极点,画出零极点分布图,再结合系统函数判断稳定性;有的版本还带 z 变换的入门实验,为数字信号处理做衔接。这样一套组合拳打下来,教材的三大主线——信号描述、系统响应、变换域分析——就全部覆盖了,知识框架的完整度非常高。
2.2 为什么选 MATLAB 而不是纯数学推导
这个问题我自己琢磨了很久。学信号与系统的时候,最容易产生的困惑就是:公式我都记住了,卷子我也会做,但“傅里叶变换把时域信号变成频域表示”这件事到底意味着什么,脑子里始终缺一个具体的画面。
MATLAB 的价值恰恰在于把这个画面补上了。举个例子,一个矩形脉冲信号的傅里叶变换是 Sa 函数,公式推导几分钟能出来,但你真的去画频谱图,看到主瓣、旁瓣、过零点这些特征时,对“时域越窄、频域越宽”这句话的理解会完全不同。再比如抽样定理,课本上说抽样频率小于 2 倍最高频率时会发生频谱混叠,但混叠是什么样子?在 MATLAB 里把抽样频率调到低于奈奎斯特频率,恢复出来的波形直接扭曲给你看,这个冲击力是纯数学推导给不了的。
另外一个现实的原因是效率。信号与系统课程里大量涉及符号运算和数值计算,像卷积积分,用手算一个简单的门函数卷积都要折腾半天,用 MATLAB 一行 conv() 就出来了。对学生来说,把省下来的时间花在观察结果、分析现象上,学习效率高得多;对工程应用来说,MATLAB 本身就能无缝过渡到信号处理的实际仿真验证,学的东西不浪费。我记得当年考研复习的时候,每次被某个变换的物理意义卡住,就会翻出这套代码跑一遍对应实验,很多疑惑当场就通了。
3. 核心实验实操与复现要点
3.1 信号生成与基本运算实验
第一类实验在整套代码里属于“开胃菜”,但非常重要,因为很多同学对信号的时域波形没有基本概念。以单位冲激信号为例,MATLAB 里没有现成的冲激函数,代码里通常用两种方式逼近:一种是通过符号数学表达式的极限定义,另一种是用一个窄脉冲近似,比如在 n=0 处取值为 1、其余位置为 0 的离散序列。
实操的时候有个细节要注意:画单位冲激信号时,如果直接用 stem 函数画离散冲激,显示的是一根根竖线;如果用 plot 连续绘制,脉冲会变成一条三角形的伪波形。代码里通常会在注释中说明用 stem 还是 plot,建议初学者两种都试一下,对比着看离散信号和连续信号画法的区别。指数衰减正弦信号这类复合信号也值得亲手画一遍,调节衰减系数 alpha,能直观看到波形衰减快慢的变化,这可以为后面理解系统响应的瞬态分量做铺垫。
信号的基本运算实验相对简单,但有一个容易踩坑的地方:时移运算。很多同学想当然地认为 f(t-t0) 就是把波形整体往右移,但如果你在 MATLAB 里用数值方式构造一个数组,直接对横坐标加加减减,很容易把数组下标搞错,导致结果完全不对。正确的做法是对时间向量做平移,而不是对采样点数组做平移,也就是先定义 tt = t - t0,再把原信号的下标和新的时间向量对应起来画图。这套代码里的写法值得细看,它就是标准的向量化处理思路。
3.2 时域卷积与系统响应实验
卷积实验是我觉得这套代码里性价比最高的一块。它一般会提供两种计算方式:一种是直接用 MATLAB 内置的 conv 函数算离散卷积;另一种是让读者自己编写数值卷积函数,再对比结果。离散卷积直接用 conv,几乎没有技术门槛,关键是要理解 conv 输出向量的长度。设两个序列长度分别为 N 和 M,卷积结果是长度 N+M-1 的序列,代码里通常要加上对应的横坐标标注,否则画图时坐标轴对不上,波形看着就是错位的。
连续信号的卷积稍微麻烦一点,因为 MATLAB 没有内置的连续卷积指令,常见的做法是把连续信号离散化,用 conv 逼近,再乘以采样间隔修正幅值。这里有一个非常重要的细节:采样间隔的选择会影响卷积结果的精度。间隔越小,结果越接近理论值,但计算量增大;间隔过大,卷积波形会出现明显失真。代码里通常会给一个推荐的采样间隔范围,比如 0.01 或 0.05,跑通之后你可以试着改大改小,观察结果的差异,这个过程比单纯跑通代码更有收获。
系统响应实验本质上是卷积概念的延伸——已知系统的单位冲激响应 h(t),输入信号 f(t) 激励下的零状态响应就是两者的卷积。许多同学容易忽略的是,这个结论成立的前提是系统满足线性时不变(LTI)条件。代码实验里一般会构造一个简单的 RC 电路作为示例系统,用卷积方法求输出,再和直接用微分方程数值解法得到的结果对比,两条曲线重合的那一刻,“卷积求系统响应”这个知识点就真正刻在脑子里了。
3.3 傅里叶变换与抽样定理实验
傅里叶变换相关的实验是整套代码的重头戏。MATLAB 做傅里叶变换有两条路线:一是用符号数学工具箱的 fourier() 函数求闭式表达式,适合理论推导验证;二是用 fft() 做离散傅里叶变换,适合数值分析。
用 fourier() 函数时要注意,返回值往往是包含了 heaviside(单位阶跃)函数的符号表达式,不要觉得奇怪。比如求双边指数信号的傅里叶变换,结果里会出现绝对值符号。这时候用 pretty() 或者 simplify() 把表达式整理一下,再配合 subs() 代入具体数值画图,就能得到幅度谱和相位谱。
用 fft() 做频谱分析时,有几个高频踩坑点。第一,fft 计算的结果是复数,画幅度谱要用 abs() 取模,画相位谱要用 angle(),别直接 plot。第二,fft 默认的输出顺序是 0 到采样率的一半、再是负频率部分,所以画频谱前通常要用 fftshift() 把零频移到中间,否则图形中心会有一条看起来像“直流分量错位”的跳变。第三,频率轴的刻度要自己构造,公式是 fs*(0:N-1)/N,单位是 Hz,这个刻度弄错的人特别多。
抽样定理实验更是把知识变成了肉眼可见的现象。代码里一般会这样做:选择一个带限信号,比如频率为某个值的正弦信号或者带限的复杂信号,然后用不同的抽样频率 fs 去采样,每个频率下再用插值或者理想的低通滤波恢复原信号,最后把原始信号和恢复信号叠加画出对比图。当 fs 大于两倍最高频率时,恢复波形和原波形基本重合;当 fs 逼近甚至低于奈奎斯特频率时,恢复波形和原信号明显不一样,混叠的失真一眼就能看出来。
我在实际操作中强烈建议自己跑一个对照实验:固定信号不变,逐步降低抽样频率,观察恢复出的波形从“几乎重合”到“面目全非”的渐变过程。这个过程会让你对“为什么语音电话的频率范围要限制在 300Hz 到 3400Hz”这类工程问题有非常直观的认知。顺便说一句,抽样定理这块的实验输出图也是答辩时最好用的素材,把不同频率下的恢复效果并排放在一张图里,说服力远强过文字描述。
3.4 拉普拉斯变换与系统零极点分析实验
拉普拉斯变换部分对很多学生来说是难点,因为从傅里叶变换到拉普拉斯变换,从频域到复频域,抽象程度进一步提高。这套代码的处理方式是把抽象的概念“画”出来,帮你建立几何直觉。符号计算方面,laplace() 和 ilaplace() 两个函数就可以实现大多数教材例题的验证。比如求单边指数信号的拉普拉斯变换,一行代码得到结果 1/(s+a),再用 ilaplace() 能反变换回时域表达式,非常适合用来检查手算的作业对不对。
零极点分析是这一部分的精髓。系统的稳定性可以直接从系统函数 H(s) 的零极点分布判断出来。代码里一般会用 pzmap() 函数或者先求出零极点再手动绘制散点图的方式展示。你在图上会看到,只要有一对极点在 s 平面的右半平面,系统就是不稳定的;所有极点都在左半平面,系统才是稳定的。把这句话和图对应起来,比背十遍结论都管用。
这里我特别想提醒一个细节:不要只盯着“极点是否在左半平面”这个结论,要动手改代码里的参数,观察极点移动。比如把系统函数的某个系数从小改到大,看极点从实轴移到复平面再越过虚轴,系统从“过阻尼”“欠阻尼”到“不稳定”的变化过程。这个实验做完,你对“系统参数如何影响动态性能”的理解会超过很多只做教材习题的同学。
4. 代码复用与工程化改造建议
4.1 老代码适配新版 MATLAB 的策略
这套代码毕竟是几年前甚至十几年前写的,拿到新版 MATLAB 上跑,多多少少会遇到兼容问题。我总结了三类最常见的适配场景,基本覆盖了大多数情况。
第一类是绘图函数的老化。代码里大量使用 ezplot(),这个函数在新版本中不推荐使用,但不至于报错,只是会给出提示。如果你想消除这些提示,可以手动把 ezplot(f) 替换成 fplot(f) 或者 fplot(f, [xmin xmax]),参数基本对应,改动成本很低。另外,老代码里常用 plot(t, y) 后接 grid on 的写法,新版依然通用,这部分不用动。
第二类是符号计算接口的变化。老版本的 sym、syms、fourier、laplace 这些函数接口在新版里基本保持一致,但有些细节需要留意。比如新版本中符号变量默认是复数形式,如果你希望变量是实数,最好显式声明:syms t real,否则某些化简结果会带 conj() 共轭符号,看起来非常难受。
第三类是脚本文件编码问题。早期代码如果是在 Windows 中文环境下编写的,可能存在 GBK 编码,新版 MATLAB 在 UTF-8 环境下打开时中文注释会乱码。遇到这种情况,用记事本或者 VS Code 把文件的编码统一转成 UTF-8 后重新保存即可,代码本身不受影响。如果源文件里的中文注释乱到看不下去,就直接把注释删掉,反正核心逻辑在代码里,不影响运行。
4.2 把实验代码改造成工具箱的进阶玩法
跑通实验只是第一步,把这套代码改造成自己的“信号处理工具箱”才是更进阶的玩法。我的建议是不必全部改造,而是挑几个高频功能封装成函数。比如信号生成,你可以把单位冲激、阶跃、门函数、指数信号、正弦信号统一封装成几个函数文件,输入参数是时间范围和幅度,输出是时间和信号值两个向量。下次做课程设计或者实际项目时,直接调用自己的函数,比每次重新写生成逻辑效率高得多。
再比如卷积和频谱分析,可以封装成一个自动对比模块:输入两个序列和采样频率,自动计算卷积或傅里叶变换,并绘制时域和频域的对比图。这其实非常接近工程上“仿真脚本模块化”的思路。我自己在给学弟学妹改代码的时候,就经常把这套代码里的零极点分析部分抽出来,加上注释,作为判断系统稳定性的标准工具。
还有一点值得尝试:把不同实验的结果图批量导出。MATLAB 里用 exportgraphics 或者 saveas 函数批量导出图片,可以把所有关键波形整理成一份 PDF 报告,这在做课程大作业、准备答辩材料时非常实用。代码里通常只展示运行结果,没有现成的导出脚本,这块你可以自己补充。整个过程顺便练了 MATLAB 的文件操作和脚本组织能力,一举两得。
5. 常见问题与避坑指南
5.1 运行报错排查速查表
跑代码过程中遇到的问题,我把高频的整理成了下面这张表,方便你直接对照排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 命令窗口报“未定义函数或变量” | 当前工作目录不对,或未将代码目录添加进路径 | addpath(genpath('代码根目录')),或在当前文件夹窗口定位到目标目录 |
| plot 画出来是空白图 | 可能数据全是 NaN 或 Inf | 检查时间向量是否有问题,t=0 时某些函数出现 0/0 形式,用 eps 代替 0 |
| 中文字体或者中文注释乱码 | 文件编码与 MATLAB 环境不匹配 | 用编辑器将文件转成 UTF-8 编码保存 |
| ezplot 提示不推荐使用 | 用了新版本 MATLAB | 替换成 fplot,参数稍作调整即可 |
| fft 画出的频谱图两边高中间低 | 没做 fftshift,零频在两端 | 对频率向量和频谱同时做 fftshift |
| 卷积结果的波形幅度偏大或偏小 | 连续卷积没有乘以采样间隔 | 离散化逼近连续卷积时,结果需要乘以 dt(采样间隔) |
| 符号计算出现大量 conj() | 符号变量被认成复数 | 声明 sym(...,'real') 或 syms t real |
| 解压后某些文件缺失 | zip 包编码导致文件名乱码,误以为缺失 | 用支持 GBK 编码解压的工具重新解压 |
这张表的来源是我自己跑这套代码和帮别人调代码时踩过的坑,覆盖了大约八成以上的报错场景。遇到没列入的情况,优先看命令窗口的报错信息里提到的那一行,定位到具体代码行再分析,效率最高。
拿卷积那段代码举例子,有个同学跑出来波形幅度比理论值大了一百倍,百思不得其解。后来我一看,他没有用采样间隔去修正卷积结果的幅值,连续信号数值卷积本身需要乘上 dt 才能近似积分运算,他直接拿离散卷积的结果当连续卷积用了。这类问题在代码注释里其实有提到,但很容易被忽略,算是一个隐藏比较深的坑。
5.2 学习这套代码的正确姿势
最后聊一个可能被很多人忽略的问题:怎么“学”这套代码才最有效果。第一个建议是不要直接双击运行就完事。正确路径应该是:先看实验说明文档,明确这个实验要验证什么知识点;然后手动运行一遍看结果;随后在关键代码行设置断点,或者逐行执行,搞清楚每一行在干什么;最后改动参数去观察输出变化。这套流程走下来,才算真正“消化”了一个实验。
第二个建议是把代码和教材对照着看。每跑完一个实验,回到教材找到对应的推导公式,对比代码里使用的数值方法和教材里的解析方法,你会发现两者的结果一致性本身就是最好的验证。尤其是傅里叶变换和拉普拉斯变换的实验,代码里直接算出来的表达式和教材的结论几乎一模一样,这种“闭环验证”对建立知识体系非常有帮助。
第三个建议是主动“破坏”代码。这是一种很有意思的学习方式:故意把参数改成不合理值,比如把抽样频率改得非常低,或者把系统函数的极点改到右半平面,观察运行结果会发生什么变化。报错本身就是学习信号,搞清楚为什么报错、为什么波形失真,这些错误的教训往往比正确运行十次更深刻。
我在带同学的时候经常用一个比喻:这套代码就像一本带答案的习题集,真相都在里面,但你直接抄答案没有任何意义。花时间把每个实验从头到尾自己改一遍、跑一遍、想一遍,收获会完全不一样。还有一个很实际的操作是多版本对比。不要只满足于代码跑出图,试着把同一个实验用不同方法实现——比如卷积实验,一手用内置 conv,另一手自己写数值卷积循环;傅里叶变换,一手用符号计算的 fourier,另一手用数值的 fft。两个结果画在一起对比,既能验证自己对原理的理解,也能积累更多 MATLAB 实操技巧,后面做项目会顺手很多。
6. 写在最后的一些体会
说实话,这套代码从大学用到工作,从自己学信号与系统用到辅导别人学,是慢慢“榨干”价值的。每次回头翻它,都能发现当初没注意的细节。它不是什么华丽的工程项目,代码风格甚至有点朴素,但它把信号与系统课程那些最让人头疼的抽象概念,用最直观的方式铺在了你面前。花一个周末从头到尾把实验跑一遍,比闷头刷一个月题都更能帮你建立直觉。
最后再分享一个小技巧:跑完全部实验后,试着把其中两三个实验的代码合并起来,比如在卷积实验里加入频谱分析,观察时域卷积和频域相乘的结果是否一致。这个“跨界”操作做完,你对这门课的理解就真正串起来了。
本文还有配套的精品资源,点击获取