别再混淆了!用Python+Matplotlib亲手画NRZ和RZ信号,搞懂时频域区别
用Python实战解析NRZ与RZ信号的时频域特性差异
在数字通信系统设计中,NRZ(非归零码)和RZ(归零码)是两种基础但至关重要的信号编码方式。许多教材和理论课程会直接给出它们的数学定义和频谱公式,但对于初学者来说,这些抽象概念往往难以形成直观理解。本文将带您通过Python代码亲手生成这些信号,并可视化它们的时域波形和频域特性,从而深入掌握两者的核心差异。
1. 理解NRZ与RZ信号的本质区别
在开始编码之前,我们需要明确NRZ和RZ信号的基本特征。这两种编码方式都用于表示二进制数据(0和1),但它们的物理表现形式有着根本不同。
NRZ信号在整个比特周期内保持电平不变。也就是说,如果当前比特是1,信号在整个周期内都保持高电平;如果是0,则保持低电平。这种编码方式简单直接,但存在一个潜在问题:当连续多个相同比特出现时,信号会长时间保持同一电平,这使得时钟恢复变得困难。
相比之下,RZ信号会在每个比特周期中间回归到零电平。具体来说:
- 对于比特1:信号在前半周期为高电平,后半周期回归零
- 对于比特0:信号在整个周期保持零电平(或负电平,取决于具体实现)
这种"归零"特性使得RZ信号具有更强的时钟信息,但也带来了带宽效率的代价。我们可以通过改变"占空比"(高电平持续时间与整个比特周期的比例)来调整RZ信号的特性。
import numpy as np import matplotlib.pyplot as plt # 基本参数设置 bit_rate = 1e6 # 1Mbps bit_duration = 1 / bit_rate # 每个比特的持续时间(秒) sample_rate = 100e6 # 100MHz采样率 samples_per_bit = int(sample_rate / bit_rate) t = np.linspace(0, bit_duration, samples_per_bit, endpoint=False) # 生成单个NRZ比特 def generate_nrz_bit(bit_value): return np.full(samples_per_bit, bit_value) # 生成单个RZ比特(50%占空比) def generate_rz_bit(bit_value, duty_cycle=0.5): signal = np.zeros(samples_per_bit) if bit_value == 1: on_samples = int(samples_per_bit * duty_cycle) signal[:on_samples] = 1 return signal2. 时域波形生成与可视化对比
现在让我们实际生成并比较这些信号的时域波形。我们将创建一个包含交替1和0的比特序列,以便清楚地观察信号的变化。
# 生成比特序列 bits = [1, 0, 1, 1, 0, 0, 1, 0] # 生成NRZ信号 nrz_signal = np.concatenate([generate_nrz_bit(b) for b in bits]) # 生成不同占空比的RZ信号 rz_50_signal = np.concatenate([generate_rz_bit(b, 0.5) for b in bits]) rz_75_signal = np.concatenate([generate_rz_bit(b, 0.75) for b in bits]) # 绘制时域波形 plt.figure(figsize=(12, 8)) # NRZ信号 plt.subplot(3, 1, 1) plt.plot(nrz_signal) plt.title('NRZ信号时域波形') plt.ylim(-0.2, 1.2) plt.grid(True) # 50%占空比RZ信号 plt.subplot(3, 1, 2) plt.plot(rz_50_signal) plt.title('50%占空比RZ信号时域波形') plt.ylim(-0.2, 1.2) plt.grid(True) # 75%占空比RZ信号 plt.subplot(3, 1, 3) plt.plot(rz_75_signal) plt.title('75%占空比RZ信号时域波形') plt.ylim(-0.2, 1.2) plt.grid(True) plt.tight_layout() plt.show()从生成的波形图中,我们可以直观地看到:
- NRZ信号:电平在整个比特周期内保持不变,连续1时表现为长高电平
- 50% RZ信号:每个1比特只在前半周期为高电平,后半周期归零
- 75% RZ信号:每个1比特在前3/4周期为高电平,最后1/4周期归零
注意:在实际工程中,RZ信号的占空比选择需要权衡多个因素,包括带宽效率、时钟恢复能力和系统噪声容限等。
3. 频域特性分析与功率谱密度计算
时域波形让我们看到了信号的"外貌",但要真正理解NRZ和RZ信号的区别,我们需要分析它们的频域特性。特别是,我们需要计算并比较它们的功率谱密度(PSD)。
根据傅里叶分析理论,矩形脉冲的频谱呈现Sa函数(抽样函数)形状。NRZ和RZ信号的功率谱密度可以通过以下公式计算:
NRZ信号的PSD: $$P_{NRZ}(f) = T_B \cdot \text{sinc}^2(\pi f T_B)$$
占空比为D的RZ信号的PSD: $$P_{RZ}(f) = D^2 T_B \cdot \text{sinc}^2(\pi f D T_B)$$
其中,$T_B$是比特周期,$D$是占空比(0 < D ≤ 1),$\text{sinc}(x) = \sin(x)/x$。
让我们用Python实现这些计算并可视化结果:
from scipy.fft import fft, fftshift def calculate_psd(signal, sample_rate): n = len(signal) fft_result = fft(signal) psd = np.abs(fft_result)**2 / (n * sample_rate) psd = fftshift(psd) freq = np.linspace(-sample_rate/2, sample_rate/2, n) return freq, psd # 计算各信号的PSD freq_nrz, psd_nrz = calculate_psd(nrz_signal, sample_rate) freq_rz50, psd_rz50 = calculate_psd(rz_50_signal, sample_rate) freq_rz75, psd_rz75 = calculate_psd(rz_75_signal, sample_rate) # 绘制功率谱密度 plt.figure(figsize=(12, 8)) # 只显示正频率部分 positive_freq = freq_nrz > 0 plt.plot(freq_nrz[positive_freq], 10*np.log10(psd_nrz[positive_freq]), label='NRZ') plt.plot(freq_rz50[positive_freq], 10*np.log10(psd_rz50[positive_freq]), label='50% RZ') plt.plot(freq_rz75[positive_freq], 10*np.log10(psd_rz75[positive_freq]), label='75% RZ') plt.title('NRZ与不同占空比RZ信号的功率谱密度比较') plt.xlabel('频率 (Hz)') plt.ylabel('功率谱密度 (dB/Hz)') plt.xlim(0, 5e6) # 限制显示范围以便观察主瓣 plt.grid(True) plt.legend() plt.show()从功率谱密度图中,我们可以观察到几个关键现象:
主瓣宽度:
- NRZ信号的主瓣宽度最窄,约为1/TB(比特率的倒数)
- 50% RZ信号的主瓣宽度是NRZ的两倍
- 75% RZ信号的主瓣宽度介于两者之间
旁瓣衰减:
- NRZ信号的旁瓣衰减较慢
- RZ信号的旁瓣衰减更快,特别是占空比较小的RZ信号
功率分布:
- NRZ信号的总功率集中在较窄的频带内
- RZ信号的功率分布更广,特别是高频成分更多
4. 谱效率的定量分析与工程权衡
谱效率是数字通信系统的一个重要性能指标,它表示单位带宽内能够传输的信息量。从我们的仿真结果可以清楚地看到,NRZ信号比RZ信号具有更高的谱效率。
为了量化这一差异,我们可以计算不同编码方式的主瓣宽度和等效带宽:
| 编码方式 | 占空比 | 主瓣宽度 | 相对谱效率 |
|---|---|---|---|
| NRZ | 100% | 1/TB | 1.0 |
| RZ | 75% | ~1.33/TB | ~0.75 |
| RZ | 50% | 2/TB | 0.5 |
提示:在实际系统中,谱效率的计算还需要考虑调制方式、编码方案和信道条件等因素。
虽然NRZ在谱效率上占优,但RZ信号也有其独特的优势:
- 更强的时钟恢复能力:由于RZ信号在每个比特周期都有电平跳变,接收端更容易提取时钟信号
- 更好的噪声免疫力:RZ信号对某些类型的信道损伤(如码间干扰)有更强的抵抗能力
- 适用于光通信:在光纤通信系统中,RZ格式常被用于长距离传输
# 计算并比较主瓣宽度 def find_main_lobe_width(freq, psd): peak_index = np.argmax(psd) threshold = 0.1 * psd[peak_index] # -10dB作为主瓣边界 left = np.where(psd[:peak_index] <= threshold)[0] right = np.where(psd[peak_index:] <= threshold)[0] left_edge = freq[left[-1]] if len(left) > 0 else freq[0] right_edge = freq[peak_index + right[0]] if len(right) > 0 else freq[-1] return right_edge - left_edge nrz_width = find_main_lobe_width(freq_nrz[positive_freq], psd_nrz[positive_freq]) rz50_width = find_main_lobe_width(freq_rz50[positive_freq], psd_rz50[positive_freq]) rz75_width = find_main_lobe_width(freq_rz75[positive_freq], psd_rz75[positive_freq]) print(f"NRZ主瓣宽度: {nrz_width/1e6:.2f} MHz") print(f"50% RZ主瓣宽度: {rz50_width/1e6:.2f} MHz") print(f"75% RZ主瓣宽度: {rz75_width/1e6:.2f} MHz")在实际工程设计中,选择NRZ还是RZ编码需要综合考虑以下因素:
- 带宽限制:如果信道带宽受限,NRZ可能是更好的选择
- 时钟恢复需求:在缺乏独立时钟信道的系统中,RZ可能更合适
- 传输距离:长距离传输可能更倾向于使用RZ格式
- 功耗考虑:RZ信号通常需要更高的峰值功率
5. 扩展实验:不同比特序列的影响
前面的分析使用了简单的交替比特序列。为了更全面地理解这些编码方式的特性,我们可以考察不同比特序列下的信号表现,特别是连续多个相同比特的情况。
# 生成长序列测试信号 long_bits = [1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0] # 生成长序列信号 long_nrz = np.concatenate([generate_nrz_bit(b) for b in long_bits]) long_rz50 = np.concatenate([generate_rz_bit(b, 0.5) for b in long_bits]) # 计算眼图 def plot_eye_diagram(signal, bits, samples_per_bit, title): plt.figure(figsize=(10, 4)) for i in range(1, len(bits)-1): start = i * samples_per_bit end = (i + 1) * samples_per_bit segment = signal[start:end] plt.plot(np.linspace(0, 1, samples_per_bit), segment, 'b-', alpha=0.1) plt.title(title) plt.xlabel('归一化比特周期') plt.ylabel('幅度') plt.grid(True) plt.show() plot_eye_diagram(long_nrz, long_bits, samples_per_bit, 'NRZ信号眼图') plot_eye_diagram(long_rz50, long_bits, samples_per_bit, '50% RZ信号眼图')眼图分析揭示了更多关于信号质量的细节:
- NRZ眼图:在连续相同比特时,眼图会"闭合",表明时钟恢复困难
- RZ眼图:即使连续相同比特,眼图仍保持较好的开口,有利于时钟提取
这种可视化方法为工程师提供了直观的信号质量评估工具,在实际系统调试中非常有用。
