2. MATLAB零基础快速上手

水声通信仿真最终要落到“产生信号—处理信号—观察结果”这条链路上,而 MATLAB 正是承载这条链路最常用的工具。本节不追求把 MATLAB 讲成一本语言手册,而是围绕仿真中真正会反复用到的四件事展开:矩阵与向量运算、脚本与函数、绘图、调试,并用一个“生成正弦信号并显示频谱”的小任务把它们串起来。学完本节,你应当能独立写出一个可运行、可修改、可复现的脚本,并看懂它输出的时域波形和频谱图。

一、先建立“一切皆矩阵”的直觉

MATLAB 的名字来自 Matrix Laboratory,它的基本数据单位是矩阵,标量是 1×1 矩阵,向量是 1×n 或 n×1 矩阵。水声仿真里,一个采样序列就是一行或一列向量,一段多通道接收数据就是一个矩阵,每一列对应一个阵元或一次快拍。理解这一点,后面的索引、运算和绘图都会顺理成章。

创建向量最常用的是冒号运算符和 linspace。例如 t = 0:1/fs:T-1/fs 生成从 0 开始、步长 1/fs 的时间序列;f = linspace(-fs/2, fs/2, N) 生成等间隔的频率轴。索引从 1 开始而不是 0,这是初学者最容易出错的地方:x(1) 是第一个元素,x(end) 是最后一个元素,x(1:100) 取前 100 个点。矩阵用分号换行、逗号或空格分列,例如 A = [1 2; 3 4]。

运算要区分“逐元素”和“矩阵意义”。A*B 是矩阵乘法,A.*B 是对应元素相乘;同理有 ./ 和 .^。在信号处理中,对向量逐点做运算几乎都要加点,例如给信号加窗 xw = x .* w,或者计算功率 p = x .^ 2。忘记加点往往不报错但结果完全错误,这是调试时的高频陷阱。

二、脚本与函数:把一次性的命令变成可复用的工具

在命令行窗口逐条输入适合探索,但仿真需要可重复执行,因此应写成脚本(.m 文件)。脚本是一串按顺序执行的命令,共享工作区变量;函数则封装输入输出,内部变量不污染工作区。一个实用的习惯是:主脚本负责参数设置和流程编排,具体算法写成函数。

函数的基本形式是 function [y, f] = mySpectrum(x, fs),文件名必须与函数名一致。函数内部应尽量只依赖传入参数,避免隐式使用全局变量,这样换一组数据时不会出现“结果对不上却找不到原因”的情况。下面给出本节实战任务的完整脚本框架,参数均为教学示例值,实际使用时按需修改:

fs = 8000;              % 采样率 8 kHz(示例)
T  = 0.5;               % 信号时长 0.5 s
f0 = 1000;              % 正弦频率 1 kHz
A  = 1;                 % 幅度
N  = round(fs*T);       % 采样点数
t  = (0:N-1)/fs;        % 时间轴,从 0 开始
x  = A*sin(2*pi*f0*t);  % 生成正弦信号

% 频谱:加窗后做 FFT,并做幅度归一化
w   = hann(N)';         % 汉宁窗,行向量
xw  = x .* w;
X   = fft(xw);
X   = X(1:N/2+1);       % 取单边谱
P   = abs(X)/(sum(w)/2);% 幅度校正
f   = (0:N/2)*fs/N;     % 频率轴

figure;
subplot(2,1,1); plot(t, x); xlabel('时间/s'); ylabel('幅度');
title('正弦信号时域波形'); grid on;
subplot(2,1,2); plot(f, P); xlabel('频率/Hz'); ylabel('幅度');
title('单边幅度谱'); xlim([0 2000]); grid on;

这段代码体现了几个关键点。第一,时间轴用 (0:N-1)/fs 而不是 0:1/fs:T,保证点数与 FFT 长度严格一致。第二,频谱分析前加窗可以减小截断引起的频谱泄漏,汉宁窗是常用选择;加窗会降低幅度,因此用 sum(w)/2 做归一化校正。第三,只取前 N/2+1 个点得到单边谱,频率分辨率 Δf = fs/N,本例中为 8000/4000 = 2 Hz。这些量之间的关系,正是仿真中判断“频谱是否合理”的依据。

三、绘图:让结果“看得见”

绘图不是装饰,而是验证手段。时域图看波形是否完整、有无削顶或直流偏置;频域图看峰值位置是否落在预期频率、旁瓣是否合理。常用命令包括 plot、stem(离散序列)、subplot(分图)、xlabel/ylabel/title、grid on、xlim/ylim。对频谱建议用 plot 而非 stem,点数多时更清晰;对短序列则 stem 更能体现离散性。

下图把本节任务的信号链与关键参数关系画在一起:从参数设置、时间轴构造、正弦生成、加窗、FFT、单边谱与幅度校正,到最终两张图的判读要点。它对应的是“数据流与参数关系”,而不是操作步骤清单,便于你在写代码时对照检查每一环的输入输出。

正弦信号生成与频谱显示:数据流与参数关系 参数设置 fs = 8000 Hz T = 0.5 s f0 = 1000 Hz A = 1 N = fs·T = 4000 构造时间轴 t = (0:N-1)/fs 长度严格等于 N 避免 0:1/fs:T 的 点数不一致问题 生成正弦 x = A·sin(2πf0t) 每周期采样 fs/f0 = 8 点 满足奈奎斯特条件 加窗 w = hann(N) xw = x .* w 抑制泄漏 FFT X = fft(xw) 长度 N 复数结果 单边谱与幅度校正 取 X(1:N/2+1) P = |X|/(sum(w)/2) f = (0:N/2)·fs/N 频率分辨率 Δf = fs/N = 8000/4000 = 2 Hz 绘图与判读 subplot(2,1,1) 时域 subplot(2,1,2) 频域 峰值应在 1000 Hz 结果判读要点 时域:0.5 s 内应有 500 个周期,波形无削顶、无直流偏置;频域:单边谱峰值应出现在 1000 Hz,幅度接近 1。 若峰值偏移或出现明显杂散,优先检查 fs、N、时间轴长度是否一致,以及是否漏写逐元素运算符“.”。

四、调试:从“报错”到“结果不对”

MATLAB 的调试分两类。第一类是语法与维度错误,例如索引越界、矩阵维度不匹配,命令行会给出红色提示和出错行号,按提示检查即可。第二类是“能运行但结果不对”,这类更需要方法:先用小规模数据手算验证,再用 size、length、max、min、isnan 检查中间变量;在可疑行设置断点(编辑器行号左侧点击),用 dbstop if error 让程序在出错处暂停;必要时用 disp 或 fprintf 打印关键量。

针对本节任务,几个典型现象值得记住:频谱峰值不在 1000 Hz,多半是频率轴 f 与 FFT 结果没有对齐;幅度远小于 1,通常是忘记加窗归一化;时域波形“看起来在变”,可能是时间轴步长写成了 1/fs 之外的数;出现 Undefined function,检查函数文件名与函数名是否一致、当前文件夹是否在搜索路径中。养成“每改一处、重跑一次、看图确认”的习惯,比一次性写完再排查高效得多。

五、把任务做成可复现的小实验

建议你按下面的方式完成一次完整练习:先固定 fs = 8000 Hz、T = 0.5 s、f0 = 1000 Hz,运行脚本,确认时域图有 500 个周期、频域峰值在 1000 Hz 且幅度约为 1;然后把 f0 改为 1500 Hz,观察峰值移动;再把 T 改为 0.25 s,观察 Δf 变为 4 Hz、谱线变粗;最后把 x .* w 改成 x * w,看 MATLAB 是否报维度错误,从而加深对逐元素运算的理解。每次只改一个参数,记录现象与原因,这就是最朴素的仿真验证思维。

写脚本时把参数集中放在文件开头,并用注释标明单位(Hz、s、V)。这样换场景时只改几行,既减少错误,也方便把脚本交给同学复现。
FFT 结果默认是复数且包含负频率成分,直接 plot(abs(fft(x))) 会得到左右对称的双边谱。做单边谱时必须截取前 N/2+1 点并重建频率轴,否则横轴与峰值位置会对不上。

本节的任务虽小,却覆盖了后续水声仿真最常用的动作:用向量构造时间与频率轴、用逐元素运算生成和加窗信号、用 FFT 做谱分析、用 subplot 对照时频结果、用断点和中间量检查定位问题。把这套流程练熟,后面处理调制信号、信道响应和接收阵列数据时,你只需要替换信号模型和分析对象,工具链本身不再成为障碍。