Categories
General

Sagemath: LC bandpass

This time we used Sagemath to model an LC bandpass filter, centered on 100MHz, and calculate the insertion loss in dB at various frequencies. It’s based on what the filter calculator defaults to for a 1st order shunt-first Chebyshev bandpass filter.

# Complex impedance for capacitor and inductor
C(c,f) = -I / (2 * pi * f * c)
L(l,f) = I * 2 * pi * f * l

# Composition rules for impedance
recip(x) = 1 / x
ipar2(a,b) = recip( recip(a) + recip(b))
ipar3(a,b,c) = recip( recip(a) + recip(b) + recip(c))
iser(a,b) = a + b

# A simple RC lowpass filter
V = 10
Rs = 50 # source impedance
Rl = 50 # load impedance

# These are just the default values from https://rf-tools.com/lc-filter/
# They give a bandpass filter centered at 100MHz, with 20MHz bandwidth.
C1 = 48.58e-12
L1 = 52.67e-9

# This is the overall LC bandpass filter, with source impedance and load.
impedance = iser( Rs, ipar3(C(C1,f),L(L1,f),Rl) )

# The filter website shows insertion loss, which looks at how the power in the load
# is affected by putting in the LC filter.

power(v,r) = (v^2 / r).abs()

# Without our filter ("device under test"), the power is simple - half the voltage
# is dropped over the source impedance, leaving half for the load.
power_without_dut = power(V/2,Rl)

# To calculate power in load with our filter, we need to know the current
current = V / impedance
V_over_load = V - (current * Rs)
power_in_load = power(V_over_load, Rl)

as_dB(frac) = log(frac,10) * 10
insertion_loss_dB = as_dB(power_in_load / power_without_dut)

insertion_loss_dB(51750194).n()  # ans: -3.29183565814456
insertion_loss_dB(41896869).n()  # ans: -5.05202056128256

These values for insertion loss match what the calculator website shows in the “S parameters” tab.

How to think about this? If you take away the LC bit, you’re just left with a “maximum power theorem” situation, ie. source impedance and load impedance are both constant and equal.

Now, let’s introduce the LC again but imagine that there’s some magic circumstance in which it’s impedance ends up zero – meaning we’ll still be in “maximum power theorem” situation. Since the L and C are of “opposite nature”, there’ll be some frequency where the L has reactance-blah reactance at the same time as the C has negative-blah impedance. With the L and C in parallel we sum their impedances (actually, reciprocal of sum of reciprocals) so they cancel out.

In all other circumstances, either the L will “win” with a larger reactance or the C will “win” and we’ll net out to the L-par-C having some non-zero impedance. This will pull us away from our perfect 50ohm “maximum power” point and the power in the load will be less.

At low frequencies, the inductor (L) will present a low-impedance path, meaning that the overall impedance will be much lower than 50. This results in a higher current, a greater voltage drop over the source impedance leaving our parallel RLC bundle seeing a lower voltage.

At high frequencies, it’s the capacitor which presents a low-impedance path, and by the same argument, we get a lower voltage over our load.

But when we’re dealing with RF we’re dealing with waves, and when there’s a change in impedance you get reflections. If our bandpass filter only had 50ohms impedance at it’s centre frequency, then it has a non-50ohm impedance everywhere else – which causes reflections. Actually, this is kinda obvious from conservation of energy: if there’s energy coming in, but it’s not ending up in the load then either it’s absorbed in the filter itself or it must be reflected. If our filter was absorbing energy, it would be heating up (unlikely, since we build filters with low-resistance components), or it would have to be radiating (unlikely unless the filter was physically very big).

If we’re using a bandpass filter at the input of a receiver, all the signals outside the passband are getting bounced back towards the antenna (the amount of energy here is tiny so it’s not a problem).

If we use a lowpass filter to “get rid of” harmonics at the output of a transmitter, the energy of those harmonics is heading straight back towards the power amp.

If we use an IF bandpass filter in a superhet receiver, then those reflected signals will be heading back towards a mixer where they can re-mix and cause problems.

If we’re worried by those reflections, what can we do? The answer is to use a diplexer – essentially a fork in the road leading to a lowpass and highpass filters respectively. Whichever part (low or high frequencies) we don’t want can be sent to a 50ohm terminator to be absorbed and turned into heat – and overall the whole thing looks like 50ohms across all frequencies This is something I’ve seen used in the “High Performance Direct Conversion Receivers” ARRL article. Also in this “Popcorn DC Receiver”, this one and this one.

When learning about amateur radio, there’s loads of discussion about reflections that happen when the impedance of your antenna isn’t 50ohm at the frequency you’re sending on. This leads to reflections back up your 50ohm coax, and the sum of the outgoing and reflected wave leads to standing waves. The standing waves can be characterised by the “standing wave ratio” (SWR) which is a measurable quantity, and can therefore be used to indicate that your antenna impedance isn’t right. However, if your feeder cable is attenuating the waves, the reflected wave might be weak by the time it gets back to the home end of the cable – which could wrongly lead you to infer that you have the impedance spot on.

Despite all this chatter about reflections from the antenna, I have seen almost no mention of reflections from filtering steps. I guess it’s just a question of degree. Reflections from antennas are bad because its potentially the full transmit power at your transmit frequency that’s bouncing back. Your lowpass filter is also after the power amp, but in normal operation the reflected power is much small since it’s only the power from the harmonics that bounces back. If your power amp was very very nonlinear you might have more power in your harmonics. Or if you had been sending at 7MHz with a 8MHz lowpass filter, and then switched up to sending at 14MHz but didn’t change to an appropriate lowpass filter you’d end up getting all your transmit power bouncing back.

Categories
General

Spice vs Sagemath

I entered this particular rabbithole with a desire to understand how the various kinds of passive filters work. I’ve already physically constructed bandpass and lowpass filters, but I had to get the values for my capacitors and hand-wound inductors from a mysterious but helpful online filter calculator. Being a cynical kinda person, I first “built” each filter using ngspice (the circuit simulation tool) to check they looked sane before heating up my soldering iron.

But that online filter calculator is kinda mysterious. It lets you build various kinds of filters – Butterworth, Chebyshev, Elliptic – and each one comes in different flavours, such as shunt-first or series-first.

They’re all made from combinations of capacitors and inductors – the two standard frequency-dependent building blocks, which harness electric and magnetic fields respectively. I know how a capacitor behaves on its own, and I know how an inductor behaves on its own – but I don’t know what magic happens when you put a bunch of them together.

Buoyed by previous suggest in modelling pi-attenuators in Sagemath from first principles, I decided to go on a journey to also model various families of passive filters in Sagemath too. The hope is that I “tell” Sagemath about the axioms – what the impedance of each component is, and how series/parallel combinations work – along with the combinations of capacitors/inductors that make up a filter, and out should pop the shapes of whatever bandpass filter you like Of course, this is exactly what the SPICE simulator will do. But the attractive thing about Sagemath is that it’s tracking the entire equation relating everything, and hence you can ask many more questions of it.

I initially tried to dive straight in an model a 3rd order Chebyshev bandpass filter. I got kinda close, but couldn’t get to the point of having correct bandpass filter behaviour. So I took that as a hint that perhaps I should be starting with something simpler.

The simplest frequency-dependent circuit is an RC filter. I’ll use R=4700ohm and C=47nF. The resistor (with constant impedance) and capacitor (with impedance that decreases as frequency rises) act as a potential divider.

Now, a RC filter is simple enough that you can work it out by hand directly. Since I use emacs a lot, I often use emacs lisp as a kind of quick calculator, so here’s how I calculate the impedance (xc) and the output voltage (assuming 10v sine input):

(defun 1/ (x) (/ 1 x))
(defun sq (x) (* x x))

(let* ((f 10e3)
       (c 47e-9)
       (r 4700)
       (xc (1/ (* 2 pi f c))))
       (* 10 (/ xc (sqrt (+ (sq r) (sq xc))))))

;; Give 0.719, meaning at 10kHz the output is 0.719 volts.

I can also use ngspice to simulate the circuit, and do a sweep across frequencies:

RC filter

Vin 1 0 DC 0 AC 10
R1 1 2 4700
C1 2 0 47e-9

.control
AC LIN 10000 1 20000
run
set hcopypscolor
hardcopy rc.ps vm(2) xlimit 0 11k
shell evince rc.ps
.endc
.end
Voltage vs frequency for RC (R=4700ohm, C=4.7nF), thanks to ngspice

This plot visually confirms that at 10kHz, voltages is around 0.7v.

Now for the sagemath attempt. Sagemath knows about complex numbers, so we can represent impedances directly as complex numbers (sagemath uses uppercase I):

# Complex impedance for capacitor and inductor
C(c,f) = -I / (2 * pi * f * c)
L(l,f) = I * 2 * pi * f * l

# Composition rules for impedance
recip(x) = 1 / x
ipar(a,b) = recip( recip(a) + recip(b))
iser(a,b) = a + b

# If you have [v] volts across  a potential divider 
# consisting of impedance [a] on top, [b] below, 
# connected to [load], what voltage does the load see?
pd_vout(v, a, b, load) = v * ipar(b,load) / iser(a,ipar(b,load))

# Our example RC lowpass filter
R1 = 4700
C1 = 47e-9
overall = pd_vout(10, R1, C(C1,f), 10e9)

# What's the voltage at 10kHz?  Note: use n() to get 
# a number instead of a maths expression, and abs() to
# get the complex magnitude
overall(10e3).n().abs()

# Answer: 0.718621364643

It gets the right answer, but what’s interesting (to me) is that it’s clearly there’s no additional magic going on. Spice might be doing all sorts of elaborate things to get it’s answer. But in our Sagemath version, it’s obvious that the only ingredients are 1) impedance, represented as a complex number, 2) combinations of impedances, and 3) ohms law, as expressed in the potential divider equation.

In the next post, I’ll move onto something rather more useful – an LC bandpass filter.