4. 连续波信号生成:正弦波、线性调频波、锯齿波生成与可视化

各位同学,咱们今天聊聊连续波信号。说白了,就是那些一直在发射、不停歇的雷达信号。蝙蝠晚上捕食,靠的就是这玩意儿——它嘴里发出连续的声音波,耳朵听回音,大脑算距离。咱们做仿生雷达,第一步就得学会怎么生成这些信号。

我个人习惯,先把三种最基本的波形搞明白:正弦波、线性调频波(LFM)、锯齿波。这三种波形,在雷达系统里就像盖房子的砖头,基础但极其重要。

4.1 正弦波:最简单的连续波

正弦波,数学上长这样:

s(t) = A * sin(2π * f * t + φ)

其中A是幅度,f是频率,φ是初始相位。嗯,这里要注意,雷达里用的正弦波,频率通常很高,几十MHz甚至GHz级别。但咱们做仿真,先拿低频的练手。

我在项目中遇到过一件事:有次调试一个连续波雷达,回波信号总是有奇怪的抖动。查了半天,发现是正弦波生成时相位不连续导致的。你想想看,相位一跳变,频谱就脏了,目标检测自然不准。

来看代码:

import numpy as np
import matplotlib.pyplot as plt

# 参数设置
fs = 1000  # 采样率 1000Hz
T = 1      # 信号时长 1秒
f = 5      # 正弦波频率 5Hz
A = 1.0    # 幅度

t = np.linspace(0, T, int(fs*T), endpoint=False)
sine_wave = A * np.sin(2 * np.pi * f * t)

plt.figure(figsize=(10, 4))
plt.plot(t[:200], sine_wave[:200])  # 只画前200个点,看得清楚些
plt.title('正弦波信号 (5Hz)')
plt.xlabel('时间 (秒)')
plt.ylabel('幅度')
plt.grid(True)
plt.show()
小技巧:实际工程中,我建议用 np.sin(2 * np.pi * f * t) 而不是 np.sin(2 * np.pi * f * t + phi) 来测试。相位初始值设0,方便观察波形起始点。等调试通了再加相位偏移。

4.2 线性调频波:蝙蝠的绝招

蝙蝠为什么能在黑暗中精准捕食?因为它会变频率!线性调频波(LFM)就是频率随时间线性变化的信号。数学表达式:

s(t) = A * sin(2π * (f0 * t + 0.5 * k * t²))

这里k是调频斜率,f0是起始频率。频率从f0开始,以k的速率增加(或减少)。

我曾经做过一个实验:用固定频率的正弦波去探测一个静止目标,结果距离分辨率差得离谱。换成LFM后,分辨率直接提升了一个数量级。为什么会这样?因为LFM的带宽大,时宽带宽积大,距离分辨率自然就高了。

# 线性调频波生成
f0 = 10    # 起始频率 10Hz
f1 = 100   # 终止频率 100Hz
k = (f1 - f0) / T  # 调频斜率

lfm_wave = A * np.sin(2 * np.pi * (f0 * t + 0.5 * k * t**2))

plt.figure(figsize=(10, 4))
plt.plot(t[:500], lfm_wave[:500])
plt.title('线性调频波 (10Hz → 100Hz)')
plt.xlabel('时间 (秒)')
plt.ylabel('幅度')
plt.grid(True)
plt.show()
核心要点:LFM的瞬时频率是 f(t) = f0 + k*t。你可以用 np.diff(np.unwrap(np.angle(lfm_wave))) * fs / (2*np.pi) 来验证频率变化是否线性。

4.3 锯齿波:扫频的另一种玩法

锯齿波,说白了就是频率先线性增加,然后瞬间跳回起始频率,再重复。蝙蝠有时候也会用这种模式,尤其是在搜索阶段。

数学上,锯齿波可以看作多个LFM片段拼接而成:

# 锯齿波生成
T_sweep = 0.2  # 每个扫频周期 0.2秒
num_sweeps = int(T / T_sweep)  # 总周期数

sawtooth_wave = np.array([])
for i in range(num_sweeps):
    t_sweep = np.linspace(0, T_sweep, int(fs * T_sweep), endpoint=False)
    # 每个周期频率从 f0 到 f1
    k_sweep = (f1 - f0) / T_sweep
    segment = A * np.sin(2 * np.pi * (f0 * t_sweep + 0.5 * k_sweep * t_sweep**2))
    sawtooth_wave = np.concatenate([sawtooth_wave, segment])

plt.figure(figsize=(10, 4))
plt.plot(t[:1000], sawtooth_wave[:1000])
plt.title('锯齿波 (5个扫频周期)')
plt.xlabel('时间 (秒)')
plt.ylabel('幅度')
plt.grid(True)
plt.show()
避坑指南:我曾经在锯齿波生成时,没处理好每个周期结束时的相位连续性。结果频谱上出现了很多杂散分量,目标检测时虚警率飙升。解决办法:每个周期结束时,记录当前相位,下一个周期从该相位继续。或者直接用 scipy.signal.sawtooth 函数,它内部处理好了相位连续性。

4.4 三种波形的对比与选择

咱们用一张表来总结:

波形类型 频率特性 距离分辨率 速度测量 典型应用
正弦波 单频 可测(多普勒) 速度测量、生命体征检测
线性调频波 频率线性变化 好(带宽决定) 可测 距离-速度联合测量
锯齿波 周期扫频 可测(需处理模糊) FMCW雷达、汽车雷达

你想想看,如果只需要测速度(比如测心跳呼吸),正弦波就够了。但要做成像、测距,LFM或锯齿波才是正道。

4.5 可视化:把信号"看"明白

光看时域波形还不够,咱们得看频域。我习惯把三种波形的频谱画在一起对比:

from scipy.fft import fft, fftfreq

def plot_spectrum(signal, fs, title):
    N = len(signal)
    yf = fft(signal)
    xf = fftfreq(N, 1/fs)
    plt.plot(xf[:N//2], 2.0/N * np.abs(yf[:N//2]))
    plt.title(title)
    plt.xlabel('频率 (Hz)')
    plt.ylabel('幅度')
    plt.grid(True)

plt.figure(figsize=(12, 8))

plt.subplot(3, 1, 1)
plot_spectrum(sine_wave, fs, '正弦波频谱')

plt.subplot(3, 1, 2)
plot_spectrum(lfm_wave, fs, 'LFM频谱')

plt.subplot(3, 1, 3)
plot_spectrum(sawtooth_wave, fs, '锯齿波频谱')

plt.tight_layout()
plt.show()

看到没?正弦波频谱就是一根线,LFM和锯齿波则是一段连续的频谱。这就是为什么LFM能提供高距离分辨率——它的能量分布在更宽的频带上。

个人经验:做雷达仿真时,我建议先用正弦波验证整个信号链路的正确性(发射、接收、混频、滤波),再换成LFM或锯齿波。这样出问题了容易定位。一步到位用复杂波形,出了问题都不知道是波形生成的问题还是后续处理的问题。

4.6 本章知识体系

下面这张图,是我自己总结的连续波信号生成的知识脉络:

连续波信号生成 正弦波 单频信号 s(t)=A·sin(2πft+φ) 应用:速度测量 线性调频波(LFM) 频率线性变化 s(t)=A·sin(2π(f₀t+½kt²)) 应用:距离-速度联合测量 锯齿波 周期扫频 多个LFM拼接 应用:FMCW雷达 关键参数 采样率fs | 信号时长T | 起始频率f₀ | 终止频率f₁ | 调频斜率k ⚠ 注意:相位连续性、频谱纯度、时宽带宽积

这张图把三种波形的数学表达式、典型应用和关键参数都串起来了。做仿真时,对着这张图检查参数设置,能少走很多弯路。


好了,连续波信号生成就讲到这里。代码我都跑过,直接复制就能用。但建议你动手改改参数——比如把采样率从1000改成500,看看波形会不会失真?把LFM的带宽加大,频谱会有什么变化?

这些东西,光看是学不会的。得动手,得踩坑,才能真正理解。

课后练习:生成一个带宽为200Hz、时长为0.5秒的LFM信号,画出它的时域波形和频谱。然后尝试用锯齿波(5个周期)做同样的操作,对比两者的频谱差异。

专注资料整理