diff --git a/README.md b/README.md index 82a1f57..2a20eeb 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,14 @@ Once a spiral inductor with the desired characteristics has been obtained, the c Here are some pictures of spiral inductors generated with spiki -- 3.5 turn, single- layer +- Circular 3.5 turn, single-layer -![3.5 turn, 1 layer](https://raw.github.com/in3otd/spiki/gh-pages/ind_spi_3.5.png) +![Circular 3.5 turn, 1 layer](https://raw.github.com/in3otd/spiki/gh-pages/ind_spi_3.5.png) -- 3.5 turn, two layers +- Circular 3.5 turn, two layers -![3.5 turn, 2 layer2](https://raw.github.com/in3otd/spiki/gh-pages/ind_spi_3.5_2layers.png) \ No newline at end of file +![Circular 3.5 turn, 2 layer2](https://raw.github.com/in3otd/spiki/gh-pages/ind_spi_3.5_2layers.png) + +- Square 13 turns, single-layer + +![Square 13 turns, 1 layer](https://raw.github.com/in3otd/spiki/gh-pages/int_square_13.png) diff --git a/design.ui b/design.ui index 0805012..6824a42 100644 --- a/design.ui +++ b/design.ui @@ -6,8 +6,8 @@ 0 0 - 756 - 684 + 958 + 686 @@ -18,455 +18,434 @@ spiki.pngspiki.png - - - - 400 - 10 - 271 - 131 - - - - PCB properties - - - - - 0 - 20 - 261 - 111 - - - - - - - mm - - - - - - - copper thickness - - - - - - - - - - - - - min spacing - - - - - - - um - - - - - - - mm - - - - - - - - - - PCB thickness - - - - - - - - - - 30 - 270 - 351 - 281 - - - - Simulation - - - - - 0 - 20 - 331 - 242 - - - - - - - - - - Q - - - - - - - - - - - 125 - 0 - - - - frequency - - - - - - - ohm - - - - - - - - - - resistance - - - - - - - MHz - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - uH - - - - - - - inductance - - - - - - - - - - - 125 - 0 - - - - desired inductance - - - - - - - uH - - - - - - - optimize - - - - - - - skin depth - - - - - - - - - - mm - - - - - - - Run simulation - - - - - - - - - - 30 - 10 - 341 - 241 - - - - Inductor parameters - - - - - 0 - 20 - 331 - 221 - - - - - - - - - - - - - trace width - - - - - - - inner radius - - - - - - - estimated inductance - - - - - - - - - - mm - - - - - - - number of turns - - - - - - - mm - - - - - - - - - - - - - - - - spacing - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - uH - - - - - - - mm - - - - - - - mm - - - - - - - pitch - - - - - - - number of layers - - - - - - - - - - - - - 400 - 150 - 291 - 111 - - - - Settings - - - - - 0 - 20 - 261 - 80 - - - - - - - drawing tolerance - - - - - - - mm - - - - - - - - - - inductor style - - - - - - - - circular segments - - - - - circular arcs - - - - - - - + + + + + Simulation + + + + + + + + ohm + + + + + + + + + + Q + + + + + + + + + + + + + + 125 + 0 + + + + frequency + + + + + + + resistance + + + + + + + MHz + + + + + + + + + + uH + + + + + + + inductance + + + + + + + + + + + 125 + 0 + + + + desired inductance + + + + + + + uH + + + + + + + optimize + + + + + + + skin depth + + + + + + + + + + mm + + + + + + + Run simulation + + + + + + + + + + + + PCB properties + + + + + + + + + + + + + + um + + + + + + + PCB thickness + + + + + + + min spacing + + + + + + + + + + copper thickness + + + + + + + mm + + + + + + + mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Settings + + + + + + + + drawing tolerance + + + + + + + mm + + + + + + + + + + inductor style + + + + + + + + circular segments + + + + + circular arcs + + + + + square + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + polygon vertex count + + + + + + + true + + + + + + + + + + + + Inductor parameters + + + + + + + + trace width + + + + + + + + + + + + + inner radius + + + + + + + estimated inductance + + + + + + + + + + mm + + + + + + + number of turns + + + + + + + mm + + + + + + + + + + + + + + + + spacing + + + + + + + uH + + + + + + + mm + + + + + + + mm + + + + + + + pitch + + + + + + + number of layers + + + + + + + + + + gridLayoutWidget + groupBox_2 + groupBox_2 + + + 0 0 - 756 - 25 + 958 + 31 @@ -482,7 +461,9 @@ - + + + Exit @@ -490,7 +471,9 @@ - + + + Save module diff --git a/dos.py b/dos.py index bd8250e..cfa28f2 100644 --- a/dos.py +++ b/dos.py @@ -440,6 +440,66 @@ def circ_spiral(N_turns, r_in, pitch, dir, d=0.1): return vertices +def square_spiral_segment(side_width_in_mm): + vertices = [] + + gap_between_terminals_in_mm = 2.3 + + + x = (side_width_in_mm / 2.0) * -1.0 + y = (gap_between_terminals_in_mm / 2.0) + vertices.append(Point(x, y)) + + y = (side_width_in_mm / 2.0) + vertices.append(Point(x, y)) + + x = (side_width_in_mm / 2.0) + vertices.append(Point(x, y)) + + y = (side_width_in_mm / 2.0) * -1.0 + vertices.append(Point(x, y)) + + x = (side_width_in_mm / 2.0) * -1.0 + vertices.append(Point(x, y)) + + y = (gap_between_terminals_in_mm / 2.0) * -1.0 + vertices.append(Point(x, y)) + + return vertices + +def square_spiral(N_turns, e_len, pitch): + """ + Draw a square antenna using straight segments + + Keyword arguments: + N_turns -- number of turns + e_len -- edge length + pitch -- spacing between conductors centers + + Returns segments vertices list + """ + + vertices = [] + + new_vertices = square_spiral_segment(e_len) + vertices = vertices + new_vertices + + if (N_turns > 1): + N_turns = (N_turns - 1) + while (N_turns > 0): + if (e_len < pitch): + vertices = None + break + + e_len = e_len - (pitch * 2.0) + new_vertices = square_spiral_segment(e_len) + + vertices = vertices + new_vertices + + N_turns = (N_turns - 1) + + return vertices + def draw_arcs_spiral(N_turns, r_in, pitch, tr_w, N, dir): """ diff --git a/spiki.py b/spiki.py index 896730b..9b4269b 100644 --- a/spiki.py +++ b/spiki.py @@ -21,6 +21,18 @@ Ui_MainWindow, QtBaseClass = uic.loadUiType("design.ui") +class SuperEnum(object): + class __metaclass__(type): + def __iter__(self): + for item in self.__dict__: + if item == self.__dict__[item]: + yield item + +class InductorStyle(SuperEnum): + CIRCULAR_SEGMENTS = 0 + CIRCULAR_ARCS = 1 + SQUARE = 2 + class kSpiralCalc(QtBaseClass, Ui_MainWindow): def __init__(self): @@ -68,6 +80,8 @@ def __init__(self): self.minSpacingLineEdit.setText('0.15') self.drawTolLineEdit.setText('0.1') + self.polygonVertexCountLineEdit.setText('4') + self.setPolygonVertexCountSettingVisible(False) self.nTurnsLineEdit.textChanged.connect(self.estimateInductance) self.innerRadiusLineEdit.textChanged.connect(self.estimateInductance) @@ -75,6 +89,8 @@ def __init__(self): self.spacingLineEdit.textChanged.connect(self.estimateInductance) self.traceWidthLineEdit.textChanged.connect(self.estimateInductance) self.nLayersLineEdit.textChanged.connect(self.estimateInductance) + + self.indStyleCB.activated.connect(self.inductorStyleChanged) self.estimateInductance() @@ -90,6 +106,39 @@ def __init__(self): self.optimizeBtn.setEnabled(False) # disable optimization button self.statusBar().showMessage("Ready.") + + def showInvalidParametersErrorMsg(self): + msg = QtGui.QMessageBox() + msg.setIcon(QtGui.QMessageBox.Critical) + msg.setText('It is physically impossible to produce an inductor from the given inductor parameters') + msg.setInformativeText('Please adjust one or more of the inductor parameters and try again') + msg.setWindowTitle("Error") + msg.exec_() + + def inductorStyleChanged(self, index): + if (index == InductorStyle.SQUARE): + self.setPolygonVertexCountSettingVisible(False) + + self.nLayersLabel.setVisible(False) + self.nLayersLineEdit.setText('1') + self.nLayersLineEdit.setVisible(False) + + self.innerRadiusLabel.setText('edge length') + + if (index == InductorStyle.CIRCULAR_SEGMENTS): + self.setPolygonVertexCountSettingVisible(False) + + if (index == InductorStyle.CIRCULAR_ARCS): + self.setPolygonVertexCountSettingVisible(True) + + if ((index == InductorStyle.CIRCULAR_SEGMENTS) or (index == InductorStyle.CIRCULAR_ARCS)): + self.nLayersLabel.setVisible(True) + self.nLayersLineEdit.setVisible(True) + self.innerRadiusLabel.setText('inner radius') + + def setPolygonVertexCountSettingVisible(self, val): + self.polygonVertexCountLabel.setVisible(val) + self.polygonVertexCountLineEdit.setVisible(val) def updateSpacing(self): pitch = float(self.pitchLineEdit.text()) @@ -110,8 +159,10 @@ def updatePitch(self): def updateSkinDepth(self): freq = float(self.freqLineEdit.text()) * 1e6 sigma = 5.8e7 # copper conductivity - mu0 = 4.0e-7 * math.pi - self.delta = 1.0 / math.sqrt(math.pi * freq * mu0 * sigma) # in meters + mur = 0.999994 # relative magnetic permeability of copper + mu0 = 4.0e-7 * math.pi # the permeability of free space + mu = mur * mu0 # permeability of a given conductor, + self.delta = 1.0 / math.sqrt(math.pi * freq * mu * sigma) # in meters self.skinDepthLineEdit.setText(str(self.delta * 1e3)) def runSimulation(self): @@ -163,7 +214,12 @@ def simulate(self): #draw_arcs_spiral(N_turns, r_in, pitch, tr_w, N, dir) dir = 1 - vx = dos.circ_spiral(nTurns, innerRadius, pitch, dir, d) + inductorStyleIndex = self.indStyleCB.currentIndex() + if (inductorStyleIndex == InductorStyle.SQUARE): + vx = dos.square_spiral(nTurns, innerRadius, pitch) + else: + vx = dos.circ_spiral(nTurns, innerRadius, pitch, dir, d) + sf.add_circ_spiral(vx, 1, traceWidth, cuThickness * 1e-3, pcbThickness) sf.add_ports() if (nLayers == 2): @@ -257,57 +313,82 @@ def writeModule(self): traceWidth = float(self.traceWidthLineEdit.text()) nLayers = int(self.nLayersLineEdit.text()) d = float(self.drawTolLineEdit.text()) + + polygonVertexCount = None - sm = dos.kmodule(fname) - sm.write_header(name='SIND', descr='spiral inductor', tags='SMD') + geometricPrimitives = None dir = 1 - if (self.indStyleCB.currentIndex() == 0): # circular segments - vx = dos.circ_spiral(nTurns, innerRadius, pitch, dir, d) - sm.add_circ_spiral(vx, 'F.Cu', traceWidth) - if (nLayers == 2): - pad1 = vx[-1] # inductor starts at end of top spiral - vx = dos.circ_spiral(nTurns, innerRadius, pitch, -dir, d) - sm.add_circ_spiral(vx, 'B.Cu', traceWidth) - sm.add_thru_pad('lc', 'circle', vx[0], dos.Point(0.6, 0.6), 0.3) - end_layer = 'B' - else: # single-layer spiral - pad1 = vx[0] # inductor starts at center of spiral - end_layer = 'F' - pad2 = vx[-1] # inductor ends always at end of last spiral - else: # circular arcs - # FIXME: make N below user-configurable instead of 4 - arcs = dos.arcs_spiral(nTurns, innerRadius, pitch, dir, 4) - sm.add_arc_spiral(arcs, 'F.Cu', traceWidth) - if (nLayers == 2): - # inductor starts at end of top spiral + inductorStyleIndex = self.indStyleCB.currentIndex() + if (inductorStyleIndex == InductorStyle.CIRCULAR_SEGMENTS): # circular segments + geometricPrimitives = dos.circ_spiral(nTurns, innerRadius, pitch, dir, d) + elif (inductorStyleIndex == InductorStyle.CIRCULAR_ARCS): # circular arcs + polygonVertexCount = int(self.polygonVertexCountLineEdit.text()) + geometricPrimitives = dos.arcs_spiral(nTurns, innerRadius, pitch, dir, polygonVertexCount) + elif (inductorStyleIndex == InductorStyle.SQUARE): # rectangle + geometricPrimitives = dos.square_spiral(nTurns, innerRadius, pitch) + + # Check if making an inductor from the given parameters is at all possible + if (geometricPrimitives is None): + self.showInvalidParametersErrorMsg() + else: + sm = dos.kmodule(fname) + sm.write_header(name='SIND', descr='spiral inductor', tags='SMD') + if (inductorStyleIndex == InductorStyle.CIRCULAR_SEGMENTS): # circular segments + vx = geometricPrimitives + sm.add_circ_spiral(vx, 'F.Cu', traceWidth) + if (nLayers == 2): + pad1 = vx[-1] # inductor starts at end of top spiral + vx = dos.circ_spiral(nTurns, innerRadius, pitch, -dir, d) + sm.add_circ_spiral(vx, 'B.Cu', traceWidth) + sm.add_thru_pad('lc', 'circle', vx[0], dos.Point(0.6, 0.6), 0.3) + end_layer = 'B' + else: # single-layer spiral + pad1 = vx[0] # inductor starts at center of spiral + end_layer = 'F' + pad2 = vx[-1] # inductor ends always at end of last spiral + elif (inductorStyleIndex == InductorStyle.CIRCULAR_ARCS): # circular arcs + arcs = geometricPrimitives + sm.add_arc_spiral(arcs, 'F.Cu', traceWidth) + if (nLayers == 2): + # inductor starts at end of top spiral + p_end = arcs[-1][1].copy() # starting point of the last arc + p_center = arcs[-1][0] # centre of the last arc + theta = arcs[-1][2] # arc starting point + p_end.rotate_about(p_center, theta) # end point of the circular arc + pad1 = p_end + arcs = dos.arcs_spiral(nTurns, innerRadius, pitch, -dir, polygonVertexCount) + sm.add_arc_spiral(arcs, 'B.Cu', traceWidth) + sm.add_thru_pad('lc', 'circle', arcs[0][1], dos.Point(0.6, 0.6), 0.3) + end_layer = 'B' + else: # single-layer spiral + # inductor starts at center of spiral + pad1 = arcs[0][1] # starting point of the last arc + end_layer = 'F' + # inductor ends always at end of last spiral p_end = arcs[-1][1].copy() # starting point of the last arc p_center = arcs[-1][0] # centre of the last arc theta = arcs[-1][2] # arc starting point p_end.rotate_about(p_center, theta) # end point of the circular arc - pad1 = p_end - arcs = dos.arcs_spiral(nTurns, innerRadius, pitch, -dir, 4) - sm.add_arc_spiral(arcs, 'B.Cu', traceWidth) - sm.add_thru_pad('lc', 'circle', arcs[0][1], dos.Point(0.6, 0.6), 0.3) - end_layer = 'B' - else: # single-layer spiral - # inductor starts at center of spiral - pad1 = arcs[0][1] # starting point of the last arc + pad2 = p_end + elif (inductorStyleIndex == InductorStyle.SQUARE): # rectangle + vx = geometricPrimitives + sm.add_circ_spiral(vx, 'F.Cu', traceWidth) + # single-layer spiral + pad1 = vx[0] # inductor starts at center of spiral end_layer = 'F' - # inductor ends always at end of last spiral - p_end = arcs[-1][1].copy() # starting point of the last arc - p_center = arcs[-1][0] # centre of the last arc - theta = arcs[-1][2] # arc starting point - p_end.rotate_about(p_center, theta) # end point of the circular arc - pad2 = p_end - - # add SMD pads at the beginning and end - padSize = dos.Point(traceWidth/2.0, traceWidth/2.0) - sm.add_smd_pad('1', 'rect', pad1, padSize, 'F') - sm.add_smd_pad('2', 'rect', pad2, padSize, end_layer) - - #draw_arcs_spiral(nTurns, innerRadius, pitch, traceWidth, N, dir) - sm.write_refs(0, 0, ref='REF**', value='LLL') - sm.close() + + pad2 = vx[-1] # inductor ends always at end of last spiral + else: + pass + + # add SMD pads at the beginning and end + padSize = dos.Point(traceWidth/2.0, traceWidth/2.0) + sm.add_smd_pad('1', 'rect', pad1, padSize, 'F') + sm.add_smd_pad('2', 'rect', pad2, padSize, end_layer) + + #draw_arcs_spiral(nTurns, innerRadius, pitch, traceWidth, N, dir) + sm.write_refs(0, 0, ref='REF**', value='LLL') + sm.close() def estimateInductance(self): try: diff --git a/spiki.sh b/spiki.sh new file mode 100755 index 0000000..8788c39 --- /dev/null +++ b/spiki.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +SCRIPT_LOCATION_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + + +export PATH=$SCRIPT_LOCATION_DIR:$PATH + +python $SCRIPT_LOCATION_DIR/spiki.py