import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
Chirp Non-Linearity in FMCW Radar: Impact on Performance¶
Introduction¶
This notebook demonstrates chirp non-linearity—a critical impairment in FMCW radar where the frequency sweep deviates from the ideal linear relationship. Even small deviations significantly degrade range resolution, accuracy, and SNR, particularly for distant targets.
Ideal vs. Non-Linear Chirp¶
Ideal linear chirp:
$$f(t) = f_0 + \frac{B}{T_c} \cdot t$$
Real hardware adds deviation $\epsilon(t)$ from PLL dynamics, VCO non-idealities, temperature drift, and component tolerances:
$$f(t) = f_0 + \frac{B}{T_c} \cdot t + \epsilon(t)$$
Why Linearity Matters¶
FMCW range measurement assumes a linear sweep: $R = f_b c T_c / (2B)$. Non-linearity violates this assumption and causes:
- Peak broadening: Targets appear wider in range (reduced resolution)
- Range shift: Incorrect range measurement (bias error)
- Sidelobe increase: Higher spectral leakage; false targets
- SNR loss: Energy spread across multiple bins
The degradation increases with range—errors accumulate over the longer round-trip delay of distant targets.
Mitigation¶
- Hardware: PLL optimization, VCO pre-distortion, temperature control
- Signal processing: Phase correction, chirp calibration lookup tables, robust windowing
This Example¶
This notebook uses RadarSimPy to compare:
- Non-linear chirp: 100-point measured frequency curve from a real synthesizer
- Linear chirp: Ideal 2-point definition (reference)
- 3 targets: 30 m, 95 m, and 200 m — to show range-dependent degradation
- Processing: Range FFT with Chebyshev windowing
Radar System Configuration¶
Waveform Definition: Non-Linear vs. Linear Chirp¶
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)))
RadarSimPy supports arbitrary waveforms by specifying frequency as a multi-point function of time. This enables simulation of measured chirps from real hardware.
- Non-linear chirp: 100-point curve captured from an FMCW synthesizer—shows subtle deviations from perfect linearity due to PLL dynamics and VCO non-idealities
- Linear chirp: Defined by just 2 points (start and end)—the theoretical ideal reference
# Define non-linear chirp from measured frequency data
# This represents a realistic chirp with imperfections from hardware limitations
# 100 frequency points captured over 80 μs chirp duration
freq_nonlinear = np.array(
[
2.40750000e10, # Start: 24.075 GHz
2.40760901e10,
2.40771786e10,
2.40782654e10,
2.40793506e10,
2.40804341e10,
2.40815161e10,
2.40825964e10,
2.40836750e10,
2.40847521e10,
2.40858275e10,
2.40869012e10,
2.40879734e10,
2.40890439e10,
2.40901127e10,
2.40911800e10,
2.40922456e10,
2.40933096e10,
2.40943719e10,
2.40954326e10,
2.40964917e10,
2.40975491e10,
2.40986049e10,
2.40996591e10,
2.41007117e10,
2.41017626e10,
2.41028119e10,
2.41038595e10,
2.41049055e10,
2.41059499e10,
2.41069927e10,
2.41080338e10,
2.41090733e10,
2.41101111e10,
2.41111473e10,
2.41121819e10,
2.41132149e10,
2.41142462e10,
2.41152759e10,
2.41163039e10,
2.41173304e10,
2.41183552e10,
2.41193783e10,
2.41203999e10,
2.41214198e10,
2.41224380e10,
2.41234546e10,
2.41244696e10,
2.41254830e10,
2.41264947e10,
2.41275048e10,
2.41285133e10,
2.41295202e10,
2.41305254e10,
2.41315289e10,
2.41325309e10,
2.41335312e10,
2.41345298e10,
2.41355269e10,
2.41365223e10,
2.41375161e10,
2.41385082e10,
2.41394987e10,
2.41404876e10,
2.41414748e10,
2.41424605e10,
2.41434444e10,
2.41444268e10,
2.41454075e10,
2.41463866e10,
2.41473640e10,
2.41483399e10,
2.41493140e10,
2.41502866e10,
2.41512575e10,
2.41522268e10,
2.41531945e10,
2.41541605e10,
2.41551249e10,
2.41560876e10,
2.41570488e10,
2.41580083e10,
2.41589661e10,
2.41599224e10,
2.41608770e10,
2.41618299e10,
2.41627812e10,
2.41637309e10,
2.41646790e10,
2.41656254e10,
2.41665702e10,
2.41675134e10,
2.41684550e10,
2.41693949e10,
2.41703331e10,
2.41712698e10,
2.41722048e10,
2.41731381e10,
2.41740699e10,
2.41750000e10, # End: 24.175 GHz
]
) # 100 frequency points (Hz)
# Time axis for non-linear chirp (80 μs, 100 samples)
t_nonlinear = np.linspace(0, 80e-6, 100) # 0 to 80 microseconds
Define Ideal Linear Chirp¶
The ideal linear chirp is defined by just start and end frequencies—representing perfect linearity.
# Define ideal linear chirp (2-point definition)
# Perfectly linear frequency sweep from start to end
freq_linear = np.array([24.125e9 - 50e6, 24.125e9 + 50e6]) # [24.075, 24.175] GHz
# Time axis for linear chirp (start and end only)
t_linear = np.array([0, 80e-6]) # [0, 80] microseconds
Visualize Chirp Comparison¶
Display frequency vs. time for both chirps to observe non-linearity deviations.
# Create figure comparing linear and non-linear chirps
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=t_nonlinear * 1e6,
y=freq_nonlinear / 1e9,
name="Non-linear chirp",
)
)
fig.add_trace(
go.Scatter(
x=t_linear * 1e6,
y=freq_linear / 1e9,
name="Linear chirp (ideal)",
)
)
fig.update_layout(
title="Chirp Comparison: Non-Linear (Measured) vs. Linear (Ideal)",
yaxis=dict(tickformat=".2f", title="Frequency (GHz)", gridcolor='lightgray'),
xaxis=dict(tickformat=".2f", title="Time (μs)", gridcolor='lightgray'),
height=500,
legend=dict(x=0.02, y=0.98),
)
show(fig)
Transmitter Configuration¶
Both transmitters are identical except for the chirp waveform, enabling direct comparison.
| Parameter | Non-linear | Linear |
|---|---|---|
| Frequency | 100-point measured curve | 2-point ideal [24.075, 24.175] GHz |
| Time | 100 points over 80 μs | [0, 80] μs |
| Bandwidth | 100 MHz (both) | 100 MHz (both) |
| TX Power | 60 dBm | 60 dBm |
| PRP | 100 μs | 100 μs |
| Pulses | 1 | 1 |
# Configure transmitter with non-linear chirp
tx_nonlinear = Transmitter(
f=freq_nonlinear, # Non-linear chirp: 100-point measured frequency curve
t=t_nonlinear, # Time array: 100 points over 80 μs
tx_power=60, # Transmit power: 60 dBm (1 kW)
prp=100e-6, # Pulse repetition period: 100 μs
pulses=1, # Single chirp (no Doppler processing)
channels=[
dict(
location=(0, 0, 0), # Antenna at origin
)
],
)
# Configure transmitter with ideal linear chirp
tx_linear = Transmitter(
f=freq_linear, # Linear chirp: 2-point ideal frequency sweep
t=t_linear, # Time array: [0, 80] μs
tx_power=60, # Transmit power: 60 dBm (1 kW, same as non-linear)
prp=100e-6, # Pulse repetition period: 100 μs
pulses=1, # Single chirp (same configuration)
channels=[
dict(
location=(0, 0, 0), # Antenna at origin (same location)
)
],
)
Receiver Configuration¶
Identical receiver used for both radar systems.
| Parameter | Value | Description |
|---|---|---|
| Sampling Rate | 2 MHz | Maximum range ~240 m |
| Noise Figure | 12 dB | Standard performance |
| RF Gain | 20 dB | LNA amplification |
| Baseband Gain | 30 dB | Total: 50 dB |
| Load Resistor | 500 Ω |
# Configure receiver (same for both radars)
rx = Receiver(
fs=2e6, # Sampling rate: 2 MHz
noise_figure=12, # Noise figure: 12 dB
rf_gain=20, # RF gain: 20 dB (LNA)
load_resistor=500, # Load resistance: 500 Ω
baseband_gain=30, # Baseband gain: 30 dB
channels=[
dict(
location=(0, 0, 0), # Receiver at origin (monostatic)
)
],
)
Create Radar Systems¶
Combine transmitters and receiver to form two complete FMCW radar systems for comparison.
# Create FMCW radar with non-linear chirp
radar_nonlinear = Radar(transmitter=tx_nonlinear, receiver=rx)
# Create FMCW radar with ideal linear chirp (reference)
radar_linear = Radar(transmitter=tx_linear, receiver=rx)
Target Configuration¶
Three targets at different ranges demonstrate that non-linearity effects increase with range—errors accumulate over longer round-trip delays.
| Target | Location (m) | Velocity (m/s) | RCS (dBsm) | Purpose |
|---|---|---|---|---|
| 1 (far) | (200, 0, 0) | (-5, 0, 0) | 30 | Maximum non-linearity impact |
| 2 (medium) | (95, 20, 0) | (-50, 0, 0) | 25 | Intermediate degradation |
| 3 (near) | (30, -5, 0) | (-22, 0, 0) | 15 | Minimal impact (reference) |
# Configure Target 1: Far range, large RCS (worst non-linearity effect)
target_1 = dict(
location=(200, 0, 0), # Position: 200m range (far)
speed=(-5, 0, 0), # Velocity: -5 m/s (18 km/h approaching)
rcs=30, # Radar cross section: 30 dBsm (large vehicle/truck)
phase=0, # Initial phase: 0 degrees
)
# Configure Target 2: Medium range, medium RCS (moderate non-linearity effect)
target_2 = dict(
location=(95, 20, 0), # Position: ~97m range (medium)
speed=(-50, 0, 0), # Velocity: -50 m/s (180 km/h approaching)
rcs=25, # Radar cross section: 25 dBsm (car)
phase=0, # Initial phase: 0 degrees
)
# Configure Target 3: Near range, small RCS (minimal non-linearity effect)
target_3 = dict(
location=(30, -5, 0), # Position: ~30m range (near)
speed=(-22, 0, 0), # Velocity: -22 m/s (79 km/h approaching)
rcs=15, # Radar cross section: 15 dBsm (motorcycle)
phase=0, # Initial phase: 0 degrees
)
# Combine targets for simulation
targets = [target_1, target_2, target_3]
Simulate Baseband Signals¶
Both simulations use the same targets, receiver, and noise—only the chirp waveform differs.
Output Structure: [channels, pulses, samples] = [1, 1, ~160] (2 MHz × 80 μs)
# Import radar simulator
from radarsimpy.simulator import sim_radar
# Simulate radar with non-linear chirp
data_nonlinear = sim_radar(radar_nonlinear, targets)
timestamp_nonlinear = data_nonlinear["timestamp"] # Time axis
baseband_nonlinear = data_nonlinear["baseband"] + data_nonlinear["noise"] # I/Q + noise
# Simulate radar with ideal linear chirp (reference)
data_linear = sim_radar(radar_linear, targets)
timestamp_linear = data_linear["timestamp"] # Time axis
baseband_linear = data_linear["baseband"] + data_linear["noise"] # I/Q + noise
Radar Signal Processing¶
Apply range FFT with Chebyshev windowing (60 dB sidelobes) to both baseband signals. The same window is applied to both for a fair comparison.
# Import signal processing modules
from scipy import signal
import radarsimpy.processing as proc
# Create Chebyshev window for range FFT (60 dB sidelobe suppression)
# Use linear radar sample count (both should be identical)
range_window = signal.windows.chebwin(
radar_linear.sample_prop["samples_per_pulse"], at=60
)
# Perform range FFT on non-linear chirp baseband
range_profile_nonlinear = proc.range_fft(baseband_nonlinear[:, :, :], range_window)
# Perform range FFT on linear chirp baseband (reference)
range_profile_linear = proc.range_fft(baseband_linear[:, :, :], range_window)
Visualize Range Profile Comparison¶
Overlay both range profiles to show non-linearity impact at different ranges:
- Near target (30 m): Both curves nearly identical — minimal non-linearity effect
- Medium target (95 m): Non-linear shows slight peak broadening
- Far target (200 m): Significant broadening and SNR loss with non-linear chirp
# Calculate maximum unambiguous range and create range axis
max_range = (
3e8
* radar_linear.radar_prop["receiver"].bb_prop["fs"]
* radar_linear.radar_prop["transmitter"].waveform_prop["pulse_length"]
/ radar_linear.radar_prop["transmitter"].waveform_prop["bandwidth"]
/ 2
)
range_axis = np.linspace(
0, max_range, radar_linear.sample_prop["samples_per_pulse"], endpoint=False
)
# Create comparison figure
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=range_axis,
y=20 * np.log10(np.abs(range_profile_nonlinear[0, 0, :])),
name="Non-linear chirp",
)
)
fig.add_trace(
go.Scatter(
x=range_axis,
y=20 * np.log10(np.abs(range_profile_linear[0, 0, :])),
name="Linear chirp (ideal)",
)
)
fig.add_annotation(
text="Far target (200m):<br>Significant broadening<br>and SNR loss",
x=200, y=20,
showarrow=True,
arrowhead=2,
arrowcolor="red",
ax=-80, ay=-40,
bgcolor="rgba(255,200,200,0.8)",
bordercolor="red",
)
fig.add_annotation(
text="Near target (30m):<br>Minimal difference",
x=30, y=20,
showarrow=True,
arrowhead=2,
arrowcolor="green",
ax=60, ay=-60,
bgcolor="rgba(200,255,200,0.8)",
bordercolor="green",
)
fig.update_layout(
title="Range Profile Comparison: Non-Linearity Effects vs. Range",
yaxis=dict(title="Amplitude (dB)", gridcolor='lightgray'),
xaxis=dict(title="Range (m)", gridcolor='lightgray'),
height=600,
)
show(fig)
Analysis: Non-Linearity Impact¶
The range profile comparison illustrates several key effects:
Range-dependent degradation: Non-linearity errors integrate over the round-trip delay, so far targets accumulate more phase error than near targets.
Peak broadening: Far targets show significantly wider peaks with the non-linear chirp, reducing the ability to separate closely-spaced targets.
SNR degradation: Signal energy spreads across multiple range bins, lowering the peak amplitude and raising detection thresholds at far ranges.
Sidelobe increase: Non-ideal frequency progression increases spectral leakage, which can create false targets near strong reflectors.
Engineering implications: Tight linearity specifications are essential for long-range FMCW operation, justifying investment in high-quality synthesizers, PLL optimization, and chirp calibration.
Summary¶
This example demonstrated chirp non-linearity effects in FMCW radar:
- Non-linearity $\epsilon(t)$ from PLL/VCO imperfections distorts the ideal linear sweep
- Arbitrary waveforms in RadarSimPy allow simulation of real measured chirps
- Range-dependent degradation — near targets are minimally affected; far targets show significant broadening and SNR loss
- Peak broadening reduces range resolution and target separation capability
- Sidelobe increase raises the risk of false targets near strong reflectors
- Mitigation requires hardware pre-distortion, PLL optimization, or signal-processing-based chirp correction
Things to Try¶
| Experiment | What to Change | What to Observe |
|---|---|---|
| Non-linearity severity | Amplify freq_nonlinear deviations |
Resolution and SNR vs. error magnitude |
| Bandwidth | Change BW: 50, 200, 500 MHz | Linearity vs. resolution trade-off |
| Chirp duration | Vary: 40, 80, 160 μs | PLL bandwidth requirements |
| More range points | Add targets at 50, 100, 150 m | Range vs. degradation curve |
| Target separation | Two targets 5 m apart at 200 m | Resolution limits with non-linearity |
| Window functions | Rectangular, Hamming, Blackman | Sidelobe vs. resolution trade-off |
| Phase correction | Apply polynomial fit to freq_nonlinear |
Effectiveness of compensation |
| Quantitative analysis | Measure 3-dB widths and SNR loss | Numeric characterization of degradation |