Less of a project than a collection of blog posts
Adobe Portable Document Format - 189.33 kB - 05/20/2019 at 17:25
I started with an ambitious project, optimizing an SMA connector footprint, but had to scale back to get a better understanding of how this all worked. It turns out that a lot of my experience in lower-frequency regimes and SPICE simulation can be fairly misleading :-)
The simulation shown in the video is of a 1-inch piece of microstrip 109 mils wide on a 2-layer PCB with 60 mil FR-4 - it's the OSH Park 2-layer stackup. There are two 50-ohm ports, one on each end. A 20-ps rise-time Gaussian step is applied to the input port. The animation is of the magnitude of the electric field in the FR-4 substrate.
You can see apparent overshoot in both ports, then later a reflection arrives back at the input port. It turns out that these overshoots are a result of exciting high-order propagation modes in the microstrip. We tend to think of microstrip as a type of "flat coax" which operates in TEM, but that's not right, I have come to find out. It turns out that because the dielectric is only on one side of the conductors, microstrip supports other transmission modes , which can be especially troublesome as the thickness of the dielectric approaches a significant fraction of a wavelength.
The problem here is that the 20-ps rise-time edge has frequencies high enough to excite these other modes. If you drop the rise-time down to a more reasonable value of 70 ps, for example, you get the following:
Much better. You can still see the effect of a reflection in the input port trace. This is a reflection from the output port that has made its way back to the input port. More problematic than the reflection itself is the different levels before and after the reflection. This is caused by the fact that the impedance of the trace isn't exactly 50 Ohms. Interestingly, this 109-mil wide trace was created by the PCB calculator that ships with KiCad. It's not quite right.
After some experimentation, I ended up with a 117-mil wide trace. You can see the difference in this plot, where there still is a reflection off the output port, but the levels are the same before and after, indicating a good impedance match.
The fact that the reflection is still present is an artifact of the port geometry. In this case, the ports are simply 50-Ohm resistors as wide as the trace on either end and connected, through the substrate, to the ground plane. This isn't a perfect termination, and creates reflections. There are some improved terminations in the literature, but none of them looks particularly easy to implement in this case.
I may experiment a little with some improved terminations before continuing with more complex models. It would be useful to have a good one available. It would also be useful to simulate some real-world terminations to see how they perform. I have an intentional AC-short termination in one of the designs in an earlier log, and it would be interesting to see how it could be improved.
At least I got something going. Next, I may move to a coplanar-waveguide structure. I don't know how to connect the top and bottom ground planes to get a good launch, so some experimentation will be in order.
I'd also like to experiment with adding soldermask. Unfortunately, I'm not sure how to make a conformal coat easily - although I have a few ideas. It seems like it's worth trying, if only to simulate and verify the results I saw on real PCBs.
openEMS doesn't have a built-in facility for creating Gaussian steps. Most of the analysis I have seen uses frequency-domain techniques, which have their place, but sometimes a time-domain analysis approach is warranted. So, I wrote some code to approximate a Gaussian step function using the built-in facilities of openEMS.
Note that I haven't added this to the openEMS code; it just uses the existing functions. I don't feel comfortable modifying GPL-licensed code. Unfortunately, it took some doing to approximate this function using just those facilities already available in openEMS. But it's done and now available for others to use.
I've documented the derivation of this function in a short PDF document, mostly because it will be easier to find than the dozen or so pages of notes and rambling calculations it took to get here. If you're at all interested, that note gives much more detail than this build log.
openEMS provides a SetCustomExcite() function to allow the user to pass a string representing an excitation function to the FDTD engine, and you can use this to generate a Gaussian step excitation. I'll be posting a log soon to show how it can be used, and why you would choose it over the built-in unit step excitation SetStepExcite().
Here's the octave function to create the step approximation, and return the required Nyquist sampling frequency. Yeah, it's a mess because it has to generate a string representation of the function.
function [string, f_nyquist] = GaussianStep(rise_time, center_time, dB_cutoff) C = center_time; sigma = rise_time / (2*erfinv(0.8)); K = 1/sigma; f_nyquist = 2*sqrt(dB_cutoff/20 * log(10) / (pi*pi*sigma*sigma)); % % Gaussian step using erf() approximation 7.1.25 from Abramowitz and Stegun % http://people.math.sfu.ca/~cbm/aands/page_299.htm % string = ['0.5+0.5*(', num2str(K), '*(t-', num2str(C), ')>=0)*(1 - exp(-', num2str(K), ... '*(t-', num2str(C), ')*', num2str(K), '*(t-', num2str(C), '))*(0.3480242/(1+0.47047*', ... num2str(K), '*(t-', num2str(C), ')) - 0.0958798/((1+0.47047*', num2str(K), '*(t-', ... num2str(C), '))*(1+0.47047*', num2str(K), '*(t-', num2str(C), ... '))) + 0.7478556/((1+0.47047*', num2str(K), '*(t-', num2str(C), '))*(1+0.47047*', ... num2str(K), '*(t-', num2str(C), '))*(1+0.47047*', num2str(K), '*(t-', num2str(C), ... ')))))-0.5*(', num2str(K), '*(t-', num2str(C), ')<0)*(1 - exp(-', num2str(K), '*(t-', ... num2str(C), ')*', num2str(K), '*(t-', num2str(C), '))*(0.3480242/(1-0.47047*', ... num2str(K), '*(t-', num2str(C), ')) - 0.0958798/((1-0.47047*', num2str(K), '*(t-', ... num2str(C), '))*(1-0.47047*', num2str(K), '*(t-', num2str(C), ... '))) + 0.7478556/((1-0.47047*', num2str(K), '*(t-', num2str(C), '))*(1-0.47047*', ... num2str(K), '*(t-', num2str(C), '))*(1-0.47047*', num2str(K), '*(t-', num2str(C), ')))))']; end
Here's what I've found for links about starting with openEMS. I'll update this as I find more info.
The source code.
There's a ton of info in the posts, but it can be very difficult to find. For one thing, the search function doesn't allow searches like "Gaussian step" because the words are "too common".
The good thing about the forum is that Thorsten, who wrote the software, is active and answers questions. I haven't felt the need to bother him yet, but I'm getting there.
Very useful in some places; spotty and link-busted in others, this seems like the best starting point.
There are a lot of examples here. Mostly RF and antenna stuff, which isn't necessarily applicable to what I'm doing at the moment.
I almost gave up on openEMS because building it was a challenge at first. With enough digging through the forums and experimenting, I finally got it going. I present here the recipe for building on Linux Mint 19.1 - I've tried this on a fresh install in a VM, and it works. If all else fails on your platform of choice, you can always spin up a Mint VM yourself and work in there.
sudo apt-get install autogen autoconf libtool automake autotools-dev build-essential git cmake libhdf5-dev libvtk6-dev libboost-all-dev libcgal-dev libtinyxml-dev libqt4-dev libvtk6-qt-dev libhpdf-dev gengetopt git clone --recursive https://github.com/thliebig/openEMS-Project.git cd openEMS-Project/ ./update_openEMS.sh ~/opt/openEMS --with-hyp2mat --with-CTB --with-MPI
You will also need to install GNU Octave and ParaView, but those are in the package repos.