MATLAB仿真解析四种数字基带码型:DNRZ、DRZ、SNRZ、SRZ的时域与频域特性
1. 项目缘起:为什么从这四种基本码型开始?
在数字通信系统里,我们常把一串串的“0”和“1”叫做数字基带信号。但计算机里的0和1是逻辑电平,没法直接在电缆或空气中传输。所以,第一步就是把它们变成实实在在的、看得见摸得着的电信号波形,这个过程就是“码型编码”。你可能会觉得,这不就是画个方波吗?高电平代表1,低电平代表0。但实际工程中,远没这么简单。信号的能量分布、有没有直流分量、是否方便接收端提取时钟、抗干扰能力如何,这些全都和选择的码型息息相关。
而双极性不归零码(DNRZ)、双极性归零码(DRZ)、单极性不归零码(SNRZ)和单极性归零码(SRZ),就是构成这个庞大码型世界的四块基石。几乎所有复杂的、工程中实用的码型,比如曼彻斯特码、AMI码,其设计思想都能从这四种基本码型里找到影子。搞懂它们,不仅是应付考试,更是为了在以后设计或调试通信链路时,当示波器上出现一个奇怪的波形,或者频谱仪上显示能量异常,你能立刻反应过来:“哦,这可能是码型选择不当引起的。”
这次,我们不空谈理论,直接上手MATLAB,把这四种码型的时域波形和它们的“身份证”——功率谱密度给画出来。通过仿真,你能直观地看到“归零”与“不归零”、“单极性”与“双极性”究竟带来了哪些本质区别。相信我,当你亲手运行代码,看着波形和频谱图在屏幕上跳出来时,那种“原来如此”的感觉,比看十遍公式推导都要深刻。
2. 仿真前的核心概念拆解:别再混淆这些术语
动手之前,我们必须把四个码型的定义和关键参数掰扯清楚,这是后续一切仿真的基础。很多初学者容易在这里犯迷糊。
2.1 “单极性”与“双极性”:电平的参照系不同
这是第一个分水岭,核心在于信号幅度是否关于零电平对称。
- 单极性码(Unipolar):用一种极性的电平来表示二进制信息。通常,我们用正电平(比如+1V或+A伏)表示二进制“1”,用零电平(0V)表示二进制“0”。它的全部信号幅度都在零轴的一侧(通常是正侧)。你可以把它想象成“单腿跳”,始终在零线以上活动。
- 双极性码(Bipolar):用两种相反极性的电平来表示二进制信息。通常,用正电平(+A伏)表示“1”,用负电平(-A伏)表示“0”。它的信号幅度关于零电平对称。这就像是“正常走路”,一步在前(正),一步在后(负)。
关键点:双极性码的“0”不是没信号,而是一个负脉冲!这是和单极性码最直观的区别。双极性码的直流分量通常很小甚至为零,这对于通过变压器耦合或电容耦合的信道非常有利。
2.2 “归零”与“不归零”:脉冲的“休息”哲学
这是第二个分水岭,决定了在一个码元周期内,电平是否“坚守岗位”。
- 不归零码(Non-Return-to-Zero, NRZ):在一个码元周期
Tb(比特间隔)内,电平保持恒定不变。如果当前是“1”,那么在整个Tb时间内都保持高电平;如果是“0”,则在整个Tb时间内保持低电平(单极性)或负电平(双极性)。它很“敬业”,从不中途开小差。 - 归零码(Return-to-Zero, RZ):在一个码元周期
Tb内,电平只在一部分时间(通常是一半时间,即 τ = Tb/2)内保持有效状态,然后在周期结束前提前返回到零电平。比如,对于“1”,它可能在前半个周期是高电平,后半个周期就回到0V;对于“0”,则可能是前半个周期是低(或负)电平,后半个周期回到0V。它懂得“劳逸结合”,每个周期都回零休息一下。
关键点:“归零”归的是“零电平”,而不是归到负电平。对于双极性归零码,表示“0”的负脉冲也会在周期中途归零。归零码因为每个比特都有电平跳变(回到零),更有利于接收端从信号中提取定时时钟,但代价是带宽更宽。
把这四个概念两两组合,就得到了我们的四位主角:
- 双极性不归零码(DNRZ):1=+A, 0=-A, 电平持续整个
Tb。 - 双极性归零码(DRZ):1=+A(持续 Tb/2), 0=-A(持续 Tb/2), 每个比特后半段都归零。
- 单极性不归零码(SNRZ):1=+A, 0=0, 电平持续整个
Tb。 - 单极性归零码(SRZ):1=+A(持续 Tb/2), 0=0(持续整个 Tb 或也归零?这里有个细节!)。
注意:对于单极性归零码,常见的定义是:只有“1”码元产生一个归零脉冲,“0”码元在整个周期内保持为零电平。即“0”是没有脉冲的。这与双极性归零码中“0”也对应一个负的归零脉冲不同。这是我们仿真时需要特别注意的,也是它频谱特性不同的根源之一。
3. 仿真环境搭建与核心参数设定
我们使用MATLAB进行仿真,因为它处理信号和绘图非常方便。这里假设你已有基本的MATLAB操作知识。
3.1 基础参数定义
在脚本开头,我们先定义一组全局参数,确保四种码型在同等条件下比较。
clear all; close all; clc; % ========== 基础参数设置 ========== Rb = 1000; % 比特率,单位:bps (bits per second) Tb = 1 / Rb; % 比特间隔,单位:秒 (s) fs = 100 * Rb; % 采样频率,为了准确绘制波形,采样率要远高于比特率,这里取100倍 dt = 1 / fs; % 采样间隔 A = 1; % 信号幅度,设为1方便归一化分析 % ========== 生成随机的二进制信息序列 ========== % 为了对比,我们使用同一组随机序列 N_bits = 50; % 生成50个比特进行演示 bit_sequence = randi([0, 1], 1, N_bits); % 生成50个随机的0和1 % ========== 时间轴生成 ========== % 总时间 = 比特数 * 比特间隔 t_total = N_bits * Tb; % 时间向量,从0开始,以dt为步进,直到接近总时间(避免浮点误差) t = 0:dt:(t_total - dt/2); % t_total - dt/2 是为了确保时间点个数正好是 N_bits * Tb / dt参数选择背后的逻辑:
Rb=1000bps:这是一个典型的低速数据率,便于我们观察波形细节。速率太高,一个图里显示不了几个周期。fs=100*Rb:根据奈奎斯特采样定理,采样频率至少是信号最高频率的2倍。对于方波这类富含高频分量的信号,为了在时域上画出光滑的方波边沿,需要很高的采样率。100倍是一个经验值,能很好地平衡精度和计算量。N_bits=50:比特数太少,统计特性不明显(比如功率谱);太多,时域波形图会过于密集看不清。50个比特是一个折中的选择,既能看清局部波形,又能让功率谱有一定平滑度。- 使用同一组
bit_sequence:这是对比的关键!只有源数据一致,我们才能说波形和频谱的差异纯粹是由码型变换引起的,而不是随机序列不同导致的。
3.2 构建码型生成函数
为了代码清晰和复用,我们为每种码型编写一个独立的生成函数。函数输入是比特序列和时间轴,输出是生成的信号波形。
function signal = generate_DNRZ(bits, t, Tb, A) % 生成双极性不归零码 % bits: 二进制序列 (行向量) % t: 时间向量 % Tb: 比特间隔 % A: 幅度 signal = zeros(size(t)); for i = 1:length(bits) bit = bits(i); % 确定当前比特的时间范围 t_start = (i-1) * Tb; t_end = i * Tb; % 找到在这个时间范围内的所有时间点索引 indices = (t >= t_start) & (t < t_end); if bit == 1 signal(indices) = A; % 比特为1,赋值 +A else signal(indices) = -A; % 比特为0,赋值 -A end end end function signal = generate_DRZ(bits, t, Tb, A) % 生成双极性归零码 (占空比50%) % 归零脉冲宽度 tau = Tb / 2 tau = Tb / 2; signal = zeros(size(t)); for i = 1:length(bits) bit = bits(i); t_start = (i-1) * Tb; t_pulse_end = t_start + tau; % 脉冲结束时间 % 脉冲期间的时间索引 indices = (t >= t_start) & (t < t_pulse_end); if bit == 1 signal(indices) = A; else signal(indices) = -A; end % 归零期间 (t_pulse_end 到 t_start+Tb) 信号默认为0,已由zeros初始化 end end function signal = generate_SNRZ(bits, t, Tb, A) % 生成单极性不归零码 signal = zeros(size(t)); for i = 1:length(bits) bit = bits(i); t_start = (i-1) * Tb; t_end = i * Tb; indices = (t >= t_start) & (t < t_end); if bit == 1 signal(indices) = A; % 比特为1,赋值 +A else signal(indices) = 0; % 比特为0,赋值 0 end end end function signal = generate_SRZ(bits, t, Tb, A) % 生成单极性归零码 (占空比50%) % 注意:通常定义中,只有'1'码元产生归零脉冲,'0'码元持续为0。 tau = Tb / 2; signal = zeros(size(t)); for i = 1:length(bits) bit = bits(i); if bit == 1 t_start = (i-1) * Tb; t_pulse_end = t_start + tau; indices = (t >= t_start) & (t < t_pulse_end); signal(indices) = A; % 仅当比特为1时,在前半段产生脉冲 end % 比特为0时,全程为0,无需操作 end end实操心得:在编写这类波形生成函数时,最核心也最容易出错的就是时间索引的计算。务必注意MATLAB中向量索引从1开始,而时间从0开始。使用
(t >= t_start) & (t < t_end)这种逻辑索引方式非常稳健,能精确匹配时间段。避免使用基于采样点数的计算(如(i-1)*sps+1 : i*sps),因为当Tb/fs不是整数时会产生误差。逻辑索引法能自动处理时间对齐问题。
4. 时域波形生成与对比分析
现在,我们调用函数生成波形,并将它们绘制在一起进行对比。
% ========== 生成四种码型的波形 ========== dnrz_signal = generate_DNRZ(bit_sequence, t, Tb, A); drz_signal = generate_DRZ(bit_sequence, t, Tb, A); snrz_signal = generate_SNRZ(bit_sequence, t, Tb, A); srz_signal = generate_SRZ(bit_sequence, t, Tb, A); % ========== 绘制时域波形对比图 (前10个比特) ========== figure('Position', [100, 100, 1200, 800]); num_bits_to_show = 10; % 只显示前10个比特,便于观察细节 t_show_limit = num_bits_to_show * Tb; show_indices = t < t_show_limit; % 子图1: 双极性不归零码 (DNRZ) subplot(4,1,1); plot(t(show_indices)*1000, dnrz_signal(show_indices), 'b-', 'LineWidth', 1.5); grid on; ylim([-1.5*A, 1.5*A]); title('双极性不归零码 (DNRZ) 时域波形'); ylabel('幅度'); xlabel('时间 (ms)'); % 添加比特序列标注(可选,使图形更清晰) hold on; for i = 1:min(num_bits_to_show, length(bit_sequence)) text((i-0.5)*Tb*1000, 1.2*A, num2str(bit_sequence(i)), ... 'HorizontalAlignment', 'center', 'FontWeight', 'bold'); end hold off; % 子图2: 双极性归零码 (DRZ) subplot(4,1,2); plot(t(show_indices)*1000, drz_signal(show_indices), 'r-', 'LineWidth', 1.5); grid on; ylim([-1.5*A, 1.5*A]); title('双极性归零码 (DRZ) 时域波形 (占空比50%)'); ylabel('幅度'); xlabel('时间 (ms)'); hold on; for i = 1:min(num_bits_to_show, length(bit_sequence)) text((i-0.5)*Tb*1000, 1.2*A, num2str(bit_sequence(i)), ... 'HorizontalAlignment', 'center', 'FontWeight', 'bold'); end hold off; % 子图3: 单极性不归零码 (SNRZ) subplot(4,1,3); plot(t(show_indices)*1000, snrz_signal(show_indices), 'g-', 'LineWidth', 1.5); grid on; ylim([-0.2*A, 1.5*A]); % y轴从-0.2开始,以便看清零线 title('单极性不归零码 (SNRZ) 时域波形'); ylabel('幅度'); xlabel('时间 (ms)'); hold on; for i = 1:min(num_bits_to_show, length(bit_sequence)) text((i-0.5)*Tb*1000, 1.2*A, num2str(bit_sequence(i)), ... 'HorizontalAlignment', 'center', 'FontWeight', 'bold'); end hold off; % 子图4: 单极性归零码 (SRZ) subplot(4,1,4); plot(t(show_indices)*1000, srz_signal(show_indices), 'm-', 'LineWidth', 1.5); grid on; ylim([-0.2*A, 1.5*A]); title('单极性归零码 (SRZ) 时域波形 (占空比50%)'); ylabel('幅度'); xlabel('时间 (ms)'); hold on; for i = 1:min(num_bits_to_show, length(bit_sequence)) text((i-0.5)*Tb*1000, 1.2*A, num2str(bit_sequence(i)), ... 'HorizontalAlignment', 'center', 'FontWeight', 'bold'); end hold off;运行这段代码,你会得到一张包含四个子图的时域波形对比图。现在,我们来逐一点评:
DNRZ (图1):波形最“干净利落”。每个比特周期内,电平要么是+1,要么是-1,并且持续整整1ms(因为Rb=1000bps,Tb=1ms)。当出现连续的“1”或“0”时,你会看到一条长时间不变的水平线。问题来了:如果传输一长串“1”,接收端怎么知道每个比特的边界在哪里?这就需要额外的时钟同步信号,或者采用自带时钟信息的码型(比如我们下面的归零码)。
DRZ (图2):与DNRZ使用相同的比特序列。最明显的特征是,每个比特周期中间,电平都会“归零”。对于“1”,是一个持续0.5ms的正脉冲;对于“0”,是一个持续0.5ms的负脉冲。注意看,即便是连续的“1”或“0”,每个比特周期内也都有一次从有效电平到零的跳变。这个跳变就是接收端用来恢复时钟的宝贵信息!代价是,为了传输同样的比特信息,DRZ需要更快的电平变化,这意味着它需要更宽的带宽。
SNRZ (图3):波形全部在零轴以上。“1”是+1,“0”就是0V。同样,在连续“1”或连续“0”时,没有电平变化。一个致命缺点暴露了:由于“0”和“1”的平均电平不同(“1”多则平均电平高),信号中存在显著的直流分量。直流分量无法通过变压器或电容耦合的信道,会造成能量浪费和基线漂移。
SRZ (图4):这是最“偷懒”的码型。只有“1”的时候,才在前半段产生一个正脉冲;“0”的时候,整个周期都是安静的0V。你可以看到,当出现连续“0”时,波形就是一条长长的零线。它的优点和DRZ类似,每个“1”比特都自带时钟跳变信息(但“0”没有)。缺点也很明显:直流分量更大(因为只有正脉冲),且当连续“0”过多时,接收端会失去同步参考。
踩坑记录:在绘制时域图时,最初我尝试用
stem函数画离散序列图,但发现对于展示码型这种持续一段时间的电平信号,plot连线图更能体现“电平保持”的概念。另外,时间轴乘以1000转换为毫秒,是因为1ms对于人类视觉感知来说是一个更舒服的单位。这些小细节决定了你的仿真结果是否直观、专业。
5. 功率谱密度计算:窥探信号的频率“指纹”
时域波形告诉我们信号随时间怎么变,而功率谱密度(PSD)则告诉我们信号的能量在不同频率上是如何分布的。这对于评估信号需要多大的信道带宽、是否会产生干扰至关重要。
5.1 为什么用周期图法?如何计算?
对于有限长的随机信号,我们通常用周期图法来估计其功率谱。MATLAB中的pwelch函数是实现这一方法的利器。它通过将长信号分段、加窗、求各段周期图平均,来得到相对平滑、稳定的PSD估计。
% ========== 计算功率谱密度 (PSD) ========== % 使用pwelch函数,采用Welch平均周期图法 % 设置窗函数和重叠点数,以获得平滑的谱估计 window = hamming(1024); % 使用汉明窗,减少频谱泄漏 noverlap = 512; % 重叠点数,一般为窗长的一半 nfft = 4096; % FFT点数,决定频率分辨率,点数越多,分辨率越高,曲线越光滑 [PSD_dnrz, f_dnrz] = pwelch(dnrz_signal, window, noverlap, nfft, fs, 'power'); [PSD_drz, f_drz] = pwelch(drz_signal, window, noverlap, nfft, fs, 'power'); [PSD_snrz, f_snrz] = pwelch(snrz_signal, window, noverlap, nfft, fs, 'power'); [PSD_srz, f_srz] = pwelch(srz_signal, window, noverlap, nfft, fs, 'power'); % 将频率转换为kHz,功率转换为dBm/Hz (以1mW为基准) 或 dBW/Hz (以1W为基准) % 这里我们计算相对功率谱密度,关注形状而非绝对值,所以常用dB表示 PSD_dnrz_dB = 10*log10(PSD_dnrz); PSD_drz_dB = 10*log10(PSD_drz); PSD_snrz_dB = 10*log10(PSD_snrz); PSD_srz_dB = 10*log10(PSD_srz); f_kHz_dnrz = f_dnrz / 1000; % 转换为kHz % ========== 绘制功率谱密度对比图 ========== figure('Position', [100, 100, 1200, 800]); % 子图1: 线性坐标对比 (观察主瓣宽度和零点) subplot(2,1,1); plot(f_kHz_dnrz, PSD_dnrz, 'b-', 'LineWidth', 1.5); hold on; plot(f_kHz_dnrz, PSD_drz, 'r-', 'LineWidth', 1.5); plot(f_kHz_dnrz, PSD_snrz, 'g-', 'LineWidth', 1.5); plot(f_kHz_dnrz, PSD_srz, 'm-', 'LineWidth', 1.5); hold off; grid on; xlim([0, 10*Rb/1000]); % 显示到10倍比特率频率(10kHz) title('四种基带码型的功率谱密度 (线性坐标)'); xlabel('频率 (kHz)'); ylabel('功率谱密度 (W/Hz)'); legend('DNRZ', 'DRZ', 'SNRZ', 'SRZ', 'Location', 'best'); % 子图2: 对数坐标对比 (观察旁瓣衰减和动态范围) subplot(2,1,2); plot(f_kHz_dnrz, PSD_dnrz_dB, 'b-', 'LineWidth', 1.5); hold on; plot(f_kHz_dnrz, PSD_drz_dB, 'r-', 'LineWidth', 1.5); plot(f_kHz_dnrz, PSD_snrz_dB, 'g-', 'LineWidth', 1.5); plot(f_kHz_dnrz, PSD_srz_dB, 'm-', 'LineWidth', 1.5); hold off; grid on; xlim([0, 10*Rb/1000]); ylim([-100, 10]); % 设置合理的dB范围 title('四种基带码型的功率谱密度 (对数坐标,单位: dB)'); xlabel('频率 (kHz)'); ylabel('功率谱密度 (dB)'); legend('DNRZ', 'DRZ', 'SNRZ', 'SRZ', 'Location', 'best');5.2 功率谱图深度解读:理论与仿真的碰撞
运行上述代码,你会得到两张功率谱图:一张线性坐标,一张对数坐标。我们结合理论,重点看对数坐标图,因为它能清晰展示能量分布的细节。
1. DNRZ的谱特征:
- 主瓣宽度:它的能量主要集中在低频段。理论上,NRZ码的功率谱形状为
(sin(πfTb)/(πfTb))^2,即sinc函数的平方。第一个零点出现在f = Rb处(本例中为1kHz)。我们的仿真图中可以看到,在1kHz附近,DNRZ的谱线确实降到了很低。 - 旁瓣衰减:在主瓣之外,能量以
1/f^2的速度衰减(每倍频程下降6dB)。图中可以看到明显的旁瓣结构。 - 直流分量:由于是双极性且0和1等概,理论上直流分量为零。仿真中在0Hz处没有明显的冲激线,与理论相符。
2. DRZ的谱特征:
- 主瓣宽度:明显变宽!因为归零脉冲更窄(τ=Tb/2),其频谱主瓣宽度是不归零码的两倍。第一个零点出现在
f = 1/τ = 2Rb处(本例中为2kHz)。图中清晰显示DRZ的能量在2kHz之后才显著下降。 - 旁瓣衰减:与DNRZ类似,但因为它相当于用了一个更窄的矩形窗,其旁瓣衰减速度与DNRZ相同。
- 时钟分量:由于每个比特都有归零动作,信号中存在周期性的成分(尽管幅度随机),这可能在频谱的
Rb(1kHz)及其谐波处产生离散的谱线,用于时钟提取。仿真中可能表现为在1kHz、2kHz等处有小的凸起。
3. SNRZ的谱特征:
- 主瓣宽度:与DNRZ相同,第一个零点也在
f = Rb处。 - 致命的直流分量:这是单极性码最显著的特征!在频谱的0Hz处,有一个非常高的尖峰,这代表了信号中的平均直流功率。在线性坐标图中,这个尖峰会异常突出;在对数坐标图中,0Hz处的值也远高于其他频点。这意味着大量能量被浪费在无法传输的直流上。
- 连续谱形状:除了直流冲激,其连续谱部分与DNRZ相似,但整体电平更高(因为增加了直流功率)。
4. SRZ的谱特征:
- 主瓣宽度:与DRZ相同,因为脉冲宽度也是τ=Tb/2,第一个零点在
f = 2Rb(2kHz)。 - 直流分量:同样存在,因为只有正脉冲,信号平均值大于零。0Hz处也有尖峰,但可能比SNRZ的小,因为“1”码只占半个周期。
- 时钟分量:非常强!由于“1”码产生规则的正脉冲,而“0”码全为零,信号中包含了很强的
Rb(1kHz)基频及其谐波分量。这在频谱图上表现为在1kHz、2kHz等处有明显的离散谱线。这正是接收端用窄带滤波器就能轻松提取出时钟信号的原因。
参数设置心得:
pwelch函数的参数设置直接影响谱估计的质量。nfft越大,频率分辨率越高,曲线越光滑,但计算量也越大。window选择汉明窗或汉宁窗可以有效抑制“频谱泄漏”(即能量扩散到不该有的频率上)。noverlap设置重叠,可以增加用于平均的段数,使最终谱估计更平滑。对于这类随机数字信号,我通常从nfft=2048或4096开始尝试。如果谱线起伏太大,就增加nfft或使用更长的window。
6. 从仿真结果看工程选型:如何为你的系统挑选码型?
仿真图表很漂亮,但最终要落到实际应用。这四种基本码型各自适合什么场景?又有什么坑要避开?
6.1 带宽效率与时钟恢复的权衡
- 追求带宽效率,选NRZ:DNRZ和SNRZ的主瓣窄,在
Rb处就有第一个零点,这意味着传输它们所需的最小理论带宽(尼奎斯特带宽)约为Rb/2。在带宽紧张的场合(如高速长途光纤通信),NRZ码是主流。- 代价:需要独立的时钟信道或复杂的时钟恢复电路(如锁相环PLL)来从长连0/1序列中提取时钟。
- 追求时钟恢复简单,选RZ:DRZ和SRZ频谱中富含时钟线(特别是SRZ),接收端用一个简单的窄带滤波器或锁相环就能轻松提取出稳定的时钟。
- 代价:带宽需求翻倍(主瓣宽到
2Rb)。在同样的信道带宽下,你能传输的数据速率只有NRZ的一半。
- 代价:带宽需求翻倍(主瓣宽到
工程中的折衷:于是就有了“扰码”和“线路编码”。比如,在光纤SDH/SONET系统中,先对NRZ数据进行扰码,打乱长连0/1,再传输。接收端先恢复时钟,再解扰码。这样既保持了NRZ的带宽效率,又解决了时钟恢复难题。
6.2 直流分量与信道适应性
- 避免直流,选双极性:DNRZ和DRZ没有直流分量(在0、1等概时),可以通过变压器或电容耦合,适合有线信道。它们对信道低频响应不敏感。
- 小心直流,慎用单极性:SNRZ和SRZ有显著的直流分量。如果信道不能传输直流(比如通过交流耦合的射频链路或光纤),信号会产生严重的基线漂移,导致判决错误。除非系统专门为单极性设计了直流恢复电路,否则应避免使用。
一个经典应用:早期的以太网(10BASE5, 10BASE2)使用的就是曼彻斯特码,它是一种特殊的双极性归零码。每个比特中间都有跳变,“0”是低到高跳变,“1”是高到低跳变。它完美结合了归零码(自带时钟)和双极性码(无直流)的优点,当然,代价是带宽效率最低(需要两倍带宽)。
6.3 抗噪声与误码性能
在加性高斯白噪声(AWGN)信道下,如果比较最佳接收的性能(匹配滤波器+采样判决),对于相同的比特能量Eb,双极性码的抗噪声性能优于单极性码。
- 直观理解:DNRZ中,“1”和“0”的信号距离是
2A;而SNRZ中,“1”和“0”的距离只有A。在同样的噪声水平下,距离越大,越不容易判错。 - 定量分析:在0、1等概时,达到相同误码率,单极性码需要的信噪比(SNR)比双极性码高3dB。这意味着发射功率需要翻倍!
所以,从抗噪声角度看,DNRZ是这四种基本码型中的“优等生”:带宽效率高、无直流、抗噪性好。它的主要短板就是时钟恢复。
7. 仿真进阶:验证理论公式与探索边界条件
我们的仿真不能只停留在画图上,还要用它来验证理论,甚至发现理论中忽略的细节。
7.1 验证功率谱公式
我们可以编写代码,计算上述四种码型的理论功率谱密度公式,并将其与仿真得到的周期图进行叠加比较。
以双极性不归零码(DNRZ)为例,其理论功率谱公式为:P(f) = A^2 * Tb * (sin(πfTb)/(πfTb))^2 = A^2 * Tb * sinc^2(fTb)(假设0、1等概且独立)。
% ========== 绘制DNRZ理论PSD与仿真PSD对比 ========== figure; % 理论计算 f_theory = f_dnrz; % 使用相同的频率点 P_theory_dnrz = (A^2 * Tb) * (sinc(f_theory * Tb)).^2; % sinc(x) = sin(πx)/(πx) % 绘制对比 plot(f_kHz_dnrz, PSD_dnrz, 'b-', 'LineWidth', 1.5, 'DisplayName', '仿真 (Welch)'); hold on; plot(f_kHz_dnrz, P_theory_dnrz, 'r--', 'LineWidth', 2, 'DisplayName', '理论公式'); hold off; grid on; xlim([0, 10*Rb/1000]); title('DNRZ功率谱密度:仿真 vs. 理论'); xlabel('频率 (kHz)'); ylabel('功率谱密度'); legend('show'); % 计算并显示均方误差 (MSE),量化仿真精度 mse = mean((PSD_dnrz - P_theory_dnrz').^2); fprintf('DNRZ仿真与理论PSD的均方误差(MSE)为: %e\n', mse);运行这段代码,你会看到两条曲线高度重合,并且MSE值非常小(例如10^-6量级)。这强有力地证明了我们仿真方法的正确性。对于其他码型,你也可以找到对应的理论公式进行验证(例如,单极性NRZ的谱包含一个直流冲激函数δ(f)和一个连续谱部分)。
7.2 探索非等概情况的影响
我们之前的仿真默认“0”和“1”等概出现(randi([0,1]))。但实际数据中,可能会出现“0”多或“1”多的情况。这会对功率谱,尤其是直流分量,产生什么影响?
% ========== 生成非等概序列:'1'的概率为p = 0.8 ========== p = 0.8; N_bits = 10000; % 使用更多比特以获得稳定的统计特性 bit_seq_biased = rand(1, N_bits) < p; % 以概率p生成1 % 生成非等概下的SNRZ信号 t_long = 0:dt:(N_bits*Tb - dt/2); snrz_signal_biased = generate_SNRZ(bit_seq_biased, t_long, Tb, A); % 计算其功率谱 [PSD_snrz_biased, f_snrz_biased] = pwelch(snrz_signal_biased, window, noverlap, nfft, fs, 'power'); PSD_snrz_biased_dB = 10*log10(PSD_snrz_biased); % 与等概情况对比(重新用长序列生成等概SNRZ) bit_seq_equal = randi([0,1], 1, N_bits); snrz_signal_equal = generate_SNRZ(bit_seq_equal, t_long, Tb, A); [PSD_snrz_equal, ~] = pwelch(snrz_signal_equal, window, noverlap, nfft, fs, 'power'); PSD_snrz_equal_dB = 10*log10(PSD_snrz_equal); % 绘图对比 figure; plot(f_snrz_biased/1000, PSD_snrz_biased_dB, 'r-', 'LineWidth', 1.5, 'DisplayName', sprintf('SNRZ (p=%.1f)', p)); hold on; plot(f_snrz_biased/1000, PSD_snrz_equal_dB, 'b--', 'LineWidth', 1.5, 'DisplayName', 'SNRZ (p=0.5)'); hold off; grid on; xlim([0, 5]); ylabel('功率谱密度 (dB)'); xlabel('频率 (kHz)'); title('单极性不归零码(SNRZ)在不同比特概率下的功率谱'); legend('show');你会发现,当“1”的概率p增大到0.8时,SNRZ频谱在0Hz处的直流尖峰显著升高。因为信号的直流平均值A*p变大了。同时,连续谱部分的形状也会发生改变。这验证了理论:单极性码的直流分量与“1”的概率成正比。这在实际系统中是个大问题,如果数据流中长时间出现连续的“1”(比如传输一个全亮图像),直流分量会剧增,可能导致接收端放大器饱和。
而对于双极性码DNRZ,理论分析表明,其直流分量为A*(2p-1)。当p=0.5时,直流分量为零;当p偏离0.5时,也会产生直流分量。通过修改上面的代码测试DNRZ,你会看到类似但可能更微妙的变化。
7.3 眼图:更直观的性能评估工具
功率谱告诉我们频域特性,而“眼图”则是评估数字基带信号时域传输质量的终极工具。它通过将不同比特周期的波形叠加在一起,形成像眼睛一样的图案。眼图的张开度、噪声容限、定时误差灵敏度等一目了然。
% ========== 绘制DNRZ和DRZ的眼图 ========== % 眼图通常需要展示2-3个比特周期的叠加 sps = round(fs / Rb); % 每比特采样点数 span = 2; % 显示2个比特周期的眼图 offset = 0; % 从第几个采样点开始 % 选取一段信号用于生成眼图 signal_for_eye = dnrz_signal(offset+1 : offset + span*sps*10); % 取10个“眼”的长度 % 重塑矩阵,每一列代表一个“眼”的波形 eye_matrix = reshape(signal_for_eye, span*sps, []); figure; subplot(2,1,1); plot((0:size(eye_matrix,1)-1)/fs * 1000, eye_matrix, 'b-'); grid on; xlabel('时间 (ms)'); ylabel('幅度'); title(sprintf('双极性不归零码 (DNRZ) 眼图 (每眼%d个比特周期)', span)); xlim([0, span*Tb*1000]); % 对DRZ做同样处理 signal_for_eye_drz = drz_signal(offset+1 : offset + span*sps*10); eye_matrix_drz = reshape(signal_for_eye_drz, span*sps, []); subplot(2,1,2); plot((0:size(eye_matrix_drz,1)-1)/fs * 1000, eye_matrix_drz, 'r-'); grid on; xlabel('时间 (ms)'); ylabel('幅度'); title(sprintf('双极性归零码 (DRZ) 眼图 (每眼%d个比特周期)', span)); xlim([0, span*Tb*1000]);观察生成的眼图:
- DNRZ眼图:在比特周期边界处(0ms和1ms),所有轨迹都汇聚于一点(+1或-1),眼图是张开的。但在周期中间,由于是连续电平,轨迹是水平的,眼图中间是“实心”的。这反映了NRZ码在周期中间时刻对定时抖动不敏感(因为电平不变),但在边界时刻要求精确同步。
- DRZ眼图:眼图更加清晰!在每个比特周期中间(0.5ms处),所有轨迹都回归到零,形成一个清晰的“眼孔”。这个孔的大小直接反映了系统的噪声容限。眼图越开阔、越清晰,说明信号质量越好,对定时抖动的容忍度越高。
通过眼图,工程师可以直观判断码间串扰、噪声大小和最佳采样时刻。这是仿真分析中不可或缺的一环。
