Introduction

When making electromagnets, it is difficult to know for sure what kind of magnetic flux density has been achieved. This is because it depends a lot on practical construction factors, where on the electromagnet it is measured, and the core material permittivity. There are commercial measurement devices, known as gaussmeters, but the good ones usually cost upward of $150. Furthermore, those $150 units are basic – they have a response up to 300Hz (for changing magnetic fields) and do not report the actual frequency either.

 

For this project, I wanted to build a low-cost gaussmeter, and hopefully try to add those missing features, and also aim beyond 300Hz to a few kilohertz. For sure I expected it to be more inaccurate compared to a commercial device, but I thought it might be good enough, and it was worth an attempt (I love Project14 for this reason - it gives us a good excuse to build shorter, experimental projects if we want to).

 

This blog post describes the project, and details on how to build it.  The 2-minute video here compares the operation with a commercial device, and shows the results using a homemade electromagnet operated at different strengths.

 

Who would this project benefit? Basically, anyone curious about magnetism and measuring it. This project would also be useful for school physics labs, since it is a lot cheaper to make than to buy a commercial tool (a few tens of $ USD). It might also be useful for measuring the speed of rotating objects, although I have not tested that. There is another project14 project for that anyway.

 

How does it work?

The project is simple to construct. It consists of a single-chip Hall-effect magnetic sensor, and it outputs a few millivolts per Gauss. Magnetic flux density is measured either in Gauss, or Tesla. A typical small magnet as found in earphones, may have a flux density of a few tens of milliTesla (10 Gauss is equal to 1 milliTesla).

 

The output from the sensor is buffered using an op amp, and then read by an analog-to-digital converter (ADC) in a microcontroller, and output to an LCD screen. The microcontroller is an ST ARM Cortex-M0 part, on a compact development board called the NUCLEO-F031K6NUCLEO-F031K6. This tiny board physically looks like an Arduino Nano. It is easy to program. When plugged in to the PC via USB, it virtually looks like a USB memory drive. There is an online cloud service called IBM mbed – once you’re logged in, a window appears in a browser where you can type code, click to compile, and the browser will download the binary file onto the desktop. Then, the file can be dragged into the memory drive. This has the effect of programming up the chip! Mbed is super-smooth to use, I love it.

 

The mbed system works with many different development boards. For a more in-depth look at mbed, see here: Working with FRDM Boards and ARM mbed (much of the information there is relevant to many vendor boards, although that blog post is focussed on FRDM boards from NXP).

 

Building It

The circuit is shown here. The resistor values can be adjusted according to the sensor datasheet (PDF), up to 10,000 Gauss unipolar measurements (0.4mV/Gauss from the sensor). With the current settings, the output from the sensor is 6.28mV/Gauss, centred for bipolar use. The project was constructed on breadboard, but if anyone is interested then I’ll create a PCB design file for it so we can all easily have Gaussmeters if we want them.

 

I built the sensor and buffer portion on a separate breadboard since I didn’t have a large enough single breadboard for the whole project.

 

The LCD module has pins on a 1.27mm pitch, so I used a breakout board intended for 1.27mm pitch ICs, to get it to fit the breadboard.

I’d not used a NUCLEO-F031K6NUCLEO-F031K6 board before but I like it. It is low-cost and has 32kbytes of Flash memory and 4kbytes RAM and all the basic serial interfaces (I2C, SPI, UART).

 

The board can either be powered via USB, or alternatively a 9V PP3 battery and switch could be attached to pin 1 and pin 2 of the ST development board, for a portable version of this project.

 

Coding It

The code is listed below, it is a single file.

// ***************************************************
// * Magnetometer for NUCLEO-F031K6
// * Rev 1.0 shabaz April 2019
// ***************************************************


// ******* includes ******
#include "mbed.h"


// ***** definitions *****
// MTESLA = 3300 / (mv_per_gauss * 10)
//        = 3300 / (4.06*10)
#define MTESLA 81.281
#define TICKPERIOD 1.0
#define HYST 1.0
#define LCD_RES_HIGH lcd_reset=1
#define LCD_RES_LOW lcd_reset=0
#define LCD_ADDR (0x3e<<1)


// ******** constants ****
const char lcd_init_arr[]={ 0x38, 0x39, 0x14, 0x79, 0x50, 0x6c, 0x0c, 0x01 };


// ******* globals *******
AnalogIn analog_value(A0);
DigitalOut lcd_reset(PA_12);
I2C i2c(PB_7 /*SDA*/, PB_6 /*SCL*/);
DigitalOut led(LED1);
Ticker mainTick;
static int state_init=1;
int ac=0;
int totsamp=0;
int transitions=0;
int transition_state=0;
float maxv=0.0;
float minv=0.0;
float avg=0.0;
float high_thres;
float low_thres;
float sumv=0.0;
char tbuf[17];
char tbuf2[17];
char do_print_1=0;
char do_print_2=0;
char led_state=0;


// **** LCD functions ****
void
lcd_init(void)
{
    LCD_RES_HIGH;
    wait(0.05);
    LCD_RES_LOW;
    wait(0.05);
    LCD_RES_HIGH;
    wait(0.05);
    int i;
    char data[2];
    data[0]=0;
    for (i=0; i<8; i++)
    {
        data[1]=lcd_init_arr[i];
        i2c.write(LCD_ADDR, data, 2);
    }
}


void
lcd_clear(void)
{
    char data[2];
    data[0]=0;
    data[1]=1;
    i2c.write(LCD_ADDR, data, 2);
    wait(0.002);
}


void
lcd_setpos(char ln, char idx)
{
    char data [2];
    data[0]=0;
    if (ln==0)
        data[1]=0x80+idx;   // 0x00 | 0x80
    else
        data[1]=0xc0+idx;   // 0x40 | 0x80
    i2c.write(LCD_ADDR, data, 2);
}


void
lcd_print(char* str)
{
    char data[2];
    data[0]=0x40;
    while(*str != '\0')
    {
        data[1]=*str++;
        i2c.write(LCD_ADDR, data, 2);
    }
}


// **** tick routine ****
void
tick(void)
{
    float trans_per_sec;
    float value;
    
    
    if (state_init==1)
    {
        state_init=0;
        return;
    }
    
    
    avg=sumv/totsamp;
    

    
    if (ac)
    {
        if (led_state)
        {
            value=minv;
            sprintf(tbuf, "min  ");
        }
        else
        {
            value=maxv;
            sprintf(tbuf, "max  ");
        }
        if (avg<0.0)
        {
            sprintf(tbuf+4, "%.1f mT", value);
        }
        else
        {
            sprintf(tbuf+5, "%.1f mT", value);
        }
        trans_per_sec=transitions/TICKPERIOD;
        sprintf(tbuf2, "%.1f Hz", trans_per_sec);
        do_print_1=1;
        do_print_2=1;
    }
    else
    {
        if (avg<0.0)
        {
            sprintf(tbuf, "%.1f mTesla", avg);
        }
        else
        {
            sprintf(tbuf, " %.1f mTesla", avg);
        }
        do_print_1=1;
    }
    
    if ((maxv-minv)>HYST)
    {
        high_thres=avg+((maxv-avg)/4);
        low_thres=avg-((avg-minv)/4);
        ac=1;
    }
    else
    {
        ac=0;
    }
    
    transitions=0;
    sumv=0.0;
    totsamp=0;
    maxv=avg;
    minv=avg;
    
    led=led_state;
    led_state^=0x01;
    mainTick.detach();


}




// ***** main function *****
int main()
{
    float meas_r=0.0;
    float millitesla;


    float null_offset=0.0;
    int i;
    
    lcd_init();
    
    lcd_clear();
    lcd_setpos(0,0);
    lcd_print("Hello");
    
    // null calculation at power-on
    for (i=0; i<1000; i++)
    {
        null_offset += analog_value.read();
    }
    null_offset=null_offset/1000;
    
    // initialize minv and maxv
    minv=0.0;
    maxv=0.01;
    
    mainTick.attach(&tick, TICKPERIOD);


    while(1) {


        meas_r = analog_value.read()-null_offset; // Read the analog input value (value from 0.0 to 1.0 = full ADC conversion range)
        millitesla=meas_r*MTESLA;
        
        sumv+=millitesla;
        totsamp++;
        if (millitesla>maxv) maxv=millitesla;
        if (millitesla<minv) minv=millitesla;
        
        if (ac)
        {
            if (millitesla>high_thres)
            {
                if (transition_state!=1)
                {
                    transition_state=1;
                    transitions++;
                }
            }
            else if (millitesla<low_thres)
            {
                if (transition_state!=0)
                {
                    transition_state=0;
                }
            }
        }
        
        if (do_print_1)
        {
            lcd_clear();
            lcd_setpos(0,0);
            lcd_print(tbuf);
            do_print_1=0;
            if (do_print_2)
            {
                lcd_setpos(1,0);
                lcd_print(tbuf2);
                do_print_2=0;
            }
            // if we had to print, then the ticker needs restarting
            mainTick.attach(&tick, TICKPERIOD);
        }


    }
}

 

 

At startup, the code briefly reads the analog input in order to provide an auto-nulling capability. This means that the device should only be powered up when it is not close to a strong magnetic field (Note: in reality, the nulling operation will be affected by the earth’s magnetic field too. Really the sensor would need to be placed inside a ‘zero Gauss’ chamber during power-up).

 

Next, the code reads the analog input repeatedly as fast as possible, and stores some statistics such as the max and min measurements. These are used to implement a type of ‘slicer’ in software. In the data communications world, a slicer is used to decipher an incoming signal. The average value is used to adjust an amplifier, and comparator circuits with hysteresis are used to detect the transitions to determine binary ones and zeros. I did the same thing but in software, and instead of storing the stream, the code just counts the positive going transitions. The count is used to determine the frequency, and it should be good for several kilohertz, but this is still a freshly coded project and I only tested at 50Hz so far.

 

An alternative approach would have been to implement a Fast Fourier Transform (FFT) but a slicer is easier.

 

To use the code, log into mbed (create a free account if you don’t already have one), select the NUCLEO-F031K6NUCLEO-F031K6 board and add it to the account, and then click on Compiler. Create a new project targeted for the NUCLEO-F031K6NUCLEO-F031K6 board, and in the main file, you can copy-paste the code and then save it. Click on compile and if all goes well, a binary file will get downloaded to your PC.

 

Plug the USB cable into the PC, and a drive letter should appear, just like as if it were a memory stick. Drag the binary file onto the drive letter. That’s it! The code should now be running on the board.

 

Testing It

As a first test, I built a small electromagnet (just over a hundred turns of wire around a soft iron shaft) and connected it to a DC power supply, and checked it using a commercial gaussmeter to measure the magnetic flux density at the end of the electromagnet. I did this for different applied voltages, and noted down the current and the measured value in milliTesla.

 

To make the electromagnet, I find marking every 10 turns with a speck of paint from a marker helps to keep count.

 

Next, I tested the project. The measured values are very similar across the range that I tested! The error was around +- 1 milliTesla, and a portion of that will be due to the position difference. I didn’t clamp or build a jig to hold things in place. It is just an initial sanity test, but it is very promising.

I also noticed my pliers are slightly magnetized.

 

In order to test changing magnetic fields, I connected a reel of wire up to an AC mains transformer, and put the sensor breadboard on top (I later found I didn’t need to do that – the small mains transformer emanated enough of a magnetic field that it can be measured with this project, so I didn’t really need the reel of wire). The software detected that the magnetic field was changing, and it then started displaying the frequency, and min and max values in milliTesla.

 

Summary

This project functions but could do with some more code enhancements. It would be trivial to add a button and allow conversion between Gauss and milliTesla (currently it only displays in milliTesla). I’m tempted to do a PCB design and turn it into a handheld device. However, I don’t know for sure yet how I’d want to expose the sensor – perhaps on the end of a PCB, or a wand-like design like the commercial unit could be more practical – and I also need to find a suitable enclosure to design for.

 

It was also my first project with an ST microcontroller. I love that the dev-board is compact and low-cost. I hope to use it again in future projects.

Thanks for reading!