3-Axis Coil Design and Interesting Tangents

A project log for 3D Magnetic Field Scanner

Capture interactive models of magnetic fields with your 3D printer

Ted YapoTed Yapo 07/07/2016 at 00:127 Comments

I got temporarily obsessed with designing a 3-axis field coil to calibrate the MLX90393 sensor. A 3-axis coil would allow all three axes to be calibrated with the sensor in a single position. This allows for easy detection and compensation of angular errors: not aligning the sensor exactly with the coil axes. In addition, a 3-axis coil would make it easy to register the magnetic coordinate system of the sensor into the spatial coordinate system of the printer - this is sure to be slightly different each time you mount/detach the sensor head.

I started playing with 3-axis Helmholtz designs, but for multi-turn windings, the required spacing makes for a clunky design. Here's one of the mock-ups I was playing with:

The size of the windings also allows only a very small active volume, and the nested configuration complicates the mechanical design (which I didn't complete). I started to wonder how important the spacing was in the Helmholtz design. If the spacing constraints could be relaxed, I could use something like this:

Here, the windings are farther apart than in the Helmholtz design. This allows the windings to be on the cube faces, which are all the same shape, and all the parts can be conveniently printed "flat" on the printer bed. I printed a few of these, and they're fun to snap together (the spool caps have to be glued). Mechanically, this is a win, but I had no idea what this does to the field, so some simulations were in order.

Unfortunately, the math I'd done so far could only calculate the field along the coil axis; the field in the full volume of the coil was unknown. I downloaded some finite-element software ( Elmer, a general-purpose package, and MaxFEM, aimed solely at magnetics) and played around a bit. These are powerful tools with steep learning curves, and while I'm going to keep at it and learn to use them eventually, using them for my immediate needs feels like slicing bread with a chainsaw. So, I decided to write the "bread knife" of field coil design.

I lucked out and found the paper Simple Analytic Expressions for the Magnetic Field of a Circular Current Loop on the NASA Technical Reports server. This paper provides equations for the full vector field surrounding a loop of current, and the equations are fairly simple, in part because the required elliptic integrals are conveniently hidden. To my great joy, I found that the python scipy package contains routines for numerical evaluation of these integrals, greatly simplifying field calculation. Since the field obeys superposition, these equations can be used to calculate the field surrounding any 3D configuration of circular current loops - which covers just about any coil I might think of using. I was able to create the core of a python simulation fairly quickly; it's less than 100 lines of code. Having the simulation as a library is very convenient - you can program all sorts of analyses and what-if's easily.

Here's the model for the "filamentary" current loops simulated:

The loop is assumed to be a conductor of infinitesimal diameter, centered in 3-space at position p, of radius a, with normal n, and carrying a magnetizing current NI. For normal "magnet wire" in relatively large loops, this model will work very well.

The "loopfield" code is here on GitHub, as well as on PiPY ('pip install loopfield' gets you a copy, but check out the examples on GitHub). It requires matplotlib for the plotting functions, but the core engine just uses numpy and scipy. Usage is very easy - here's code to calculate the vector field for a single-turn coil:

#!/usr/bin/env python3
import loopfield as lf
# create empty field with specified units
field = lf.Field(length_units =,
                 current_units = lf.A,
                 field_units = lf.uT)
# single-turn 10 cm x-oriented coil at origin
position = [0., 0., 0.]
normal = [1., 0., 0.]
radius = 10.
current = 1.
c = lf.Loop(position, normal, radius, current)
# add loop to field
# evaluate vector field at origin
B = field.evaluate([0., 0., 0.])
print('B = ', B)

You can add more loops and evaluate the field at more points as required. The package will also plot a 2-d slice of the field, with field lines, colored areas based on user-supplied criteria, and simple volume annotations. There appears to be a bug in the streamplot function of the matplotlib python package which would crash occasionally producing these plots. I made a temporary fix (aka quick hack) in my local matplotlib, and will submit an official patch once I figure out how to fix it properly.

I started with modelling the coils I had already wound. Here's the Helmholtz coil, simulated with two loops in place of the multi-turn windings. The field at the center of the coil agrees with earlier calculations. The red region represents the volume in which the x-component of the field is within 1% of the central value; it shows the well-known "octopus" shape. I added a hand-tuned circle of radius 12.5mm to illustrate that the usable volume (at 1% error tolerance) encloses a 25mm diameter sphere in the center of the coil. The field is sampled at 101 x 101 points to generate this plot, which takes about five seconds to compute on my desktop.

When I simulated this coil before, I had questions about how the "large" windings affected the field. Here's the answer - I modeled the actual 100 loops per winding with the same as-built measurements I had for the simple wxMaxima calculations. This calculation (again using 101 x 101 field samples) takes a little over a minute to compute. I used a rectangular annotation, since positioning errors for the probe along the three axes are likely to be relatively uncorrelated (the error region will be square, not circular). Other than my choice of different annotations, the 1%-tolerance regions are nearly identical, showing that the "large" windings are not producing significant error.

While I was at it, I simulated the original Maxwell coil I had wound. The sub-1%-error region is indeed larger than the Helmholtz design and exhibits a [insert 12-armed animal name] shape.

Cube-Coil Fail

Oh, I almost forgot why I wrote this simulator. I wanted to see what the effect moving the windings out to the cube faces had on the field. Simulating just one axis of the coil (we could do all three here), we get the following:

The blue region is the same size as the one on the Helmholtz design above - and the coil radius is identical. That Helmholtz dude knew what (tf) he was doing! Clearly, this coil design is a bust, despite the nice mechanical properties. I'll have to come up with another one. At least now I have a tool to evaluate them.

Other Uses

While these plots (and the loopfield.plot module) are all in 2D, the core of the code calculates the field in 3D. This brings me to the other reason I wanted to write this simulator - I can use it to generate simulated scan data. With a little more code, I can write out simulated 3D scans for fields around various coils, then compare the simulations with actual data from the scanner. This will provide a way to check the scanning error.

Next Up

I have a few experiments to do with the MLX90393 to check temperature calibration and accuracy on the second part I have wired up. I'm also making some decent progress on the scanning and viewing software so I should be able to crank out a few interesting test scans in the next few days.

ADDENDUM (7/7/2016)

I tried installing the loopfield python package on an older linux distro, and ran into some trouble. Here's a recipe for installing it and the required dependencies in a python3 virtualenv, so it doesn't conflict with the python modules installed by the package manager. It was tested in a Linux Mint distro, so you may have to install different packages for different distros:

# you need to be able to compile FORTRAN code to install scipy
# and have the lapack and blas system libraries installed
apt-get install python3-dev
apt-get install python-virtualenv
apt-get install libblas-dev
apt-get install liblapack-dev
apt-get install gfortran
# create a virtual python environment to install into
virtualenv -p python3 LF
# enter the environment (you need to do this in every shell)
source LF/bin/activate
# install loopfield and all the dependencies
pip install loopfield

that last line takes a *long* time to run, having to compile a lot of FORTRAN code. It also prints what appear to be compile error messages to the screen as it compiles scipy, but in the end, reports that everything was installed, and it runs fine.

The patch for matplotlib can be applied as follows:

patch -p0 <<EOF 
--- LF/lib/python3.4/site-packages/matplotlib/ 2016-07-07 09:32:47.000000000 -0400
+++ LF/lib/python3.4/site-packages/matplotlib/ 2016-07-07 10:35:22.911117920 -0400
@@ -368,7 +368,9 @@
                 self._mask[ym, xm] = 1
                 self._current_xy = (xm, ym)
-                raise InvalidIndexError
+                #raise InvalidIndexError
+                pass
 class InvalidIndexError(Exception):
The matplotlib streamplot() function I use to render field lines uses the efficient python exception handling mechanism to implement backtracking when calculating field lines. It's a nice technique I've seen elsewhere, but in this case it looks like there's a codepath which doesn't handle the exception, so it gets passed all the way up to us.


zack wrote 10/25/2019 at 21:50 point

Since the wrapping of the coils are not at the same position:

I tried:

c1 = lf.Loop([-R/2-0.5, 0, 0], [1, 0, 0], R, 1)
c2 = lf.Loop([+R/2+0.5, 0, 0], [1, 0, 0], R, 1)

by adding +-0.5 position of the loops due to loops' position, and the result is:

I was wondering if the fields coming from those loops that are not R/2 distance from the sensor, effect the result of the measurement ?

  Are you sure? yes | no

shield543_fb wrote 01/17/2018 at 14:40 point

Very nice plots. I haven't tried simulating it myself yet, but just out of curiosity, does the radius of that region of homogeneity scale linearly with the radius of the Helmholtz coils?

  Are you sure? yes | no

Ted Yapo wrote 02/18/2018 at 18:47 point

Sorry, just saw this comment; notifications seem broken.

Yes, the radius of the region scales linearly.  But, the field intensity in the region drops off inversely with the radius of the coils, so to get the same field in the larger region, you need more magnetizing current (NI).  You can either add more windings or increase the current, or both.

  Are you sure? yes | no

PointyOintment wrote 09/12/2016 at 08:59 point

I had an idea: Interlock your Helmholtz coils in the manner of a monkey's fist knot. I think—though as someone who doesn't intend to do the work ;)—that it wouldn't be hard to extend loopfield to calculate and superimpose fields from partial circles and straight line segments. I think it would be hard to physically construct it, though.

  Are you sure? yes | no

Ted Yapo wrote 09/12/2016 at 13:26 point

I'm not so sure: the winding might be the easy part of this one :-)

I was fortunate enough to find the analytic solution for loops already derived.  Check out this guy who did the math himself:

The derivation itself is summarized in:

Looks pretty hairy, but maybe it wouldn't be too bad to extend now that I've seen the derivation for the closed loop - I'd expect incomplete elliptic integrals to pop up, but I think scipy has calls for evaluating those as well...OK, maybe the winding would be the tough part.

I did think at one time of just having three intersecting coil forms of the same radius, but abandoned it as the coils would all have to be wound at the same time with the windings interleaved - I think you'd need a custom winding jig (or six hands) to actually make one.

  Are you sure? yes | no

peter jansen wrote 07/07/2016 at 06:05 point

"That Helmholtz dude knew what (tf) he was doing!" is my favorite quote that I've seen on hackaday in the 10 years I've been reading it!  But seriously, this is great work, and I'm excited to try this modeling software!

  Are you sure? yes | no

Ted Yapo wrote 07/07/2016 at 14:00 point

Thanks!  If you run the code, I'd recommend using the virtualenv recipe I just added above (in linux at least).  Not sure how virtualenv works in Windows.

  Are you sure? yes | no