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 examinedPolar 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
The UWB monopole: radiating patch (front), feed line and the ground plane on the back of the substrate, with the graded mesh (AppCSXCAD)
Reflection coefficient and phase for UWB channel 4
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.