简介:面向通信与信号处理学习者的MATLAB仿真资源包,聚焦双极性信号、正交信号与PAM信号三类调制技术的建模与仿真,适用于课程设计、期末复习及通信系统入门实践。压缩包共12个文件,包括8个m脚本、3个Simulink模型及1份详细建模说明文档,大小仅165KB。脚本覆盖信号生成、调制、加噪与解调等核心环节,Simulink模型便于可视化验证,说明文档则梳理运行方法与结果分析。已有369人学习下载。资源以循序渐进的方式展开典型实验:从双极性波形产生、正交多载波调制到多进制PAM解调,各m文件可独立运行也可组合成完整仿真链路;模型文件帮助直接观察频谱与波形变化,配合文档可快速修改参数并扩展到其他调制技术,整体是理论联系实践的高性价比工具。
1. 从“三个电平”开始的MATLAB通信仿真
很多人第一次接触双极性信号、正交信号和PAM信号时,会把它们当成三个无关的调制知识点:一个用来画波形,一个用在OFDM里,一个拿pammod一行命令就能带过。但在一个完整的MATLAB通信系统仿真包里,三者其实是同一条基带链路的三个环节:双极性信号决定码元如何映射为电平,正交信号决定载波如何承载信息,PAM则把整个过程的幅度调制部分统一起来。这个压缩包里的exe1.m到exe8.m、exe4.mdl、exe5.mdl和exe7.mdl,以及那份“建模及仿真说明.doc”,恰好覆盖了从波形生成、噪声信道到Simulink模型联调的完整路径。适合正在做通信课程设计、需要交付误码率曲线的学生,也适合想快速回顾基带仿真细节的工程师。下面直接按信号类型拆开讲。
2. 双极性信号建模:NRZ波形与抗噪能力分析
双极性信号是整个仿真的第一块基石。它把二进制数据映射成正负两个电平,典型实现是双极性不归零码。相比单极性信号只在“0”和“1”之间切换,双极性信号没有直流分量,因此在通过变压器耦合的基带系统中更友好,也能够以电平极性直接参与后续的匹配滤波和判决。
2.1 双极性信号的数学定义与带宽效率
双极性信号可以写为:
s(t) = ∑ a_k g(t − kT_b), a_k ∈ {−A, +A}
其中T_b是码元周期,g(t)是成形脉冲,通常取矩形脉冲。这里最关键的参数是A的取值和T_b的确定。MATLAB里仿真时,一般先把数据序列变成−1和+1,再用repelem或upsample把每个码元展开成多个采样点,形成连续波形。
从频域看,双极性NRZ信号的功率谱主瓣宽度约为码元速率R_b。相比单极性NRZ,双极性信号没有直流分量,功率谱在零频处不会出现离散谱线,这对后续加入载波调制非常有利。在MATLAB里可以用periodogram或pwelch直接观察这两者差异。若你看到exe1.m的绘图部分有fft和abs,多半就是在做这件事。带宽效率的差异不在码元速率上,而在低频成分上,这也是双极性信号在基带传输中更常见的原因。
2.2 用MATLAB生成双极性NRZ序列
下面这段代码是一个可靠起点,我一般把每个码元展开成sps个采样点,方便后面加任意信噪比的噪声。
% bipolar_nrz_demo.m numBits = 2000; % 测试比特数 sps = 64; % 每个码元采样的点数 data = randi([0 1], numBits, 1); bipolar = 2*data - 1; % 0 -> -1, 1 -> +1 waveform = repelem(bipolar, sps); % 每个码元重复sps次 fs = 64000; % 采样率,Hz Tb = sps / fs; % 码元周期,即每个码元的时长 % 查看前8个码元 stem(bipolar(1:8), 'filled'); xlabel('码元序号'); ylabel('电平'); title('前8个双极性码元');代码里randi([0 1], numBits, 1)生成0/1随机序列,2*data - 1完成电平映射。repelem是按码元重复,比upsample更直观,因为upsample会在采样点之间插入零,必须再做低通或保持,而repelem直接生成零阶保持波形。sps=64是为了让波形在时域显示时足够平滑,同时做FFT时频率分辨率也够。实际中sps取8到128都可以,太小时波形包络像阶梯,太大会增加仿真内存,但不会改变误码率结论。
2.3 双极性信号在噪声信道中的判决
双极性信号在AWGN信道里,最优判决方式就是看采样点的符号。关键操作是要在码元中心采样,而不是随便抽一个点。仿真的代码结构通常是:
% bipolar_awgn_demo.m EbN0dB = 8; EbN0 = 10^(EbN0dB/10); sigma = sqrt(1 / (2*EbN0)); % 双极性信号单位能量时的噪声标准差 rx = waveform + sigma * randn(size(waveform)); rx_sampled = rx(1 : sps : end); % 每个码元取第一个采样点作为判决值 if 1 % 使用码元中点采样,更贴近实际采样时钟 rx_mid = reshape(rx, sps, numBits); rx_sampled = rx_mid(end/2, :); end decision = sign(rx_sampled); % >0判为+1,<0判为-1 err = sum(decision ~= bipolar'); ser = err / numBits; fprintf('Eb/N0 = %.1f dB, SER = %.4f\n', EbN0dB, ser);sigma = sqrt(1/(2*EbN0))的推导前提是双极性信号每个比特能量Eb=1,所以噪声方差按N0/2折算。如果直接用randn(size(waveform)),生成的噪声功率是1,要乘sigma才能得到目标信噪比。rx_sampled的核心是确定采样相位,使用码元中点还是起点,取决于仿真里是否有发送滤波和接收滤波器。矩形脉冲波形时,码元中点离码元边界最远,抗噪声最好,所以reshape后取end/2比取第一个点更合理。sign把接收值硬判决到±1,不做软信息。
对双极性信号在AWGN下的理论误码率,通常用Pe=Q(√(2Eb/N0))。如果仿真结果与理论曲线差异大于0.5dB,往往就是采样相位不对,或者噪声功率公式里忘了除以sps。下表给出了常见参数和含义。
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| numBits | 1e3~1e6 | 比特数越多,误码率统计越稳,但仿真时间长 |
| sps | 8~128 | 每个码元采样点数,决定时域分辨率和内存 |
| EbN0dB | 0~15 | 双极性信号通常在这个区间从10^-2降到10^-6 |
| sigma | 按EbN0计算 | 不要手动指定固定噪声幅度 |
项目里exe2.m、exe3.m基本就是这类流程的变体:exe2.m生成双极性波形,exe3.m做噪声叠加和误码率统计。如果你拿到的exe1.m是主程序,它多半在循环里调用这两个脚本,并把EbN0从0扫到10dB,最后画出一张SER曲线图。这里要提醒的是,循环里每次都要重新生成随机数,否则多次调用的噪声序列完全一样,得出的误码率会沿一条固定曲线跳动,而不是在理论值附近波动。
3. 正交信号的构造:从正交性判据到I/Q调制仿真
正交信号在通信系统里扮演的角色和双极性信号完全不同。双极性信号解决的是“符号怎么映射”,正交信号解决的是“多路信号怎么同时传输而不互相干扰”。在MATLAB仿真中,最常见做法是先验证两路信号的内积为零,然后把零中频的I/Q两路信号送到Simulink载波上。
3.1 正交与正交频分复用的联系
两个信号正交,数学定义是它们在某个区间上的内积为零,也就是∫s1(t)·s2(t)dt=0。对频域而言,这意味着两路信号的频谱没有重叠的非零成分。OFDM之所以能利用子载波间的正交性,正是因为在码元周期内,各子载波频率间隔为1/T时,不同子载波的乘积积分恰好为零。但在一个数字仿真的基带系统里,做正交调制未必需要真的搭OFDM,只需要构造一对正弦和余弦载波,分别乘以I路和Q路数据,再把两路相加。
正交信号的好处是频谱利用率翻倍。同样的带宽内,IQ两路可以各传各的数据,接收端用本地载波做相干解调就能分开。这就是为什么大多数现代调制系统(QAM、PSK)在仿真时都要先生成双极性或PAM基带波形,再搬到正交载波上。
3.2 用fft/ifft构造正交载波并验证正交性
MATLAB里构造正交信号,最直接的方式是生成一个exp(j*2*pi*f*t)复指数,因为它的实部和虚部天然正交。但如果你要做的是实信号正交验证,下面的代码更直观:
% orthog_check.m fs = 10000; t = (0:999) / fs; f1 = 100; f2 = 150; x1 = cos(2*pi*f1*t); x2 = sin(2*pi*f1*t); % 与x1相差90度 corr12 = dot(x1, x2); % 数字内积 corr11 = dot(x1, x1); % 参考自相关值 % 用FFT观察两根谱线是否分离 X1 = fft(x1); X2 = fft(x2); f = (0:999) * fs / 1000 - fs/2; stem(f, fftshift(abs(X1) .* abs(X2)), 'filled'); xlabel('频率 (Hz)'); ylabel('|X1(k)|*|X2(k)|'); title('两个正交载波的谱乘积');dot(x1,x2)在整周期采样时接近0,非整周期时会有泄漏,所以验证正交性时最好让采样点数对应整数个载波周期。上面的fs=10000,t=0:999,f1=100Hz,正好10个周期,内积结果在浮点误差范围内。谱乘积的stem图在所有频率点上都接近0,就代表谱域正交。实际OFDM仿真里,ifft的作用是把频域符号映射到时域,而fft则用于接收端解调。不要混淆“用fft看频谱”和“用ifft生成时域OFDM符号”,前者是分析工具,后者是把子载波叠加成正交信号的生成方式。
如果要生成一个带I/Q的正交调制信号,常见做法是:
% iq_modulator.m fcarrier = 1000; Ts = 1 / fsamp; t = 0 : Ts : 100*Tb - Ts; % 100个码元时长 I = cos(2*pi*fcarrier*t); Q = sin(2*pi*fcarrier*t); s = data_I .* I + data_Q .* Q;这里data_I和data_Q是已经展开成波形的基带信号。如果省略载波而直接用复数基带信号表示,则为s_complex = data_I + 1j*data_Q,这是Simulink中AWGN信道模块和星座图仪最常用的输入格式。
3.3 Simulink中实现正交调制:exe4.mdl的结构猜测与参数
压缩包里的exe4.mdl、exe5.mdl、exe7.mdl都是Simulink模型。虽然只有.mdl文件看不到内部截图,但从命名看,exe4.mdl大概率是正交调制加噪声信道,exe5.mdl可能是PAM或基带成型滤波,exe7.mdl可能是完整解调链路。打开.mdl文件的方法是直接在MATLAB命令窗口输入open_system('exe4.mdl')。若提示模块库版本过低,在模型窗口按Ctrl+D更新诊断即可。
Simulink里搭正交调制器时,最常用的模块包括Sine Wave(设置Sample time和Bias实现正弦/余弦)、Product、Add、AWGN Channel。AWGN Channel的SNR参数有两种设定模式:Symbol period和Signal to noise ratio。如果设成Symbol period,需要把符号周期填成Tb,否则噪声功率会按每个采样点计算,最终误码率偏大。另一处容易错的是载波频率的选择:Simulink是离散时间系统,载波频率必须满足采样定理,同时要避免载波频率与采样率的比值接近0.5,否则会出现频谱混叠。我一般把载波设在采样率的1/20到1/4之间。
正交性的验证在Simulink里同样能做。在接收端用Product和Integrator(或Discrete Time Integrator)计算I路与本地正弦载波的乘积积分,再与Q路乘积积分对比。若I路对Q路本地载波的积分远小于对I路本地载波的积分,说明两条支路分离成功。下面是一个常见的参数配置表。
| 参数项 | 建议值 | 说明 |
|---|---|---|
| Carrier frequency (fc) | fsamp/10 | 太接近fsamp/2会混叠 |
| 接收滤波阶数 | 32~64 | 成形滤波器和匹配滤波器各一 |
| AWGN Channel的SNR mode | Symbol period | 按码元速率计算噪声功率 |
| Solver type | Fixed-step | 离散模型避免变步长带来的采样点抖动 |
| Stop time | 10/Tb或更长 | 至少包含10个以上码元,便于观察眼图 |
这个模型的输出通常是解调后的基带数据流,需要经过判决和误码统计。如果直接用Scope看波形,看起来像一团正弦波,可以用Reshape和Selector把每个码元末端的采样点取出来,再与发射端的码元对比。
4. PAM通信系统仿真:从pammod到误码率曲线
PAM信号经常被误写成PWM信号,其实两者本质不同:PAM是脉冲的幅度携带信息,PWM是脉冲的宽度携带信息。这个压缩包的关键词虽然出现了PWM,但摘要和文件名都指向PAM调制。在MATLAB里实现PAM非常简单,但要把误码率曲线做准确,仍需要理解电平映射和能量归一化。
4.1 PAM调制的电平映射与星座图
M进制PAM将log2(M)个比特映射成一个电平,电平集合是等间隔的,通常是{±1, ±3, ..., ±(M-1)},也可以从0开始。二进制PAM就是双极性信号,所以第2章里的双极性NRZ其实就是2-PAM。M越大,每个符号携带的比特越多,但相邻电平的距离变小,抗噪声能力下降。
MATLAB的pammod函数生成的符号默认是0到M-1的实数映射,并不严格落在{±1, ±3},它默认的是1:M映射到-M+1:2:M-1,具体要看版本。因此,使用pammod后最好做一次能量归一化,让每个符号平均能量为1,否则后续加噪声时EbN0的设置会偏。下面是标准的两行映射:
M = 4; x = randi([0 M-1], 1000, 1); sym = pammod(x, M); % 默认电平仍在±1,±3附近 sym = sym / sqrt(mean(abs(sym).^2)); % 归一化到平均能量1sqrt(mean(abs(sym).^2))是计算平均幅度,除完后平均能量变成1。这样加噪时每个符号能量E_s=1,再折算E_b=N0每比特时,只需除以log2(M)即可。对于pamdemod,它的判决边界在相邻电平的中点,所以只要接收值处于两个电平之间,就会判成对应的整数符号。
4.2 用pammod/pamdemod搭一条完整链路
完整链路包括符号生成、调制、加噪、解调、误码率统计。最可靠的方式是写成一个循环,扫描多个SNR点。
% pam_ber_curve.m M = 4; k = log2(M); numSym = 100000; data = randi([0 M-1], numSym, 1); EbN0dB = 0:2:12; BER = zeros(size(EbN0dB)); for idx = 1:length(EbN0dB) EbN0 = 10^(EbN0dB(idx)/10); N0 = 1 / k / EbN0; % 归一化符号能量下,噪声单边谱密度 noise = sqrt(N0/2) * randn(numSym, 1); sym = pammod(data, M); sym = sym / sqrt(mean(abs(sym).^2)); rx = sym + noise; demod = pamdemod(rx, M); BER(idx) = sum(demod ~= data) / numSym; end semilogy(EbN0dB, BER, 'o-'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER'); title(sprintf('%d-PAM AWGN信道误码率', M));逻辑说明:N0=1/k/EbN0是因为符号能量归一化为1,每个符号携带k比特,所以每比特能量Eb=1/k,噪声方差N0/2。sqrt(N0/2)是实高斯噪声的标准差。对PAM信号,方差公式是N0/2而不是N0,因为信号是实的,噪声功率谱密度在[-N0/2,N0/2]上积分。pamdemod会自动计算判决边界,但若使用了非对称电平映射,需要在解调前做相同的归一化。BER(idx)在numSym较大时能稳定到三位小数,想要更平滑可以改为Monte Carlo方式,在误码数小于20时继续增加符号数。
这里有一个常见的坑:randn每次循环的噪声序列是独立生成的,所以BER曲线不平滑,但这也是真实蒙特卡洛结果的体现。如果希望曲线平滑,就在外层循环前设置rng(42),但这样每个SNR点用的数据相同,会带来相关性。更专业的做法是固定数据序列,只重新生成噪声,并在每个SNR点统计足够多的错误比特。
4.3 多进制PAM的代价:M增大与信噪比需求
从2-PAM到4-PAM,一个码元能传2比特,带宽利用率翻倍。但星座点之间的欧氏距离减少到原来的约1/√3,所以达到相同误码率需要更高的Eb/N0。理论误码率可以近似为:
Pb ≈ ((M−1)/(M·log2M))·erfc(√(3·log2M·EbN0/(M²−1)))
这个公式在M=2时退化为Q(√(2EbN0))的等价形式。仿真中如果M超过8,曲线会迅速右移,0dB时的误码率可能已经很高,需要把SNR范围扩大到20dB。
下面表格展示了不同M值下,要达到10⁻⁴的BER大致需要的Eb/N0(在AWGN理想相干解调下):
| M | 所需Eb/N0 (dB) | 每个符号比特数 | 带宽效率 |
|---|---|---|---|
| 2 | 8.4 | 1 | 1 bit/s/Hz |
| 4 | 11.7 | 2 | 2 bit/s/Hz |
| 8 | 14.9 | 3 | 3 bit/s/Hz |
| 16 | 18.3 | 4 | 4 bit/s/Hz |
这些数值来自理论公式,不完全等同于实际pammod默认映射的结果,但足够用来判断仿真曲线是否合理。如果仿真得到的曲线比表中数值差了1dB以上,通常是能量归一化或噪声方差计算出了偏差。压缩包里的exe5.m、exe5sim.m和exe6.m很可能就是围绕这一组曲线做的:exe5.mdl负责图形化模型,exe5sim.m负责调用sim指令跑批处理,exe6.m则可能把不同M的结果画在同一张图上。
多进制PAM另一种常见实现是不用pammod,而是自己定义电平映射,比如用qammod把复数信号改为实数。此时需要特别注意编码顺序,比如Gray编码和自然二进制编码在相临判决区域错误时产生不同的比特错误数。没有Gray编码的PAM在高SNR下误码率会比理论值略微偏高,因为误命中的符号可能影响到不止1位比特。
5. 把仿真结果跑稳:模块参数、随机数种子和仿真发散的处理技巧
5.1 用随机数种子和批量脚本控制重复性
MATLAB仿真的一个隐藏陷阱是随机数流不可控。同一个exe5sim.m,两次运行结果可能有微小差异,这在调试时非常讨厌。我会在每个脚本开头加一行rng('shuffle')用于正式实验,但调试阶段改成rng(2024)。批量做误码率扫描时,把rng放在主脚本里,子函数只接收数据不修改全局随机流,否则每次调用都不一致。Simulink里同样可以通过set_param('mdl','RandomNumberGlobalSeed','2024')控制随机数生成模块的种子。
5.2 仿真发散先查这三处
Simulink模型仿真发散是.mdl模型最常见的故障,现象是Scope里波形跑到±10³⁰,或者求解器报错“Failed to converge”。我一般按顺序查这三处:第一,代数环。如果模型中存在输入直接经过Gain反馈回自身的环路,步长再小也无法收敛,需要在反馈路径插入Unit Delay,或用Memory模块断开代数环。第二,Solver步长。使用变步长求解器时,由于通信信号是矩形波,可能在跳变处强行减小步长导致发散,把Solver改成Fixed-step,并让步长等于采样周期的整数分之一,比如1/(fsamp)。第三,增益是否过大。AWGN信道模块的SNR若小于0dB,噪声方差远大于信号幅度,Scope纵坐标范围不够会看起来像发散,实际只是信号被噪声淹没。
下面是一个可用的模块检查列表:
| 现象 | 检查对象 | 处理 |
|---|---|---|
| 波形瞬间到1e10 | 代数环 | 插入Unit Delay断开环路 |
| 仿真时间走不动 | 变步长求解器 | 改Fixed-step,步长取1/fs |
| 误码率接近0.5 | 随机数种子重复 | 更换rng种子或shuffle |
| 误码率比理论低 | 采样点位置 | 改为码元中点而非起点 |
这些检查项基本涵盖了从.mdl模型到脚本的所有发散来源。处理完发散后,用同一份随机数跑一次完整链路,记录最终误码率数值,再改成另一份种子跑一次,两者差异如果超过10%,说明仿真长度不足,需要增大numBits或使用误差累计停止条件,而不是急着调最终结果。
最后再补充一个实用技巧:exe7.mdl和exe7sim.m如果用于完整系统验证,可以在模型里加一个Display模块直接观察解调符号,并用To Workspace把接收符号导出到MATLAB变量,这样就不用在Simulink里手动数点了。设置To Workspace的Save format为Timeseries,然后在命令行用rx_sym = out.y.signals.values提取,与发射符号对比即可。这个做法能省去大量截图和人工核对,适合课程设计最后验收。
本文还有配套的精品资源,点击获取