Measuring magnetic fields with Arduino

 A digital sensor provides just two states: on and off. Such sensors, then, can only tell you if there is a strong enough magnetic field close to the sensor or not.

In this post I'm going to illustrate the usage of a digital compass, namely a device from Digilent called PmodCMPS, tomeasure the intensity of a magnetic field. The device uses the HMC5883L digital compass IC from Honeywell to measure the three components of a magnetic field along three orthogonal axis.

A magnetic field is a vector field, i.e. it has three components that can be measured along three non parallel axis. Usually those axis are taken as orthogonal to each other. The digital compass is a chip embedded, as usual, in a box and measures the intensity of the magnetic field along the axis aligned along its edges, using the principle of magnetoresistance, briefly illustrated in the previous post. Thanks to its 12 bits ADC's, it provides a very good resolution of 2 mG, and can measure fields up to 8 G. The symbol G stands for Gauss, one of the units to measure magnetic fields. The standard unit is the Tesla (T): 1 T correspond to 10000 G. The average geomagnetic field, exploited by mariners using magnetic compasses, has an intensity of about 0.5 G, i.e. 0.05 mT (milli-Tesla). Common NMR (Nuclear Magnetic Resonance) apparatus use magnetic field of the order of 1-1.5 T.

The Digilent device integrates the digital compass IC with a circuit that allows the user to read the three ADC's value using I2C, one of those many serial protocols to exchange data. A serial protocol is a way to send and receive data between a CPU and a peripheral device using just one single line (an electrical wire) called SDA (Serial DAta). Binary digits travel along this line one after the other in both directions. The communication being serial, it is important that each bit is transferred along the line at regular time intervals. A second line, called SCL (Serial CLock)  is then employed to provide a clock signal allowing both transmitter and receiver to schedule the transmission/reception of signals properly. In order to use the device, then, you just need to feed it with 5 V, and connect the two I2C lines to an I2C enabled device such as an Arduino. The device (in the picture) comes with four pins: two of them must be connected to the ground (GND) and 5 V, respec2366446-40.jpgtively, while the other two pins are marked as SDA and SCL for communication.

You can connect these four pins to the corresponding pins on an Arduino board. The I2C pins location on Arduino depend on the board flavour. On the most popular Arduino UNO board they correspond to A4 and A5 pins. In this post, however, we are going to show how to use the digital compass using an Arduino Leonardo board, where the I2C pins are located on digital pins 2 and 3. See the Arduino - Wire page for details bout other Arduino flavours. The reason for using the Leonardo board is that it comes with built-in USB communication capabilities that makes it appear as an USB keyboard or mouse to a computer, hence we are going to use this feature to show the magnetic field vector on the screen.

First of all, connect the four pins of the digital compass to the corresponding pins on Arduino, then open the Arduino IDE, select the proper port and board, if needed, and start coding. In the picture below you can see the four cables connecting the Arduino board to the device. The need for the fifth (red) cable connecting the 3.3 V pin of the Arduino with its A0 pin is explained below.

DSC_0561.JPG

To communicate with an I2C device you need to use the Wire library, including the Wire.h header file in the sketch. In the setup() method initialise the library using the following code
void setup() {
  Wire.begin();
  
  Wire.beginTransmission(ADDRESS); 
  Wire.write(0x02); //select mode register
  Wire.write(0x00); //continuous measurement mode
  Wire.endTransmission();
}

where ADDRESS has been defined as a constant with
#define ADDRESS 0x1E 

Here 0x1E is the address of the device, as defined by the producer (see the data sheet). The beginTransmission()method tells the Arduino to send data to the I2C device whose address is specified. Data travel then from Arduino to the device until the communication is closed with endTransmission(). We use the write() method to send data over the line. In this case data represents commands or instructions given to the device. I2C device can be controlled via a number of registers, i.e. memory locations on board. Register 0x02 is the mode register, that can be used to configure the working mode of the device. We select the 0x00 mode, corresponding to continuous measurement (those information are found on the data sheet of the device). Now the device starts measuring the magnetic field continuously and we can read the values in the loop(). At its beginning, it reads as follows:

void loop() {
  int x,y,z;
  Wire.beginTransmission(ADDRESS);
  Wire.write(0x03); //select register 3
  Wire.endTransmission();
  ...

Looking at the data sheet, we can see that registers from 3 to 8 contain the data output. Each axis reading is represented by two bytes: there are then three bytes pairs, the first containing the most significant bits of the data and the second the least significant ones. Writing on register 3 we tell the I2C to send us the data contained in it. We should then be ready to receive the data. To do that we use the requestFrom() method. It has two parameters: the address of the device and the number of registers whose content must be transmitted.  The read() method, then, obtains the data when the device is ready to transmit it:
 
  //Read data from each axis, 2 registers per axis
  Wire.requestFrom(ADDRESS, 6);
  if(Wire.available()) {
    x = Wire.read() * 256;
    x += Wire.read(); 
    z = Wire.read() * 256;
    z += Wire.read(); 
    y = Wire.read() * 256;
    y += Wire.read(); 
  }

The first pair to be read is the one containing the value of the magnetic field measured along the x-axis of the device. As said, it is composed of two bytes of which the first is the most significant. The value of this component of the field is then the one read from the first register multiplied by 256, to which we add the value of the second. The second pair contains the value of the field measured along the z-axis, while the last two bytes contain the value of the field measured along the y-axis.

One can then obtain the intensity of the magnetic field computing its magnitude using Pythagoras' theorem, as the square root of the sum of the squares of x, y and z components. Our purpose, however, is to graphically represent the vector in space on the screen of our computer. To do that we exploit the capability of the Leonardo board to simulate a keyboard. We must properly format the data, transforming them into a set of characters to be transmitted to the computer.

Data come in the form of 12-bits integer numbers. For the purpose of illustrating another useful function we are going to scale them of a factor 1000, then convert them into string. We do that with
  char xstr[6];
  char ystr[6];
  char zstr[6];
  dtostrf(0.001*x, 5, 2, xstr);
  dtostrf(0.001*y, 5, 2, ystr);
  dtostrf(0.001*z, 5, 2, zstr);

the dtostrf() function takes four arguments: a floating point number, the maximum number of digits to be use to represent it, the number of non-integer digits to be shown and the address of a long enough array of characters to store the result. We then take each value, multiply it by 0.001, and store its character representation in array of characters with exactly two non-integer digits. The numbers will be then represented with five characters of which two are used for the non-integer part, one for the decimal period and the remaining two for the integer part. We then need arrays made of 6 characters: one of them, in fact, has to be used as the terminator character of the string (the NULL character, marking the end of the string).

In order to show the vector representation on the screen we use GeoGebraan open source mathematics software freely available for download. With GeoGebra you can draw vectors on screen in a variety of ways, of which one is the following: open the 3D graphics view, then define the origin as a point whose coordinates are (0,0,0) typing in the input box

O=(0,0,0)

The, define a second point initially with the same coordinates

A=(0,0,0)

Now you can define a vector typing

vector(O, A)

You should see nothing on screen, since the vector length is zero, but if you change the coordinates of A you can see an arrow starting from the origin of the reference frame and terminating on the point located by A.

With this method we can now use the Leonardo board as the input device for GeoGebra: we simply tell it to send characters to the computer on which GeoGebra is running containing the command to define the coordinates of point A:
  sprintf(cmd, "A=(%s,%s,%s)\n", xstr, ystr, zstr);
  int l = strlen(cmd);
  int ok = analogRead(A0);
  if (ok > 400) {
    for (int i = 0; i < l; i++) {
      Keyboard.write(cmd[i]);
    }
  }

The first sprintf() statement format a cmd string, defined as a long enough array of characters, such that the resulting string will be A=(x,y,z) where xy and z are the content of the xstrystr and zstr obtained above, i.e. the readings of the compass. The second argument of sprintf tells it how to interpret what follows: normal characters are inserted in the string as such, while characters preceded by a % sign are interpreted as descriptors. The %s descriptor tellssprintf() to interpret the following parameters as strings. In plain C, the %n.mf descriptor can be used to format floating point values, where n and m are, respectively, the size of the field and the number of non-integer digits to show (we should have used %5.2f). However such a descriptor is not available on Arduino and we are forced to use dtostrf().

The "\n" characters at the end of the string represent the newline character.

We can then transmit each character forming the string to the computer as if they come from a keyboard with the forloop, that takes each character in the string and transmit it over the USB cable. If the pointer of the mouse has been previously located in the GeoGebra input box, those characters are entered as if they were pressed on a keyboard and the position of the A vector changes continuously if we change the orientation of the digital compass device. Changing A makes the vector to change, as can be seen from the movie below.


We are now going to explain the reason for having the fifth cable on the Leonardo board. Imagine you upload a sketch on the board emulating a keyboard: next time you connect the same board to a computer it starts emulating the keyboard and you can no more type anything on any window. You can, in fact, type characters from your real keyboard, but they will be mixed with those typed by the Leonardo board!

You need a method to suppress this behaviour. In the snippet above you can see that we emulate the keyboard only if the analog reading of the A0 pin of the Arduino board exceeds a given threshold. Leaving the A0 pin disconnected (or better, connected to ground) prevents the board to behave like a keyboard. You can then plug the Arduino to your computer, set up GeoGebra, click on the input box, then connect the 3.3 V pin of the board to the A0 pin: the analog read returns a number close to 3.3×1024/5≈676 and the board starts behaving like a keyboard.

If you make the mistake of uploading a sketch that prevents you to use the board because it continuously transmit characters as a keyboard, scrambling your sketch as soon as you try to upload it, you can reset the board as follows: prepare a simple sketch with just empty setup() and loop() methods, then press the RESET button on the board and, keeping that button pressed, attach the board to the computer using the USB cable. Now, still keeping the RESET button pressed, click on the upload icon on the Arduino IDE and release the RESET button as soon as the word "Uploading..." appears. It may not work soon, because it depends on how fast you are: you can succeed easier if you release and press the button very fast twice.

No comments:

Post a Comment