UWB Radar — Delay and Fidelity

  • A simple UWB monopole characterised in the time domain: the delay from the source to the phase centre, and the fidelity — how similar the radiated waveform stays to the excitation.

Introduction

This tutorial covers:

  • Gaussian excitation matched to the IEEE 802.15.4 UWB channel bandwidths (channels 1-5 and 7), selectable at the top of the script

  • Graded meshing: fine resolution around the antenna, coarse in free space

  • openEMS.utilities.DelayFidelity() for time-domain pulse characterisation, with complex weights on E_theta and E_phi so any polarisation can be examined

  • Polar plots of delay (in mm) and fidelity (in %) against angle

  • Gain and radiation efficiency from the NF2FF box

Python Script

Get the latest version from git.

Import Libraries

import os, tempfile
import numpy as np
import matplotlib.pyplot as plt  # pip install matplotlib

from CSXCAD  import ContinuousStructure
from openEMS import openEMS
from openEMS.physical_constants import *
from openEMS.utilities import DelayFidelity
from openEMS.automesh import mesh_hint_from_box

Channel Configuration

Pick one channel. f_c converts the IEEE 802.15.4 20 dB bandwidth to the Gaussian 3 dB bandwidth: 3 dB is 0.3925 times the 20 dB bandwidth. ‘channel4twice’ is only there to show the fidelity degrading with bandwidth.

CHANNELS = {
    'channel1'    : (3.5e9, 0.25e9/0.3925),
    'channel2'    : (4.0e9, 0.25e9/0.3925),
    'channel3'    : (4.5e9, 0.25e9/0.3925),
    'channel4'    : (4.0e9, 0.50e9/0.3925),
    'channel5'    : (6.5e9, 0.25e9/0.3925),
    'channel7'    : (6.5e9, 0.50e9/0.3925),
    'channel4twice': (4.0e9, 1.00e9/0.3925),
}
suffix    = 'channel4'
f_0, f_c  = CHANNELS[suffix]

# polarization tilt angle against co-polarization (90 deg is cross polarized)
tilt = 45*np.pi/180

Antenna and Substrate Parameters

A planar monopole on FR4 (eps_r = 4). The gap between the feed line and the patch sets the impedance match; patchsize is the monopole length.

Sim_Path = os.path.join(tempfile.gettempdir(), 'UWB_' + suffix)
print(f'{Sim_Path=}')

post_proc_only = False
unit = 1e-3     # we will use millimeters

substrate_epsR   = 4      # FR4
substrate_height = 0.707
substrate_cells  = 3      # thickness in cells

gap       = 0.62          # gap between feed line and patch
patchsize = 14

# resolution for the finer structures, e.g. the antenna gap
fineResolution   = C0/(f_0 + f_c)/np.sqrt(substrate_epsR)/unit/40
# resolution for the coarser structures, e.g. the surrounding air
coarseResolution = C0/(f_0 + f_c)/unit/20

FDTD Configuration

OverSampling raises the time-domain sample rate well above Nyquist, which is what sets the delay resolution of DelayFidelity. All six faces are PML.

FDTD = openEMS(NrTS=30000, EndCriteria=1e-5, OverSampling=20)
FDTD.SetGaussExcite(f_0, f_c)
FDTD.SetBoundaryCond(['PML_8']*6)

CSX = ContinuousStructure()
FDTD.SetCSX(CSX)
mesh = CSX.GetGrid()
mesh.SetDeltaUnit(unit)

Geometry Setup

Sheet-like primitives for ground, feed line and patch on a substrate block.

ground    = CSX.AddMetal('Ground')
patch     = CSX.AddMetal('Patch')
line      = CSX.AddMetal('Line')
substrate = CSX.AddMaterial('Substrate', epsilon=substrate_epsR)

substrate.AddBox([-16, -16, -substrate_height], [16, 18, 0], priority=1)
ground.AddBox([-16, -16, -substrate_height], [16, 0, -substrate_height], priority=2)
line_box  = line.AddBox([-1.15, -16, 0], [1.15, gap, 0], priority=2)
patch.AddBox([-patchsize/2, gap, 0], [patchsize/2, gap + patchsize, 0], priority=2)

Mesh

Metal edges are added with the 1/3-2/3 rule, then the mesh is graded from the fine resolution at the antenna out to the coarse one in free space.

# two mesh lines for the metal coatings of the substrate
mesh.SetLines('z', np.linspace(-substrate_height, 0, substrate_cells + 1))

# edges of the patch and the ground plane, but not yet the microstrip line
FDTD.AddEdges2Grid('xy', properties=patch, metal_edge_res=fineResolution/2)
FDTD.AddEdges2Grid('xy', properties=ground, metal_edge_res=fineResolution/2)

# replace gap mesh lines which are too close by a single mesh line
y = mesh.GetLines('y')
tooclose = np.where(np.diff(y) < fineResolution/4)[0]
if len(tooclose):
    y[tooclose] = (y[tooclose] + y[tooclose+1])/2
    mesh.SetLines('y', np.delete(y, tooclose+1))

# only the x edges of the microstrip, and only the top of the substrate --
# the other substrate edges are covered by the ground plane
FDTD.AddEdges2Grid('x', primitives=line_box, metal_edge_res=fineResolution/2)
mesh.AddLine('y', 18)   # top of the substrate

# so far only edges: now fill in between
mesh.SmoothMeshLines('all', fineResolution)

# add the outer boundary and the coarse free-space lines
mesh.AddLine('x', [-60, 60])
mesh.AddLine('y', [-60, 65])
mesh.AddLine('z', [-46, 45])
mesh.SmoothMeshLines('all', coarseResolution)

Feeding port and NF2FF box

The port sits at the outer end of the microstrip, where it reaches the edge of the board: the second y edge line of the feed line, i.e. just inside it.

line_hint = mesh_hint_from_box(line_box, 'y', metal_edge_res=fineResolution/2)
feed_y    = sorted(line_hint[1])[1]
y_lines   = mesh.GetLines('y')
feed_y    = y_lines[np.argmin(np.abs(y_lines - feed_y))]   # snap to a mesh line
port = FDTD.AddLumpedPort(1, 50, [-1.15, feed_y, -substrate_height],
                          [1.15, feed_y, 0], 'z', 1.0, priority=999)

x_l, y_l, z_l = (mesh.GetLines(n) for n in 'xyz')
nf2ff = FDTD.CreateNF2FFBox('nf2ff', [x_l[9], y_l[9], z_l[9]],
                            [x_l[-10], y_l[-10], z_l[-10]])

Run the Simulation

if 0:  # debugging only
    CSX_file = os.path.join(Sim_Path, 'uwb.xml')
    if not os.path.exists(Sim_Path):
        os.makedirs(Sim_Path)
    CSX.Write2XML(CSX_file)
    from CSXCAD import AppCSXCAD_BIN
    os.system(AppCSXCAD_BIN + ' "{}"'.format(CSX_file))

if not post_proc_only:
    FDTD.Run(Sim_Path, cleanup=True)

Post-Processing

Reflection coefficient, then delay and fidelity over an azimuthal trace.

freq = np.linspace(f_0 - f_c, f_0 + f_c, 200)
port.CalcPort(Sim_Path, freq)
s11 = port.uf_ref/port.uf_inc
s11phase = np.unwrap(np.angle(s11))

fig, axis = plt.subplots(num="S11", tight_layout=True)
axis.plot(freq/1e6, 20*np.log10(np.abs(s11)), 'k-', linewidth=2)
axis.set_xlabel('frequency f / MHz')
axis.set_ylabel('reflection coefficient $|S_{11}|$ (dB)')
axis.grid()
axis.set_xmargin(0)
axis2 = axis.twinx()
axis2.plot(freq/1e6, s11phase, 'r--', linewidth=2)
axis2.set_ylabel('$S_{11}$ phase (rad)', color='r')
axis2.tick_params(axis='y', colors='r')
axis.set_title(f'reflection coefficient {suffix} $S_{{11}}$')

Delay and fidelity around the monopole

theta = np.arange(-180, 181, 10)
phi   = [0]
delay, fidelity, res = DelayFidelity(nf2ff, port, Sim_Path,
                                     np.sin(tilt), np.cos(tilt), theta, phi,
                                     f_0, f_c, verbose=1)

# gain at (close to) f_0: turn the directivity into gain
f_idx = int(np.argmin(np.abs(np.asarray(res.freq) - f_0)))
port.CalcPort(Sim_Path, res.freq[f_idx])
gain = res.Dmax[f_idx]*res.Prad[f_idx]/port.P_inc[0]
print(f'gain at {res.freq[f_idx]/1e9:.2f} GHz: {10*np.log10(gain):.2f} dBi')

th = theta/180*np.pi
fig = plt.figure(num="Delay and fidelity", figsize=(11, 5), tight_layout=True)

ax = fig.add_subplot(121, polar=True)
ax.plot(th, delay[:, 0]*C0*1000, 'k-', linewidth=2)
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_title(f'delay {suffix} / mm')

ax = fig.add_subplot(122, polar=True)
ax.plot(th, fidelity[:, 0]*100, 'r-', linewidth=2)
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_rlim([98, 100])
ax.set_title(f'fidelity {suffix} / %')

plt.show()

Images

3D view of the UWB monopole antenna

The UWB monopole: radiating patch (front), feed line and the ground plane on the back of the substrate, with the graded mesh (AppCSXCAD)

S11 magnitude and phase over frequency

Reflection coefficient and phase for UWB channel 4

Delay and fidelity polar plots

Delay to the phase centre and fidelity of the radiated pulse against angle, for UWB channel 4

Notes

The delay resolution is set by the excitation bandwidth and the OverSampling parameter, and is printed by DelayFidelity. The delay is reported as a length (delay times c0) so it can be compared directly with the antenna dimensions.

See also

The same tutorial for the Octave/Matlab interface.