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()
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能提供高距离分辨率——它的能量分布在更宽的频带上。
4.6 本章知识体系
下面这张图,是我自己总结的连续波信号生成的知识脉络:
这张图把三种波形的数学表达式、典型应用和关键参数都串起来了。做仿真时,对着这张图检查参数设置,能少走很多弯路。
好了,连续波信号生成就讲到这里。代码我都跑过,直接复制就能用。但建议你动手改改参数——比如把采样率从1000改成500,看看波形会不会失真?把LFM的带宽加大,频谱会有什么变化?
这些东西,光看是学不会的。得动手,得踩坑,才能真正理解。