Helical Antenna

Introduction

This tutorial covers:

  • setup of a helix using the wire primitive

  • setup a lumped feeding port (R_in = 120 Ohms)

  • adding a near-field to far-field (nf2ff) box using an efficient subsampling

  • calculate the S-Parameter of the antenna

  • calculate and plot the far-field pattern

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 CSXCAD

from openEMS import openEMS
from openEMS.physical_constants import *

Setup the simulation

Sim_Path = os.path.join(tempfile.gettempdir(), 'Helical_Ant')
force_re_sim = False

unit = 1e-3 # all length in mm

f0 = 2.4e9 # center frequency, frequency of interest!
lambda0 = round(C0/f0/unit) # wavelength in mm
fc = 0.5e9 # 20 dB corner frequency

Helix_radius = 20 # --> diameter is ~ lambda/pi
Helix_turns = 10  # --> expected gain is G ~ 4 * 10 = 40 (16dBi)
Helix_pitch = 30  # --> pitch is ~ lambda/4
Helix_mesh_res = 3

gnd_radius = lambda0/2

# feeding
feed_heigth = 3
feed_R = 120    #feed impedance

# size of the simulation box
SimBox = np.array([1, 1, 1.5])*2.0*lambda0

Setup FDTD parameter & excitation function

FDTD = openEMS(EndCriteria=1e-4)
FDTD.SetGaussExcite( f0, fc )
FDTD.SetBoundaryCond( ['MUR', 'MUR', 'MUR', 'MUR', 'MUR', 'PML_8'] )

Setup Geometry & Mesh

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

max_res = np.floor(C0 / (f0+fc) / unit / 20) # cell size: lambda/20

# create helix mesh
mesh.AddLine('x', [-Helix_radius, 0, Helix_radius])
mesh.SmoothMeshLines('x', Helix_mesh_res)
# add the air-box
mesh.AddLine('x', [-SimBox[0]/2-gnd_radius,  SimBox[0]/2+gnd_radius])
# create a smooth mesh between specified fixed mesh lines
mesh.SmoothMeshLines('x', max_res, ratio=1.4)

# copy x-mesh to y-direction
mesh.SetLines('y', mesh.GetLines('x'))

# create helix mesh in z-direction
mesh.AddLine('z', [0, feed_heigth, Helix_turns*Helix_pitch+feed_heigth])
mesh.SmoothMeshLines('z', Helix_mesh_res)

# add the air-box
mesh.AddLine('z', [-SimBox[2]/2, max(mesh.GetLines('z'))+SimBox[2]/2 ])
# create a smooth mesh between specified fixed mesh lines
mesh.SmoothMeshLines('z', max_res, ratio=1.4)

Create the Geometry

  • Create the metal helix using the wire primitive.

  • Create a metal gorund plane as cylinder.

# create a perfect electric conductor (PEC)
helix_metal = CSX.AddMetal('helix' )

ang = np.linspace(0,2*np.pi,21)
coil_x = Helix_radius*np.cos(ang)
coil_y = Helix_radius*np.sin(ang)
coil_z = ang/2/np.pi*Helix_pitch

Helix_x=np.array([])
Helix_y=np.array([])
Helix_z=np.array([])
zpos = feed_heigth
for n in range(Helix_turns-1):
    Helix_x = np.r_[Helix_x, coil_x]
    Helix_y = np.r_[Helix_y, coil_y]
    Helix_z = np.r_[Helix_z ,coil_z+zpos]
    zpos = zpos + Helix_pitch

p = np.array([Helix_x, Helix_y, Helix_z])
helix_metal.AddCurve(p)

# create ground circular ground
gnd = CSX.AddMetal( 'gnd' ) # create a perfect electric conductor (PEC)

# add a box using cylindrical coordinates
start = [0, 0, -0.1]
stop  = [0, 0,  0.1]
gnd.AddCylinder(start, stop, radius=gnd_radius)

# apply the excitation & resist as a current source
start = [Helix_radius, 0, 0]
stop  = [Helix_radius, 0, feed_heigth]
port = FDTD.AddLumpedPort(1 ,feed_R, start, stop, 'z', 1.0, priority=5)

# nf2ff calc
nf2ff = FDTD.CreateNF2FFBox(opt_resolution=[lambda0/15]*3)

Run the simulation

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

if force_re_sim or not os.path.exists(os.path.join(Sim_Path, 'et')):
    FDTD.Run(Sim_Path, cleanup=True)

Postprocessing & plotting

freq = np.linspace( f0-fc, f0+fc, 501 )
port.CalcPort(Sim_Path, freq)

Zin = port.uf_tot / port.if_tot
s11 = port.uf_ref / port.uf_inc

Plot the feed point impedance

fig, axis = plt.subplots(num="Zin", tight_layout=True)
axis.plot(freq/1e6, np.real(Zin), 'k-',  linewidth=2, label='$\\Re(Z_{in})$')
axis.plot(freq/1e6, np.imag(Zin), 'r--', linewidth=2, label='$\\Im(Z_{in})$')
axis.grid()
axis.set_xmargin(0)
axis.set_xlabel('frequency (MHz)')
axis.set_ylabel('Zin (Ohm)')
axis.set_title("feed point impedance")
axis.legend()

Plot reflection coefficient S11

fig, axis = plt.subplots(num="S11", tight_layout=True)
axis.plot(freq/1e6, 20*np.log10(abs(s11)), 'k-',  linewidth=2)
axis.grid()
axis.set_xmargin(0)
axis.set_xlabel('frequency (MHz)')
axis.set_ylabel('S11 (dB)')
axis.set_title('reflection coefficient $S_{11}$' )

Create the NFFF contour

  • calculate the far field at phi=0 degrees and at phi=90 degrees

theta = np.arange(0.,180.,1.)
phi = np.arange(-180,180,2)
print( 'calculating the 3D far field...' )

nf2ff_res = nf2ff.CalcNF2FF(Sim_Path, f0, theta, phi, read_cached=True, verbose=True )

Dmax_dB = 10*np.log10(nf2ff_res.Dmax[0])
E_norm = 20.0*np.log10(nf2ff_res.E_norm[0]/np.max(nf2ff_res.E_norm[0])) + 10*np.log10(nf2ff_res.Dmax[0])

theta_HPBW = theta[ np.where(np.squeeze(E_norm[:,phi==0])<Dmax_dB-3)[0][0] ]
  • Display power and directivity

print('radiated power: Prad = {:.5g} W'.format(nf2ff_res.Prad[0]))
print('directivity: Dmax = {:.2f} dBi'.format(Dmax_dB))
print('efficiency: nu_rad = {:.1f} %'.format(100*nf2ff_res.Prad[0]/np.interp(f0, freq, port.P_acc)))
print('theta_HPBW = {:.1f} °'.format(theta_HPBW))

E_norm = 20.0*np.log10(nf2ff_res.E_norm[0]/np.max(nf2ff_res.E_norm[0])) + 10*np.log10(nf2ff_res.Dmax[0])
E_CPRH = 20.0*np.log10(np.abs(nf2ff_res.E_cprh[0])/np.max(nf2ff_res.E_norm[0])) + 10*np.log10(nf2ff_res.Dmax[0])
E_CPLH = 20.0*np.log10(np.abs(nf2ff_res.E_cplh[0])/np.max(nf2ff_res.E_norm[0])) + 10*np.log10(nf2ff_res.Dmax[0])
  • Plot the pattern

fig, axis = plt.subplots(num="Pattern", tight_layout=True)
axis.plot(theta, E_norm[:,phi==0], 'k-',  linewidth=2, label='|E total|')
axis.plot(theta, E_CPRH[:,phi==0], 'r--',  linewidth=2, label='|E CPRH|')
axis.plot(theta, E_CPLH[:,phi==0], 'g-.',  linewidth=2, label='|E CPLH|')
axis.grid()
axis.set_xmargin(0)
axis.set_xlabel('theta (deg)')
axis.set_ylabel('directivity (dBi)')
axis.set_title('Frequency: {:.2f} GHz'.format(nf2ff_res.freq[0]/1e9))
axis.legend()


# show all plots
plt.show()

Images

alternate text

3D view of the Helical Antenna (AppCSXCAD)

alternate text

Far-Field pattern showing a right-handed circular polarization.