Spider Coil Calculator for Radio Applications - Python Code

In my crystal set post I use spider coils, because they are easy to make (my 8yo daughter wound one all by herself), and because they have better Q than a TPR coil (Toilet Paper Roll - common type used in basic crystal sets).

The trick with any coil is to get the right impedance, and then match it with the right capacitance to tune the right frequency.  When using a variable capacitor, the coil inductance can be fixed so that a certain frequency band can be covered - and this is the case with the AM band here in New Zealand.  If we use a standard polyvaricon (variable capacitor found in most electrical hobby shops like Dick Smith, South Island Components, Jaycar etc) which has 2 gangs - one 5 -60pf and one 5-160pf, and we parallel the two gangs together, then we get 10-220pf (parallel capacitances add together - think of two water tanks beside each other... your total capacity is how much both can hold).

Ok, thats enough theory... well, actually... its not... the tuned frequency of a tuned circuit (an inductor and a capacitor in parallel) is found by the following formula:

f = {1 \over {2 \pi \sqrt{LC}}}  (formula courtesy of wikipedia)

What this means is that we can find the lowest frequency (when capacitance is at its maximum of 220pF), and the highest (when capacitance is at 10pf) - and thus find the theoretical band coverage of our radio once complete.  And I'vce found that this is surprisingly accurate - to within +/- one turn on the coil.

So, since we were building a few - and in line with my typical 'do it the hard way, just because I can' mentality - I build an application in Python to simplify the calculations.  The maths is based on the Professor Coyle calculator by Dan Petersen, but the code is all mine (warts 'n' all).  Its built as a GUI app, and its here that I learned to hate typing 'self'.  I'm sure I will delve into that at some point tho in another post.

As with much of the Python code I write, it needs tidying up... but it is functional for now.  Once I complete my degree I intend to make this part of an overall coil calculator to develop basket-weave coils, cylindrical (TPR) coils, and air-core inductors as well... but for now it does what I need it to do.

If its of some use to you, please let me know via the comments section - cheers.

## Spider Coil Calculator
## Author: Steve Dunford
## (c)?  not really... nothing here is new...
##
## Licence: Beerware - if you like it, buy me a beer :)
 
 
#!/usr/local/bin/python
#encoding: utf-8
 
from Tkinter import *
from decimal import *
from sys     import *
from PIL     import Image, ImageTk
from math    import *
import tkMessageBox
 
 
class Application(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.fid = IntVar(self,'40') # Initial inside dia of former = 50mm
        self.nt  = IntVar(self,'75') # Initial number of turns = 50
        self.wd  = DoubleVar(self,'0.12') # Initial wire dia = 0.12mm
        self.ind = IntVar()  # inductance
        self.fod = IntVar()  # former outside dia
        self.wl = IntVar()   # wire length
        self.minf = IntVar() # min freq using 20-220pF tuning cap
        self.maxf = IntVar() # max freq using 20-220pF tuning cap
        self.grid()
        # Gave up trying to get the pic to go where I wanted it...
        #self.image = Image.open("image004.jpg")
        #self.pic   = ImageTk.PhotoImage(self.image)
        self.createMenu()
        self.createWidgets()
 
    def createMenu(self):
        top = self.winfo_toplevel()
        self.menuBar = Menu(top)
        top["menu"] = self.menuBar
 
        self.fileMenu = Menu(self.menuBar, tearoff = 0)
        self.fileMenu.add_command(label="Quit", command = top.destroy)
        self.menuBar.add_cascade(label="File", menu = self.fileMenu)
 
        self.helpMenu = Menu(self.menuBar, tearoff = 0)
        self.helpMenu.add_command(label="Howto", command = self.howto)
        self.helpMenu.add_command(label="About", command = self.about)
        self.menuBar.add_cascade(label="Help", menu = self.helpMenu)
 
    def createWidgets(self):
        top = self.winfo_toplevel()
        # Configure the main window to be 'stretchy'
        top.rowconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        # Input box labels 1st column
        self.label1 = Label(self, text = "Former inside diameter: ")
        self.label1.grid(sticky = 'e', column = 0, row = 0)
        self.label2 = Label(self, text = "mm")
        self.label2.grid(sticky = 'w', column = 2, row = 0)
        self.label3 = Label(self, text = "Wire diameter: ")
        self.label3.grid(sticky = 'e', column = 0, row = 1)
        self.label4 = Label(self, text = "mm")
        self.label4.grid(sticky = 'w', column = 2, row = 1)
        self.label5 = Label(self, text = "Number of turns: ")
        self.label5.grid(sticky = 'e', column = 0, row = 2)
        self.label6 = Label(self, text = "Inductance: ")
        self.label6.grid(sticky = 'e', column = 0, row = 3)
        uH = u'\xb5H' # This works in Unix & Windows
        self.label7 = Label(self, text = uH, width = 5)
        self.label7.grid(sticky = 'w', column = 2, row = 3)
        # Input box labels 2nd column
        self.label8 = Label(self, text = "Req'd former OD: ")
        self.label8.grid(sticky = 'e', column = 3, row = 0)
        self.label9 = Label(self, text = "mm")
        self.label9.grid(sticky = 'w', column = 5, row = 0)
        self.label10 = Label(self, text = "Req'd wire length: ")
        self.label10.grid(sticky = 'e', column = 3, row = 1)
        self.label11 = Label(self, text = "m")
        self.label11.grid(sticky = 'w', column = 5, row = 1)
        self.label12 = Label(self, text = "20-220pF low end: ")
        self.label12.grid(sticky = 'e', column = 3, row = 2)
        self.label13 = Label(self, text = "kHz")
        self.label13.grid(sticky = 'w', column = 5, row = 2)
        self.label14 = Label(self, text = "20-220pF high end: ")
        self.label14.grid(sticky = 'e', column = 3, row = 3)
        self.label15 = Label(self, text = "kHz")
        self.label15.grid(sticky = 'w', column = 5, row = 3)
        # Input boxes 1st column
        self.entfid = Entry(self, textvariable = self.fid, width = 10, justify = 'right')
        self.entfid.bind('<Return>', self.calculate)
        self.entfid.grid(sticky = 'w', column = 1, row = 0)
        self.entwd = Entry(self, textvariable = self.wd, width = 10, justify = 'right')
        self.entwd.bind('<Return>', self.calculate)
        self.entwd.grid(sticky = 'w', column = 1, row = 1)
        self.entnt = Entry(self, textvariable = self.nt, width = 10, justify = 'right')
        self.entnt.bind('<Return>', self.calculate)
        self.entnt.grid(sticky = 'w', column = 1, row = 2)
        self.entind = Entry(self, textvariable = self.ind, width = 10, justify = 'right', 
                                state = 'readonly')
        self.entind.grid(stick = 'w', column = 1, row = 3)
        # Input boxes 2nd column
        self.entfid = Entry(self, textvariable = self.fod, width = 10, justify = 'right', 
                                state = 'readonly')
        self.entfid.grid(sticky = 'w', column = 4, row = 0)
        self.entwd = Entry(self, textvariable = self.wl, width = 10, justify = 'right', 
                                state = 'readonly')
        self.entwd.grid(sticky = 'w', column = 4, row = 1)
        self.entnt = Entry(self, textvariable = self.minf, width = 10, justify = 'right', 
                                state = 'readonly')
        self.entnt.grid(sticky = 'w', column = 4, row = 2)
        self.entind = Entry(self, textvariable = self.maxf, width = 10, justify = 'right', 
                                state = 'readonly')
        self.entind.grid(stick = 'w', column = 4, row = 3)
        # The picture of the former - just won't go in - or stay in - its own cell
        #self.picture = Label(image = self.pic, bg = 'green', anchor = 'nw')
        #self.picture.grid(column = 0, columnspan = 5, row = 5, sticky = 'nsew', 
                #               ipadx = 5, ipady = 5)
        # The 'Calculate' button
        self.calcButton = Button(self, text='Calculate', padx = 5, command= self.calculate)
        self.calcButton.grid(column = 1, columnspan = 2, rowspan = 2, row = 4, 
                                padx = 5, pady = 5)
        # The 'Quit' button
        self.quitButton = Button(self, text='Quit', padx = 10, command=top.destroy)
        self.quitButton.grid(column = 4, columnspan = 2, rowspan = 2, row = 4, 
                                padx = 5, pady = 5, sticky = 'e')
        #Set an initial value based on the initial dimensions
        self.calculate()  # initialise the inductance figure
 
 
    def calculate(self, arg = None): # Arg = None is for the bind command when used
        modif = 25.4 # Make this 1 to calculate in inches
        coilSpread = self.wd.get() * self.nt.get() # Width of the coil
        meanRad = (self.fid.get() / 2) + (coilSpread / 2) # Average radius of the coil
        inductance = ((meanRad * self.nt.get() / modif) ** 2) / ((8 * meanRad / modif) + 
                                (11 * coilSpread / modif))
        ind = str(inductance)
        ind = Decimal(ind).quantize(Decimal('0.01'), rounding = ROUND_UP)
        minf = str(1 / (2 * pi * sqrt(inductance * 220 * 0.01))*100000)
        minf = Decimal(minf).quantize(Decimal('0.01'), rounding = ROUND_UP)
        maxf = str(1 / (2 * pi * sqrt(inductance * 20 * 0.01))*100000)
        maxf = Decimal(maxf).quantize(Decimal('0.01'), rounding = ROUND_UP)
        wireLength = str((2 * pi * meanRad * self.nt.get() / 1000) + 0.2)
        wireLength = Decimal(wireLength).quantize(Decimal('0.01'), rounding = ROUND_UP)
        # Set the values
        self.ind.set(ind) # This will be rounded to 2 d.p.
        self.fod.set(coilSpread * 2 + self.fid.get() + 5)
        self.wl.set(wireLength)
        self.minf.set(minf)
        self.maxf.set(maxf)
 
    def howto(self):
        tkMessageBox.showinfo("Help", "Enter the former inside diameter, wire diameter \
                and number of turns and then click the calculate\nbutton (or hit enter in \
                any of the entry boxes)\n\nNotes:\n* The inductance value is theoretical \
                but should be fairly close.\n* The outside diameter of the former is \
                calculated to have approx 5mm of blank space outside of\n   the windings \
                when complete.\n* The length of the wire required includes two lengths of \
                100mm for the connection tails.\n* The tuning capacitor refered to is a \
                typical small plastic '60-160' one found in most electronics hobby\n   \
                stores and has the two outer tabs connected together to give an overall \
                20-220pF range\n* The initial settings should provide a coil that will \
                cover the AM broadcast band of 535 - 1605kHz")
 
    def about(self):
        tkMessageBox.showinfo("About", "Coil Calculator is based on the 'Professor Coyle' \
                spreadsheet written by Dan Petersen.\n")
 
 
 
app = Application()
app.master.title("Spider Coil Calculator")
app.mainloop()