{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "\n", "# import matplotlib as plt\n", "import matplotlib.pyplot as plt\n", "from skspatial.objects import LineSegment, Line, Vector\n", "\n", "# some helper functions\n", "from helpers import (\n", " get_arc_point,\n", " draw_arc,\n", " rotate,\n", " translate,\n", " flip_y,\n", " flip_x,\n", " optimize_points,\n", " chaikin,\n", ")\n", "from pcb_json import (\n", " dump_json,\n", " plot_json,\n", " create_pad,\n", " create_silk,\n", " create_via,\n", " create_mounting_hole,\n", " create_pin,\n", ")\n", "\n", "from enum import Enum\n", "\n", "Layer = Enum(\"Layer\", \"FRONT BACK\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Track width and spacing\n", "TRACK_WIDTH = 0.127\n", "TRACK_SPACING = 0.127\n", "\n", "# via defaults\n", "VIA_DIAM = 0.8\n", "VIA_DRILL = 0.4\n", "\n", "# this is for a 1.27mm pitch pin\n", "PIN_DIAM = 1.0\n", "PIN_DRILL = 0.65\n", "\n", "# this is for the PCB connector - see https://www.farnell.com/datasheets/2003059.pdf\n", "PAD_WIDTH = 3\n", "PAD_HEIGHT = 2\n", "PAD_PITCH = 2.5\n", "\n", "# PCB Edge size\n", "STATOR_RADIUS = 25\n", "STATOR_HOLE_RADIUS = 5.5\n", "\n", "# where to puth the mounting pins\n", "SCREW_HOLE_DRILL_DIAM = 2.3 # 2.3mm drill for a 2mm screw\n", "SCREW_HOLE_RADIUS = STATOR_RADIUS\n", "\n", "# where to put the input pads\n", "INPUT_PAD_RADIUS = STATOR_RADIUS - (PAD_WIDTH / 2 + VIA_DIAM + TRACK_SPACING)\n", "\n", "# Coil params\n", "TURNS = 24\n", "COIL_CENTER_RADIUS = 16\n", "COIL_VIA_RADIUS = 17\n", "\n", "# where to place the pins\n", "# CONNECTION_PINS_RADIUS = 16.5\n", "\n", "USE_SPIRAL = False\n", "\n", "LAYERS = 4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Arbitrary Coil Generation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# templates must be simetric around the X axis and must include the center points on both size (e.g. (X1, 0).... (X2, 0) )\n", "# template must also be convex\n", "template = [\n", " (-0.6, 0),\n", " (-0.6, -0.6),\n", " (0.5, -1.2),\n", " (0.95, -0.4),\n", " (0.95, 0),\n", " (0.95, 0.4),\n", " (0.5, 1.2),\n", " (-0.6, 0.6),\n", "]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the template shape wrapping around to the first point\n", "df = pd.DataFrame(template + [template[0]], columns=[\"x\", \"y\"])\n", "ax = df.plot.line(x=\"x\", y=\"y\", color=\"blue\")\n", "ax.axis(\"equal\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def calculate_point(point, point1, point2, spacing, turn):\n", " reference_vector = Vector([-100, 0])\n", " angle = np.rad2deg(Vector(point).angle_between(reference_vector))\n", " if point[1] > 0:\n", " angle = 360 - angle\n", " vector = Vector(point1) - Vector(point2)\n", " normal = vector / np.linalg.norm(vector)\n", " # rotate the vector 90 degrees\n", " normal = np.array([-normal[1], normal[0]])\n", " # move the point along the normal vector by the spacing\n", " offset = spacing * (turn * 360 + angle) / 360\n", " coil_point = point + normal * offset\n", " return (coil_point[0], coil_point[1])\n", "\n", "\n", "def get_points(template, turns, spacing):\n", " coil_points = []\n", " reference_vector = Vector([-100, 0])\n", " template_index = 0\n", " template_length = len(template)\n", " for turn in range(turns * template_length):\n", " point1 = template[template_index % template_length]\n", " point2 = template[(template_index + 1) % template_length]\n", "\n", " # calculate the new positions of the points\n", " coil_point1 = calculate_point(\n", " point1, point1, point2, spacing, template_index // template_length\n", " )\n", " coil_point2 = calculate_point(\n", " point2, point1, point2, spacing, (template_index + 1) // template_length\n", " )\n", " # adjust the previous point so that the previous line intersects with this new line\n", " # this prevents any cutting of corners\n", " if len(coil_points) >= 2:\n", " # create a line from the previous two points\n", " line1 = Line(\n", " coil_points[len(coil_points) - 2],\n", " np.array(coil_points[len(coil_points) - 1])\n", " - np.array(coil_points[len(coil_points) - 2]),\n", " )\n", " # create a line from the two new points\n", " line2 = Line(\n", " np.array(coil_point1),\n", " np.array(np.array(coil_point1) - np.array(coil_point2)),\n", " )\n", " # find the intersection of the two lines\n", " try:\n", " intersection = line1.intersect_line(line2)\n", " # replace the previous point with the intersection\n", " coil_points[len(coil_points) - 1] = intersection\n", " # add the new point\n", " coil_points.append(coil_point2)\n", " except:\n", " # the lines did not intersect so just add the points\n", " coil_points.append(coil_point1)\n", " coil_points.append(coil_point2)\n", " else:\n", " coil_points.append(coil_point1)\n", " coil_points.append(coil_point2)\n", "\n", " template_index = template_index + 1\n", " return coil_points" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if not USE_SPIRAL:\n", " template_f = []\n", " for i in range(len(template)):\n", " template_f.append(template[len(template) - i - len(template) // 2])\n", " template_f = flip_x(template_f)\n", " points_f = chaikin(\n", " optimize_points(\n", " flip_x(get_points(template_f, TURNS, TRACK_SPACING + TRACK_WIDTH))\n", " ),\n", " 2,\n", " )\n", " points_b = chaikin(\n", " optimize_points(get_points(template, TURNS, TRACK_SPACING + TRACK_WIDTH)), 2\n", " )\n", "\n", " points_f = [(0, 0)] + points_f\n", " points_b = [(0, 0)] + points_b\n", "\n", " df = pd.DataFrame(points_f, columns=[\"x\", \"y\"])\n", " ax = df.plot.line(x=\"x\", y=\"y\", color=\"blue\")\n", " ax.axis(\"equal\")\n", " df = pd.DataFrame(points_b, columns=[\"x\", \"y\"])\n", " ax = df.plot.line(x=\"x\", y=\"y\", color=\"red\", ax=ax)\n", "\n", " print(\"Track points\", len(points_f), len(points_b))\n", "else:\n", " print(\"Using spiral\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Basic Spiral Coil Generation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_spiral(turns, start_radius, thickness, layer=Layer.FRONT):\n", " points = []\n", " # create a starting point in the center\n", " for angle in np.arange(0, turns * 360, 1):\n", " radius = start_radius + thickness * angle / 360\n", " if layer == Layer.BACK:\n", " x = radius * np.cos(np.deg2rad(angle + 180))\n", " y = radius * np.sin(np.deg2rad(angle + 180))\n", " points.append((x, -y))\n", " else:\n", " x = radius * np.cos(np.deg2rad(angle))\n", " y = radius * np.sin(np.deg2rad(angle))\n", " points.append((x, y))\n", " return points" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if USE_SPIRAL:\n", " points_f = get_spiral(\n", " TURNS, VIA_DIAM / 2 + TRACK_SPACING, TRACK_SPACING + TRACK_WIDTH, Layer.FRONT\n", " )\n", " points_b = get_spiral(\n", " TURNS, VIA_DIAM / 2 + TRACK_SPACING, TRACK_SPACING + TRACK_WIDTH, Layer.BACK\n", " )\n", "\n", " points_f = [(0, 0)] + points_f\n", " points_b = [(0, 0)] + points_b\n", " print(\"Track points\", len(points_f), len(points_b))\n", "else:\n", " print(\"Using template\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Generate PCB Layout" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# calculat the total length of the track to compute the resistance\n", "total_length_front = 0\n", "for i in range(len(points_f) - 1):\n", " total_length_front += np.linalg.norm(\n", " np.array(points_f[i + 1]) - np.array(points_f[i])\n", " )\n", "print(\"Total length front\", total_length_front)\n", "\n", "total_length_back = 0\n", "for i in range(len(points_b) - 1):\n", " total_length_back += np.linalg.norm(\n", " np.array(points_b[i + 1]) - np.array(points_b[i])\n", " )\n", "print(\"Total length back\", total_length_back)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vias = []\n", "tracks_f = []\n", "tracks_b = []\n", "pins = []\n", "pads = []\n", "\n", "angle_A = 0\n", "angle_B = 120\n", "angle_C = 240\n", "\n", "# the main coils\n", "coil_A_f = translate(rotate(points_f, angle_A), COIL_CENTER_RADIUS, angle_A)\n", "coil_A_b = translate(rotate(points_b, angle_A), COIL_CENTER_RADIUS, angle_A)\n", "tracks_f.append(coil_A_f)\n", "tracks_b.append(coil_A_b)\n", "\n", "coil_B_f = translate(rotate(points_f, angle_B), COIL_CENTER_RADIUS, angle_B)\n", "coil_B_b = translate(rotate(points_b, angle_B), COIL_CENTER_RADIUS, angle_B)\n", "tracks_f.append(coil_B_f)\n", "tracks_b.append(coil_B_b)\n", "\n", "coil_C_f = translate(rotate(points_f, angle_C), COIL_CENTER_RADIUS, angle_C)\n", "coil_C_b = translate(rotate(points_b, angle_C), COIL_CENTER_RADIUS, angle_C)\n", "tracks_f.append(coil_C_f)\n", "tracks_b.append(coil_C_b)\n", "\n", "# the opposite coils - for more power!\n", "angle_A_opp = angle_A + 180\n", "angle_B_opp = angle_B + 180\n", "angle_C_opp = angle_C + 180\n", "\n", "coil_A_opp_f = translate(\n", " rotate(flip_y(points_f), angle_A_opp), COIL_CENTER_RADIUS, angle_A_opp\n", ")\n", "coil_A_opp_b = translate(\n", " rotate(flip_y(points_b), angle_A_opp), COIL_CENTER_RADIUS, angle_A_opp\n", ")\n", "tracks_f.append(coil_A_opp_f)\n", "tracks_b.append(coil_A_opp_b)\n", "\n", "coil_B_opp_f = translate(\n", " rotate(flip_y(points_f), angle_B_opp), COIL_CENTER_RADIUS, angle_B_opp\n", ")\n", "coil_B_opp_b = translate(\n", " rotate(flip_y(points_b), angle_B_opp), COIL_CENTER_RADIUS, angle_B_opp\n", ")\n", "tracks_f.append(coil_B_opp_f)\n", "tracks_b.append(coil_B_opp_b)\n", "\n", "coil_C_opp_f = translate(\n", " rotate(flip_y(points_f), angle_C_opp), COIL_CENTER_RADIUS, angle_C_opp\n", ")\n", "coil_C_opp_b = translate(\n", " rotate(flip_y(points_b), angle_C_opp), COIL_CENTER_RADIUS, angle_C_opp\n", ")\n", "tracks_f.append(coil_C_opp_f)\n", "tracks_b.append(coil_C_opp_b)\n", "\n", "# connect the front and back coils together\n", "vias.append(create_via(get_arc_point(angle_A, COIL_CENTER_RADIUS)))\n", "vias.append(create_via(get_arc_point(angle_B, COIL_CENTER_RADIUS)))\n", "vias.append(create_via(get_arc_point(angle_C, COIL_CENTER_RADIUS)))\n", "vias.append(create_via(get_arc_point(angle_A_opp, COIL_CENTER_RADIUS)))\n", "vias.append(create_via(get_arc_point(angle_B_opp, COIL_CENTER_RADIUS)))\n", "vias.append(create_via(get_arc_point(angle_C_opp, COIL_CENTER_RADIUS)))\n", "\n", "# connect the front copper opposite coils together\n", "common_connection_radius = SCREW_HOLE_RADIUS - (\n", " TRACK_SPACING + SCREW_HOLE_DRILL_DIAM / 2\n", ")\n", "common_coil_connections_b = draw_arc(angle_A_opp, angle_C_opp, common_connection_radius)\n", "coil_A_opp_f.append(get_arc_point(angle_A_opp, common_connection_radius))\n", "coil_B_opp_f.append(get_arc_point(angle_B_opp, common_connection_radius))\n", "coil_C_opp_f.append(get_arc_point(angle_C_opp, common_connection_radius))\n", "\n", "tracks_f.append(common_coil_connections_b)\n", "\n", "# vias.append(create_via(get_arc_point(angle_A_opp, common_connection_radius)))\n", "# vias.append(create_via(get_arc_point(angle_B_opp, common_connection_radius)))\n", "# vias.append(create_via(get_arc_point(angle_C_opp, common_connection_radius)))\n", "\n", "# wires for connecting to opposite coils\n", "connection_radius1 = STATOR_HOLE_RADIUS + (2 * TRACK_SPACING)\n", "connection_radius2 = connection_radius1 + (2 * TRACK_SPACING + VIA_DIAM / 2)\n", "\n", "# draw a 45 degree line from each coil at connection radius 1\n", "# then connect up to connection radius 2\n", "# draw a 45 degree line to the opposite coil\n", "\n", "# coil A\n", "coil_A_b.append(get_arc_point(angle_A, connection_radius1))\n", "coil_A_opp_b.append(get_arc_point(angle_A_opp, connection_radius2))\n", "a_connection_b = draw_arc(angle_A, angle_A + 90, connection_radius1)\n", "a_connection_f = draw_arc(angle_A + 90, angle_A + 180, connection_radius2)\n", "a_connection_b.append(a_connection_f[0])\n", "\n", "tracks_f.append(a_connection_f)\n", "tracks_b.append(a_connection_b)\n", "\n", "# coil B\n", "coil_B_b.append(get_arc_point(angle_B, connection_radius1))\n", "coil_B_opp_b.append(get_arc_point(angle_B_opp, connection_radius2))\n", "b_connection_b = draw_arc(angle_B, angle_B + 90, connection_radius1)\n", "b_connection_f = draw_arc(angle_B + 90, angle_B + 180, connection_radius2)\n", "b_connection_b.append(b_connection_f[0])\n", "\n", "tracks_f.append(b_connection_f)\n", "tracks_b.append(b_connection_b)\n", "\n", "# coil C\n", "coil_C_b.append(get_arc_point(angle_C, connection_radius1))\n", "coil_C_opp_b.append(get_arc_point(angle_C_opp, connection_radius2))\n", "c_connection_b = draw_arc(angle_C, angle_C + 90, connection_radius1)\n", "c_connection_f = draw_arc(angle_C + 90, angle_C + 180, connection_radius2)\n", "c_connection_b.append(c_connection_f[0])\n", "\n", "tracks_f.append(c_connection_f)\n", "tracks_b.append(c_connection_b)\n", "\n", "vias.append(create_via(a_connection_f[0]))\n", "vias.append(create_via(b_connection_f[0]))\n", "vias.append(create_via(c_connection_f[0]))\n", "\n", "vias.append(create_via(a_connection_f[-1]))\n", "vias.append(create_via(b_connection_f[-1]))\n", "vias.append(create_via(c_connection_f[-1]))\n", "\n", "silk = [\n", " create_silk(get_arc_point(angle_A, COIL_CENTER_RADIUS), \"A\"),\n", " create_silk(get_arc_point(angle_B, COIL_CENTER_RADIUS), \"B\"),\n", " create_silk(get_arc_point(angle_C, COIL_CENTER_RADIUS), \"C\"),\n", "]\n", "\n", "# create mounting holes at 45 degree angles\n", "mounting_holes = [\n", " create_mounting_hole(get_arc_point(angle, SCREW_HOLE_RADIUS), SCREW_HOLE_DRILL_DIAM)\n", " for angle in [45, 135, 225, 315]\n", "]\n", "\n", "# create the pads for connecting the inputs to the coils\n", "silk.append(\n", " create_silk((-PAD_PITCH, INPUT_PAD_RADIUS - PAD_HEIGHT - 2.5), \"C\", \"b\", 2.5)\n", ")\n", "pads.append(create_pad((-PAD_PITCH, INPUT_PAD_RADIUS), PAD_HEIGHT, PAD_WIDTH, \"b\"))\n", "\n", "silk.append(create_silk((0, INPUT_PAD_RADIUS - PAD_HEIGHT - 2.5), \"B\", \"b\", 2.5))\n", "pads.append(create_pad((0, INPUT_PAD_RADIUS), PAD_HEIGHT, PAD_WIDTH, \"b\"))\n", "\n", "silk.append(\n", " create_silk((PAD_PITCH, INPUT_PAD_RADIUS - PAD_HEIGHT - 2.5), \"A\", \"b\", 2.5)\n", ")\n", "pads.append(create_pad((PAD_PITCH, INPUT_PAD_RADIUS), PAD_HEIGHT, PAD_WIDTH, \"b\"))\n", "\n", "# connect coil A to the right pad\n", "pad_angle = np.rad2deg(np.arcsin(PAD_PITCH / INPUT_PAD_RADIUS))\n", "input_connection_radius = common_connection_radius - (TRACK_SPACING + VIA_DIAM / 2)\n", "tracks_f.append(\n", " draw_arc(angle_A, angle_A - 30, input_connection_radius, 1) + [coil_A_f[-1]]\n", ")\n", "vias.append(create_via(get_arc_point(angle_A - 30, input_connection_radius)))\n", "tracks_b.append(\n", " [get_arc_point(angle_A - 30, input_connection_radius)]\n", " + draw_arc(angle_A - 30, 90 - pad_angle, common_connection_radius, 1)\n", ")\n", "\n", "# connect the C coil to the left pad\n", "tracks_f.append(\n", " draw_arc(angle_C, angle_C - 30, input_connection_radius, 1) + [coil_C_f[-1]]\n", ")\n", "vias.append(create_via(get_arc_point(angle_C - 30, input_connection_radius)))\n", "tracks_b.append(\n", " draw_arc(angle_C - 30, 90 + pad_angle, common_connection_radius, 1)\n", " + [get_arc_point(angle_C - 30, input_connection_radius)]\n", ")\n", "\n", "# connect the B coil to the middle pad\n", "tracks_f.append(\n", " [get_arc_point(90, input_connection_radius - (PAD_WIDTH))]\n", " + draw_arc(angle_B, angle_B - 30, input_connection_radius, 1)\n", " + [coil_B_f[-1]]\n", ")\n", "vias.append(create_via(get_arc_point(90, input_connection_radius - (PAD_WIDTH))))\n", "tracks_b.append(\n", " [\n", " get_arc_point(90, input_connection_radius - (PAD_WIDTH)),\n", " get_arc_point(angle_B - 30, input_connection_radius),\n", " ]\n", ")\n", "\n", "# pad_connection_point_y = SCREW_HOLE_RADIUS - (TRACK_SPACING + SCREW_HOLE_DRILL_DIAM / 2)\n", "# input_pad_connection_radius = SCREW_HOLE_RADIUS - (TRACK_SPACING + SCREW_HOLE_DRILL_DIAM / 2)\n", "# tracks_f.append(\n", "# draw_arc(angle_A, 80, input_pad_connection_radius, 5)\n", "# + [(PAD_PITCH, pad_connection_point_y)]\n", "# )\n", "# # connect coil B to the middle pad\n", "# tracks_f.append(\n", "# [(0, pad_connection_point_y)] + draw_arc(90, angle_B, INPUT_PAD_RADIUS, 5)\n", "# )\n", "# # connect coil C to the bottom pad\n", "# tracks_f.append(\n", "# [(-PAD_PITCH, pad_connection_point_y)]\n", "# + draw_arc(100, angle_C, input_pad_connection_radius, 5)\n", "# )\n", "# coil_A_f.append(get_arc_point(angle_A, input_pad_connection_radius))\n", "# coil_B_f.append(get_arc_point(angle_B, INPUT_PAD_RADIUS))\n", "# coil_C_f.append(get_arc_point(angle_C, input_pad_connection_radius))\n", "\n", "# vias.append(create_via((-PAD_PITCH, pad_connection_point_y)))\n", "# vias.append(create_via((0, pad_connection_point_y)))\n", "# vias.append(create_via((PAD_PITCH, pad_connection_point_y)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# if we are doing four layers then duplicate the front and back layers on (front and inner1), (inner2 and back)\n", "tracks_in1 = []\n", "tracks_in2 = []\n", "if LAYERS == 4:\n", " tracks_in1 = tracks_b\n", " tracks_in2 = tracks_f\n", "\n", "# dump out the json version\n", "json_result = dump_json(\n", " filename=\"coils_6.json\",\n", " stator_radius=STATOR_RADIUS,\n", " stator_hole_radius=STATOR_HOLE_RADIUS,\n", " track_width=TRACK_WIDTH,\n", " pin_diam=PIN_DIAM,\n", " pin_drill=PIN_DRILL,\n", " via_diam=VIA_DIAM,\n", " via_drill=VIA_DRILL,\n", " vias=vias,\n", " pins=pins,\n", " pads=pads,\n", " silk=silk,\n", " tracks_f=tracks_f,\n", " tracks_in1=tracks_in1,\n", " tracks_in2=tracks_in2,\n", " tracks_b=tracks_b,\n", " mounting_holes=mounting_holes,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the json\n", "plot_json(json_result)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.10.7 ('venv': venv)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.7" }, "vscode": { "interpreter": { "hash": "1ce20143987840b9786ebb5907032c9c3a8efacbb887dbb0ebc4934f2ad26cb3" } } }, "nbformat": 4, "nbformat_minor": 2 }