import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
Doppler Radar: Continuous Wave Velocity Measurement¶
Introduction¶
This notebook demonstrates Doppler radar, which measures target velocity by detecting frequency shifts in reflected signals. Unlike range-measuring radars, Doppler radar excels at velocity measurement, making it essential for traffic monitoring, weather tracking, and motion detection.
The Doppler Effect¶
The Doppler effect is the change in wave frequency due to relative motion. For a radar target moving with radial velocity $v_r$:
$$f_d = \frac{2v_r f_c}{c}$$
Where:
- $f_d$ = Doppler frequency shift (Hz)
- $v_r$ = Radial velocity (m/s, positive = receding, negative = approaching)
- $f_c$ = Carrier frequency (Hz)
- $c$ = Speed of light (3×10⁸ m/s)
The factor of 2 appears because the wave travels to the target and back, experiencing the Doppler effect twice.
Continuous Wave (CW) Doppler Radar¶
CW radar transmits a constant-frequency unmodulated signal—the simplest Doppler radar configuration:
- Single-tone transmission: Constant frequency (no modulation)
- No range information: Measures velocity only, not distance
- Simple hardware: Minimal signal generation complexity
- High sensitivity: Long integration time for weak signals
Operating principle:
- Transmit continuous wave at $f_c$
- Target reflects signal with Doppler shift $f_d$
- Mixer combines transmitted and received signals
- Baseband output contains beat frequency $f_d$
- FFT extracts $f_d$ → Calculate velocity: $v_r = f_d c / (2f_c)$
Multi-Target Detection¶
Different target velocities create distinct Doppler frequencies. Doppler resolution depends on observation time:
$$\Delta f_d = \frac{1}{T_{obs}}$$
Longer observation → better velocity resolution. The FFT reveals peaks at Doppler frequencies corresponding to target velocities.
This Example¶
This notebook uses RadarSimPy to simulate:
- 10 GHz CW Doppler radar (X-band) with 0.1-second observation
- 40 kHz sampling (±300 m/s unambiguous velocity)
- Two targets: -10 m/s (approaching) and +35 m/s (receding)
- FFT processing to extract velocity spectrum with distinct peaks
Radar System Configuration¶
Transmitter Configuration¶
Configure CW transmitter for continuous-wave Doppler radar.
import numpy as np
import plotly.graph_objs as go
from IPython.display import Image, display
from radarsimpy import Radar, Transmitter, Receiver
# Set to True for interactive plots; False renders a static JPEG (e.g. for HTML export)
INTERACTIVE = False
def show(fig):
if INTERACTIVE:
fig.show()
else:
display(Image(fig.to_image(format="jpg", scale=2)))
Transmitter Parameters:
| Parameter | Value | Description |
|---|---|---|
| Frequency | 10 GHz | X-band carrier (λ = 3 cm) |
| Pulse Length | 0.1 s | Continuous observation duration |
| Transmit Power | 10 dBm | ~10 mW |
| Pulses | 1 | Single continuous observation |
Velocity Resolution:
$$\Delta v = \frac{c \Delta f_d}{2f_c} = \frac{c}{2f_c T} = \frac{3 \times 10^8}{2 \times 10^{10} \times 0.1} = 0.15 \text{ m/s}$$
# Configure CW Doppler transmitter
tx = Transmitter(
f=10e9, # Carrier frequency: 10 GHz (X-band, single-tone)
t=0.1, # Pulse length: 0.1 seconds (CW observation)
tx_power=10, # Transmit power: 10 dBm (~10 mW)
pulses=1, # Single pulse (continuous wave)
channels=[dict(location=(0, 0, 0))], # Single antenna at origin
)
Receiver Configuration¶
Receiver Parameters:
| Parameter | Value | Description |
|---|---|---|
| Sampling Rate | 40 kHz | Nyquist frequency: 20 kHz → ±300 m/s |
| Noise Figure | 6 dB | Low noise for sensitive detection |
| RF Gain | 20 dB | LNA amplification |
| Baseband Gain | 50 dB | High gain (total: 70 dB) |
| Load Resistor | 1000 Ω | High impedance |
Maximum Velocity:
$$v_{max} = \frac{f_s}{2} \times \frac{c}{2f_c} = 20{,}000 \times \frac{3 \times 10^8}{2 \times 10^{10}} = 300 \text{ m/s}$$
# Configure Doppler radar receiver
rx = Receiver(
fs=40000, # Sampling rate: 40 kHz (±20 kHz Doppler, ±300 m/s)
noise_figure=6, # Noise figure: 6 dB (low noise)
rf_gain=20, # RF gain: 20 dB
baseband_gain=50, # Baseband gain: 50 dB (high sensitivity)
load_resistor=1000, # Load resistance: 1000 Ω (high impedance)
channels=[dict(location=(0, 0, 0))], # Single antenna at origin
)
Create Radar System¶
# Create complete CW Doppler radar system
radar = Radar(transmitter=tx, receiver=rx)
Target Configuration¶
Define two point targets with different velocities to demonstrate multi-target Doppler detection.
Target Parameters:
| Target | Range (m) | RCS (dBsm) | Velocity (m/s) | Expected Doppler (Hz) |
|---|---|---|---|---|
| 1 (Approaching) | 30 | 20 | -10 (approaching) | -666.7 |
| 2 (Receding) | 35 | 20 | +35 (receding) | +2333.3 |
Frequency Separation:
$$\Delta f_d = |f_{d2} - f_{d1}| = |2333.3 - (-666.7)| = 3000 \text{ Hz}$$
This 3 kHz separation is much larger than the Doppler resolution (10 Hz), ensuring clear separation.
# Configure Target 1: Approaching vehicle
target1 = dict(
location=(30, 0, 0), # Position: 30m along x-axis
rcs=20, # Radar cross section: 20 dBsm
speed=(-10, 0, 0), # Velocity: -10 m/s (approaching, negative Doppler)
phase=0, # Initial phase: 0 degrees
)
# Configure Target 2: Receding vehicle
target2 = dict(
location=(35, 0, 0), # Position: 35m along x-axis
rcs=20, # Radar cross section: 20 dBsm
speed=(35, 0, 0), # Velocity: +35 m/s (receding, positive Doppler)
phase=0, # Initial phase: 0 degrees
)
# Combine targets for simulation
targets = [target1, target2]
Simulate Baseband Signals¶
Generate baseband I/Q signals containing Doppler frequency shifts from both targets.
Output Structure:
- Dimensions: [channels, pulses, samples] = [1, 1, 4000]
- Content: Two sinusoids at -666.7 Hz and +2333.3 Hz plus receiver noise
# Import radar simulator
from radarsimpy.simulator import sim_radar
# Simulate Doppler radar returns from both targets
data = sim_radar(radar, targets)
# Extract timestamp and baseband signals
timestamp = data["timestamp"] # Time axis for each sample
baseband = data["baseband"] + data["noise"] # Complex I/Q with noise [1, 1, 4000]
Visualize Baseband I/Q Signals¶
The baseband shows a beat frequency pattern from the superposition of two Doppler-shifted sinusoids:
- Slow beating from 666.7 Hz (Target 1)
- Faster oscillation from 2333.3 Hz (Target 2)
# Create figure for I/Q baseband visualization
fig = go.Figure()
# Plot In-phase (I) component
fig.add_trace(
go.Scatter(
x=timestamp[0, 0, :],
y=np.real(baseband[0, 0, :]),
name="I (In-phase)",
line=dict(color='blue', width=1),
)
)
# Plot Quadrature (Q) component
fig.add_trace(
go.Scatter(
x=timestamp[0, 0, :],
y=np.imag(baseband[0, 0, :]),
name="Q (Quadrature)",
line=dict(color='red', width=1),
)
)
fig.update_layout(
title="Baseband I/Q Signals: Two Doppler-Shifted Targets",
yaxis=dict(title="Amplitude (V)", gridcolor='lightgray'),
xaxis=dict(title="Time (s)", gridcolor='lightgray'),
height=500,
legend=dict(x=0.02, y=0.98),
hovermode='x unified',
)
show(fig)
Doppler Signal Processing¶
Apply FFT to extract Doppler frequencies and convert to target velocities.
Velocity Conversion:
From Doppler frequency to velocity:
$$v = \frac{f_d \cdot c}{2f_c}$$
Where:
- $v$ = Radial velocity (m/s)
- $f_d$ = Doppler frequency (Hz)
- $c$ = Speed of light (3×10⁸ m/s)
- $f_c$ = Carrier frequency (10 GHz)
Expected Peaks:
- Peak 1: -10 m/s (Target 1, approaching)
- Peak 2: +35 m/s (Target 2, receding)
# Import FFT functions and constants
from scipy.fft import fft, fftshift
from scipy.constants import speed_of_light
# Perform FFT on baseband signal and shift zero frequency to center
spec = fftshift(fft(baseband[0, 0, :])) # Complex spectrum [4000 frequency bins]
# Create velocity axis by converting frequency to velocity
# Doppler frequency axis: -fs/2 to +fs/2
# Velocity: v = f_d * c / (2*f_c)
speed = (
np.linspace(
-rx.bb_prop["fs"] / 2, # -20 kHz (maximum approaching)
rx.bb_prop["fs"] / 2, # +20 kHz (maximum receding)
radar.sample_prop["samples_per_pulse"], # 4000 bins
endpoint=False,
)
* speed_of_light # Multiply by c (3e8 m/s)
/ 2 # Divide by 2 (two-way propagation)
/ 10e9 # Divide by f_c (10 GHz)
)
# Velocity axis spans: -300 m/s to +300 m/s
Visualize Doppler Velocity Spectrum¶
The spectrum shows two clear peaks at the expected target velocities.
# Create figure for Doppler spectrum
fig = go.Figure()
# Plot magnitude spectrum in dB
fig.add_trace(
go.Scatter(
x=speed,
y=20 * np.log10(np.abs(spec)),
name="Doppler Spectrum",
line=dict(color='purple', width=2),
mode='lines',
)
)
# Add vertical lines at expected target velocities
fig.add_vline(x=-10, line_dash="dash", line_color="blue",
annotation_text="Target 1<br>-10 m/s", annotation_position="top")
fig.add_vline(x=35, line_dash="dash", line_color="red",
annotation_text="Target 2<br>+35 m/s", annotation_position="top")
fig.update_layout(
title="Doppler Velocity Spectrum: Two-Target Detection (10 GHz)",
yaxis=dict(title="Magnitude (dB)", gridcolor='lightgray'),
xaxis=dict(title="Velocity (m/s)", range=[-100, 100], gridcolor='lightgray'),
height=500,
showlegend=False,
)
show(fig)
Summary¶
This example demonstrated CW Doppler radar for velocity measurement:
- Doppler shift $f_d = 2v_r f_c / c$ enables direct velocity measurement
- CW transmission provides simple hardware with high sensitivity
- FFT processing extracts Doppler frequencies and converts to velocities
- Multi-target detection shows distinct peaks for different velocities
- Velocity resolution $\Delta v = c/(2f_c T)$ improves with longer observation
Things to Try¶
| Experiment | What to Change | What to Observe |
|---|---|---|
| Different velocities | Set speeds: 5, 20, 50, 100 m/s | Doppler frequency scaling with $f_d \propto v$ |
| Frequency bands | Change carrier: 5, 24, 77 GHz | Doppler sensitivity $f_d \propto f_c$ |
| Observation time | Vary pulse length: 0.01, 0.5, 1 s | Velocity resolution $\Delta v = c/(2f_c T)$ |
| Sampling rate | Reduce to 10, 20 kHz | Velocity ambiguity (aliasing) |
| Close velocities | Set 10 and 11 m/s | Resolution limits and peak separation |
| RCS variations | Change RCS: 0, 10, 30 dBsm | Amplitude differences and dynamic range |
| Perpendicular motion | Set velocity (0, 10, 0) | Zero Doppler (no radial component) |