Home: Planar PCB Radio Filters

*More* Experiments in Open Loop Resonator Bandpass Filters

Now with tunability.

I've done open loop bandpass filters before but my prior design had no way to tune anything. It was just one band per design.

This is my attempt to implement a tunable radio frequency bandpass filter based off a paper I read. It uses planar PCB resonators that are tuned by a "digitally tunable capacitor" IC controlled by an esp8266 microcontroller.

I'm basically implementing the paper, "A UHF Third Order 5-bit Digital Tunable Bandpass Filter Based on Mixed Coupled Open Ring Resonators" by Ming-Ye Fu, Qian-Yin Xiang, Dan Zhang, Deng-Yao Tian, and Quan-Yuan Feng. But I translated the intent of the design from 0.8mm expensive (Er 2.65) PCB material to cheap 1.6mm thick FR4 PCB (Er 4.4) and I'm rolling my own microcontroller setup since theirs is not described.

[comment on this post] Append "/@say/your message here" to the URL in the location bar and hit enter.

[webmention/pingback] Did you respond to this URL? What's your URL?

Open Loop Resonators

In open loop resonators very close to each other the type of coupling, either electric of magnetic, is a function of the location on the loop(s). At resonance the maximum electrical field density is across the gap and the maximum magnetic field density at the opposite side. So for magnetic coupling they are put "back" to "back" and for electric "face" to "face". By turning them so neither the max electric nor max magnetic field densities are close to the other the contribution of electric and magnetic coupling is about the same. The balance can also be altered by displacing them out of alignment. This is called mixed coupling.

In this particular design RF energy takes two paths using the same start.

Digital Cap Tuning

The tunability comes from using some really neat Peregrine Semiconductor IC called the PE64904; a "Digitally Tunable Capacitor" that works from 100-3000 MHz and spans 0.6 to 4.6 pF. This IC is put in the open end of the open ring resonators to tune them. It is controlled over the 3-wire SPI protocol which necessitates a microcontroller of some sort. I've chosen to use the cheap esp8266 series esp12e boards (~$5) because they come with wifi and I already have experience with them.

The commands it takes are very simple. You pull the chip select (CS) line low then send a message containing a hex number from 0 to 31 and return the CS high.

You may be thinking, "Why not use varactors instead? Then you don't need a computer you just need a variable voltage. It's much simpler." And it is in an absolute way. But the complexity of using a digital computer to control things is something I'm used to while designing a variable 0-30 voltage source that's precise and repeatable is not. And most importantly: the guys in the paper did it this way.

Simulation and Design

Below is the design as I implemented it on 1.6mm 4.4 dielectric FR4. For scale, the input port microstrip is 2.5mm wide (~55 Ohm). I compromised on a grid size of 0.125 mm and this led to a sim time of about 5 minutes (i5 3750K processor). The granularity of the grid to get this decent iteration time made directly copying dimensions infeasible even if they hadn't used 0.8 mm 2.65 dielectric F4B-2 substrate.

I ended up spending a bit of time using an online microstrip electrical length/phase calculator to translate the intent of the shapes to my different PCB material. And then a bit of fiddling back and forth with values when it didn't quite work.

The little square pad with a via in it is for the PE64904 footpad ground. The two 0.75mm bits at the throat of the open loop are also to conform to the IC pads. I also tried adding the other IC footprint traces but the distance between them was very small and when I tried approximations with just one it didn't have much effect.

I set all the capacitors to the same variable, Caps, then did a parameter sweep of Caps from 0.6 to 4.6 mm at step size of 0.125 pF which is 32 steps and matches the PE64904 step size of 0.13 pF. Every other step is shown in the scattering parameters plot (0.25 pF step, 16 total).

The results matched the response reported in the paper pretty darn well.

Figuring out what controls what with parameter sweeps.

I ran some parameter sweeps of the various heights and gaps in the design to see what effected what.

Spacing between top capacitor patches

First I tried the spacing between the two capacitors up top. This did not effect the S11/return loss in any significant way. But it did have a significant effect on S21/insertion loss curves. Nominal spacing was 0.5mm and I covered 0.125 to 1 in 0.125 mm steps. With lower spacing (higher coupling) the 'zero' provided by destructive interference along this path shifted down in frequency towards the resonant freq. With higher spacing (lower coupling) the 'zero' moved up in frequency relative to the resonant freq. Over the parameter span the lower stopband insertion loss moved up and down by about 10 dB and the upper stopband by almost 20 dB in parts.

Vertical height of the top capacitor patches

Optimal seems to be about 8.5-9.0 mm based on return loss (S11). The effect is less important on insertion loss (S21) but with increasing cap height (and so capacitance) the lower stopband drops off more slowly after -15dB down. With decreasing cap height the upper stopband pops back up higher after the high side zero but again only when it's already below -25 dB so not that important.

Total height/aspect ratio of the loops and length of coupling lines

Then I tried altering the height of all the open loop resonators. This had a dramatic effect with a cliff in performance change after just a couple mm reduction in height. So resonator height/aspect ratio matters *a* lot. To be clear, the "height" parameter moved all the tops of the loops, the caps, and the coupling lines together. It kept their relative spacings exactly the same.

But maybe I did the scaling wrong and forgot to click some edge vertices or something. That would explain the sudden change if something began overlapping. I'll have to do this sweep again manually.

Resonator 2 side coupling gaps

Resonator 2 Height Offset

The results of shifting resonator 2's offset from the others was not very dramatic. There was little effect so I'm not going to put the plot up here.

Improved tuning range, higher freq

Some not so small tweaks in total height and coupling gaps eventually lead towards a version of the filter that would perform okay at 902-928 MHz. The total effective range is about 400 to 1000 MHz now but with some marginal performance on the high end (ie, -10dB S11 abd -3dB S21 for 915 MHz).

The large surface area shared between the metallic box and the PCB top metallization creates a significant paracitic capacitance. The closer the PCB metallization is to the conductive box ground the lower the impedance. This effect can be seen in the shift of the high frequency behavior of the transmission loss and in the decrease of the impedance of the microstrip ports from ~55 to ~52 Ohm.

Oops. My vias were going to the ceiling.

It turns out I accidentally set a config preference to send vias up by default instead of down a layer or down to ground. That meant in a lot of the filters I designed recently were not completely realistic. So I had to go back and re-make them to tune them for performance with the proper vias and not some wire going 10mm up to the box walls.

Balancing the resonators

It was actually easier to tune the open loop resonators with the vias going to the right place. I made a series of single parameter sweeps tweaking the line widths and gaps to see how the 3 dip peaks make up a single S11 notch moved. The balance of these was an S11 sweep with two resonances at either end of the tunable frequency range. At the high frequency end the contribution of one of the resonances is discarded to make the other two line up better.

The pass band is made asymmetric but this offsets for the increase relative bandwidth of the resonators with higher frequency. Instead only two peaks contribute and it's narrower but kind of shouldery and ugly. At the higher end this makes it so there are gaps between -15 to -15 db S11 passbands per DTC capacitance step.

The tradeoff is between having the small gaps in perfect passband coverage at the high end and the extended frequency range and better S11 the from overlaping only the 2 higher frequency dip peaks at higher frequencies. It'd be nice to get all 3 lined up at both low and high frequency ends but I can't figure that out.

The end result is pretty good, I think. It uses high 1cm box ceilings which I'll have to make myself out of tin rather than use an RF shield can. The final filter has a *simulated* range of 410-1000 MHz -15 db reflection loss and 1.5~3.25 dB insertion loss over that span.

The arrows on the vias now point down.

esp8266 Hardware and Software

esp8266 using nodemcu lua: software for 3-wire SPI control over wifi+tcp socket.

This lua code connects to to a hard coded wifi ssid then once it has an IP it connects to a TCP server running on a hardcoded IP and port. From there it waits for text instructions. On receiving anything in the form of a number from 0 to 31 it converts it to hex and sends it out the configured HSPI port to the digitally tunable capacitor.

Normally GPIO15 (pin D8) is used as a SPI 'Chip Select' line. But this requires adding external resistors in order to not trigger some boot issue. Additionally people on the forums have reported HSPI CS not working with NodeMCU. So since the PE64904 only requires 3 wires I instead disable the HSPI CS (GPIO15/D8) and the HSPI MOSI (GPIO13/D7) by setting their mode back to normal GPIO. Then I use the HSPI MOSI pin as a GPIO and manually pull it low as a chip select command before spi.send() then manually set it high after.

For a server on the computer it connects to I use use netcat like,

nc -l 6005

and then type, say, 15 and hit enter. If it works it responds "ok".

-- esp8266 mediated wifi:telnet control of PE64904 SPI based digitally tuned capacitor.

-- Config				esp12e		PE64904
-- -- Default HSPI for CLK and MISO
-- local CLK = 5           	 --> GPIO14 D5 | MISO, PIN 7 (SDA)
-- local MISO = 6            	 --> GPIO12 D6 | SCL,  PIN 5 (SCL)
-- -- Manual CS/SEN for chip select
local CS = 7              	 --> GPIO13 D7 | SEN,  PIN 6 (SEN)
local duration = 3000     --> 3 seconds
local i = 0
local result = 0

-- Turn on HSPI (dev 1) SPI at (80 MHz/8) 10 MHz
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)

-- Manual CS Pin (instead of HSPI CS)
gpio.mode(CS, gpio.OUTPUT)
gpio.write(CS, gpio.HIGH)

-- Need to disable HSPI CS D8/GPIO15 from being toggled. 
-- Set pull-up to prevent potential issues.
-- ref: https://nodemcu.readthedocs.io/en/master/en/modules/spi/#spisetup
gpio.mode(8, gpio.INPUT, gpio.PULLUP)

function string.fromhex(str)
	return (str:gsub('..', function (cc)
		return string.char(tonumber(cc, 16))
function string.tohex(str)
	return (str:gsub('.', function (c)
		return string.format('%02X', string.byte(c))

-- Connect to wifi

-- Connect to control server
local flagClientTcpConnected=false;
print("Start TCP Client");
tmr.alarm(3, 5000, 1, function()
	if flagClientTcpConnected==false then
		print("Trying to connect to server");
		local conn=net.createConnection(net.TCP, false) 
			print("TCPClient:connected to server");
			flagClientTcpConnected = true;
			flagClientTcpConnected = false;
		conn:on("receive", function(conn, m) 
			for i = 0,31 do
				if string==i then
					hexval = i.tohex()
					gpio.write(CS, gpio.LOW)      -->Activate the SPI chip
              				tmr.delay(1)                  -->1us Delay
					wrote = spi.send(1,hexval)    -->Write hex value
					gpio.write(CS,gpio.HIGH)      -->De-activate the SPI chip
					print("Started SPI DTC Control");

For the init.lua file I use one I cribbed from the DoitCar demo from www.doit.am bbs.doit.am. Change "spiviaweb-nogpio15-v1.lua" to whatever matches.

--DoitCar Ctronl Demo
--sta mode
--Created @ 2015/05/14 by Doit Studio
--Modified: null
--bbs: bbs.doit.am

print("ESP8266 Started")

local exefile="sta"
local luaFile = {exefile..".lua","spiviaweb-nogpio15-v1.lua"}
for i, f in ipairs(luaFile) do
	if file.open(f) then
      print("Compile File:"..f)
	  print("Remove File:"..f)

if file.open(exefile..".lc") then
	print(exefile..".lc not exist")
exefile=nil;luaFile = nil