Tuesday, September 27, 2011

Arduino, Wire, and I2C Part 3: gyro

The SEN-10724 board includes three distinct sensors on it. The linear accelerometer and magnetometer were covered in previous posts. The experience gained in working with those two sensors made it a relatively trivial matter to implement an Arduino sketch to get measurements from the 3-axis gyro.

Set-up of the IC is relatively straight-forward. The defaults are fine in most cases. Because my intent is to eventually couple the measurements from each sensor (the first being the magnetometer and gyro), I configured the data rate to be the same as the magnetometer's default. There is a single register that sets that, the sample rate divisor (SMPLRT_DIV). The datasheet provides a formula (in section 8.2) to derive the desired output rate:
Fsample=Finternal/(divider+1)
F is the frequency (sample rate) in Hz. The internal sample frequency can be either 1kHz or 8kHz. I chose an internal sample frequency of 1kHz to start with, and with the desired 15Hz output rate, that resulted in:
15Hz=1000Hz/(divider+1)
divider+1=1000Hz/15Hz
divider=65

One other piece of initialization needs to be done to take measurements, and that is to set the DLPF_FS (digital low-pass filter/full-scale) register. The "full-scale" part of the register has only one valid value, which is to set the full sale range of the gyros to ±2000°/s. The DLPF portion of the register sets the bandwidth of the filter in conjunction with the internal sample rate. For this test, I picked the value that had the most bandwidth in the 1kHz sample rate. I honestly don't understand what the low-pass filter is being used for, but that's not important right now.

I've made the Arduino sketch available. There's no self-test available, but I can get an idea as to whether the data makes any sense by picking up the board and rotating it around. It seems to work. Also, I can look at the temperature sensor measurements and do a quick sanity check.

According to the table in section 3.1, the reference point for the temperature sensor is -13200LSB=35°C, with 1°C=280LSB (i.e. each bit in the temperature measurement is 1/280°C). The measurements looked fairly stable so I just picked one at random: -15106:
(-15106+13200)/280=-6.8°C + 35°C = 28.2°C = 82.75°F
This seems a tad warm to me, but it's not completely unbelievable.

Now that I've gotten measurements (or at least test measurements) out of each device, it's time to start aggregating the data...

Monday, September 26, 2011

9DOF/SEN-10724: corrections


Looking at the schematic more closely this evening I noticed I'd made a couple of (apparently inconsequential) mistakes. First, the SEN-10724 board has built-in pull-up resistors on the SDA and SCL lines, so those in my circuit were redundant. Second, the board has a voltage regulator to keep the voltage between gnd and Vcc on the devices at 3.3V, so you can actually hook it up to your 5V source and it should still work.

The updated (and simpler) circuit diagram is on the right.

Also, I've attached my (currently very sloppy) Fritzing part to the "code" base, so you can download it.

Edit: Reading the source for the Arduino Wire library, it appears that the library activates the AVR-internal pull-ups for the pins being used to connect to SCL and SDL, apparently making even the resistors on the SEN-10724 board redundant.

Sunday, September 25, 2011

Arduino, Wire, and I2C Part 2: RTFDS

In my last post, I wrote a simple Arduino sketch that would get data from the ADXL345 accelerometer, but the "data" was all zeroes. This time around, I actually go about looking at the data sheet in more detail to learn how to properly get measurements from the accelerometer.

The first mention of enabling measurements is in the "Power Sequencing" section of the datasheet. The second paragraph indicates that to enter measurement mode, you must set the measurement bit in the POWER_CTL register at 0x2D. Another statement worth noting is that the manufacturer recommends configuring the device in standby mode and then enabling measurements. The datasheet also states that the device is in standby mode on start-up. The final bit of important information (for this project) in this section of the datasheet is tables 6 and 7, which contain the bit codes for data output rates.

The next section of the datasheet discusses power consumption as it relates to the data rate of the device. For this project, I'm not going to concern myself with how much power is being used as it's all being powered through the Arduino via USB (note that the Arduino does have a limited amount of supply current, as does USB, but this is in the mA range, where this device is at most using 1/10th of 1mA - 145μA).

Page 10 of the datasheet is where the I2C documentation starts, and that's more or less where I left off previously. Moving forward from here, we reach the section on interrupts. The SEN-10724 board does not have the interrupt pins (8 and 9) connected, so interrupts are not available. That said, the interrupt section does contain interesting information about what you could do with the sensor, if you chose to "wire up" your own board.

Page 13 is the first significant discussion of self-test mode for this device. This seems like a good place to start in terms of learning about the sensor and making sure the board and sensor are both functional. Clearly, there are a number of things to pay attention to. Enabling self-test involves setting a bit in the DATA_FORMAT register (0x31), but based on prior experience, I think it's safe to assume that more is involved than that. The next seven pages of the datasheet cover the details of each individual register, followed by even more information about self-test mode. A fair number of these registers are intended to set thresholds for interrupt functions and can therefore be ignored for any application involving the SEN-10724. For implementing a rudimentary test of the sensor using the Arduino, we need to look at:
  1. DATA_FORMAT - register for enabling self-test mode
  2. BW_RATE - register for defining the data output rate
  3. POWER_CTL - register for turning measurements on
The datasheet curiously enough does not mention sample rate, so it's my assumption that the data rate and sample rate are the same, and it's entirely up to the user to implement any data conditioning.

With the above information in hand, I added the following constants to the previously posted Arduino sketch:
static const uint8_t ADXL345 = 0x53;
static const uint8_t BW_RATE = 0x2C;
static const uint8_t POWER_CTL = 0x2D;
static   const uint8_t BIT_MEASURE = 0x08;
static const uint8_t DATA_FORMAT = 0x31;
static   const uint8_t BIT_SELF_TEST = (1 << 7);
static   const uint8_t BIT_RANGE_2G = 0x00;
static   const uint8_t BIT_RANGE_4G = 0x01;
static   const uint8_t BIT_RANGE_8G = 0x02;
static   const uint8_t BIT_RANGE_16G = 0x03;
static   const uint8_t BIT_FULL_RES = 0x08;
static const uint8_t DATAX0 = 0x32;
The data rate must be at least 100Hz for proper self-test function, according to the datasheet, but the datasheet also indicates that this is the default data rate on power-up, and that low-power mode (which trades cleaner measurements for power levels) is not selected. As such, I'll leave the BW_RATE register alone for now. The datasheet also states that the measurement range be set to the full resolution 16g mode. Note that "full resolution" is a separate configuration bit in of itself. The bits for self-test mode and for the measurement range all reside in the DATA_FORMAT register, so updating that one register should be enough. The datasheet also recommends averaging samples. For this stage of the process, I'm not going to do that, for two reasons: 1) I'm a bit lazy, and 2) the atmega328 chip on which the Arduino UNO is based doesn't even have a divide instruction, so averaging would have to be done using a software implementation of add and sub instructions on the chip. Finally, as far as code changes for this stage of effort, I'm going to cut the delay between reading samples down to match the 100Hz sample rate. The sample rate of 100Hz means that there is an accelerometer measurement every 1/100th of a second, or every 10 ms.

The resulting Arduino sketch, which is also available for direct download, executes and reports on a self-test of the ADXL345 accelerometer. If you open the serial monitor (set to 9600 baud) in the Arduino software, it will show the progress of the register settings (helpful debugging, and left-over from the HMC5883L version of the same, which has a slightly better datasheet IMO) and if the device pasts the tests (measurements are within ranges described in table 2 of the datasheet), it will print the measurements, followed by "OK", then turn off measurement and test mode. Otherwise, it will continue to show measurements and indicate which axis is reading out of spec.

... And that's it. Self test for the ADXL345 (and HMC5883L). The ITG-3200 does not have a self-test mode, so there won't be a follow-up for that.

Friday, September 23, 2011

Arduino, Wire, and I2C

In previous postings, I described connecting a sparkfun.com sensor board to the Arduino and getting magnetometer readings from it.  In this post, I intend to go into a little more detail about how I2C works, while providing similar functionality using the accelerometer on the same board.

The first thing to understand is that I2C is a completely 8-bit bus protocol, which means that all addresses and all data are 8-bit quantities (0-255).  The bus consists of a single "master" with up to 112 unique "slave" devices.  The limit of 112 is due to the fact that a device address is actually a 7-bit quantity (more on this later), and 16 addresses are reserved.  Multiple masters are apparently possible, but aren't relevant for this project.  Slave devices can't talk to other slave devices.  Off-the-shelf devices, like the three on the SEN-10724 board, have pre-assigned addresses managed by a central authority.

Devices have a 7-bit address.  This 7-bit address is stored in the 7 MSB (most significant bits) of an 8-bit quantity when sequenced on the bus, with the least significant bit indicating whether the operation is a read or a write (1 or set being "read").  As an example, the HMC5883L datasheet lists 8-bit addresses 0x3D for read, and 0x3C for write.  Convert these to binary and you'll find a 7-bit address of 0x1E followed by a binary 1 for read or 0 for write.  Some manufacturers will list only a 7-bit address in their data sheet, some will list 8-bit addresses, some will do both.

The datasheet for the Analog Devices ADXL345 lists both - 0x1D is the 7-bit address, which results in 0x3A for writes and 0x3B for reads.  However, it also supports an alternate addressing of 0x53/0xA6/0xA7 if pin 12 is tied to ground. The schematic for the sparkfun board indicates that pin 12 is indeed tied to ground, so let's assume that we're going to be using alternate addressing for that device.

Each I2C device will also have a number of registers that are readable and/or writable.  Table 16 on page 14 of the ADXL345 datasheet lists this device's register map.  For this project, we're most interested in registers 0x32-0x37.  If you look at the table, you'll see how manufacturers design for 16-bit quantities, which is (at least in this case and in the case of the HMC5883L) to have adjacent 8-bit registers containing the most significant and least significant bytes of a 16-bit quantity.

Let's start with the X axis on the accelerometer, which according to the board's silkscreen is the side-to-side direction (along the short edge of the board).  The following Arduino sketch implements a simple program that prints out the X axis acceleration once a second.
#include <Wire.h>
// Wire library uses 7-bit addresses and automatically
// sets the r/w bit
static const uint8_t ADXL345 = 0x53;
static const uint8_t DATAX0 = 0x32;

void setup()
{
  Wire.begin();
  Serial.begin(9600);
}

void loop()
{
  // Put the read address on the bus
  Wire.beginTransmission(ADXL345);
  Wire.send(DATAX0);
  Wire.endTransmission();
  // Request data starting with the X register
  Wire.beginTransmission(ADXL345);
  // 2 bytes gets us the LSB and MSB of the X data
  Wire.requestFrom(ADXL345, (uint8_t)2);
  // store the data in a signed integer quantity
  int16_t x;
  // pointer to use to store the data
  byte *p = (byte*)&x;
  // wait for 2 bytes to be available
  while (Wire.available() < 2) {}
  *p = Wire.receive();
  p++; // advance the pointer to the next 8 bits
  *p = Wire.receive();
  Wire.endTransmission();
  Serial.println(x); // finally, print the quantity
  delay(1000); // delay a bit to avoid flooding
}

If you upload this sketch to your Arduino, with the IMU connected as described in the earlier post, you'll probably get a sequence of zeroes in the serial monitor. If, for example, you tried to change the address in the above code to the primary (0x1D), you probably wouldn't see anything at all. So why is it only showing zeroes, even if I shake the heck out of the board? My guess is that the device doesn't start up in a mode where continuous measurements are taken (which is how the HMC5883L operates), and that to get these measurements, other registers must be manipulated...

To be continued...

Sunday, September 18, 2011

Magnetometer Metal Detection

Part of what motivated me to start playing with the magnetometer was an episode of Deep Sea Detectives I had recently watched on Netflix.  I believe it was the "Damn the Torpedoes" episode, where they used a magnetometer to find a ship that presumably had been buried by silt in a hurricane.  The magnetometer used in DSD was a cesium magnetometer, which is more sensitive, but probably a lot more expensive, even if it were a simple matter for an ordinary person to get their hands on cesium.  The HMC5883L that I'm using has a resolution of 2 milli Gauss, or 200nT (nanotesla).  A cesium magnetometer is probably several orders of magnitude more sensitive.

The plot shows "detection" of metal.  In this case, I moved the remains of my old internal Zip drive (which I had disassembled after viewing a "maker" youtube video along those lines) between the sensor and ground (as in the stuff beneath your feet, not the electrical sense in this case).  The off-scale measurements in the chart are probably an implementation error in the HMC5883L library mentioned in my previous post.  The maximum range of the measurements is -2047 - 2048, so I suspect that the values are being incorrectly converted from the 12-bit measurements to a 16-bit quantity.

The following list, which I unabashedly stole from geometrics.com, should give you an idea of how that rates in a real situation where a marine magnetometer is employed:
Objectflux densitymeasurement range
Ship 1000 tons0.5 to 1 nT800 ft (244 m)
Anchor 20 tons0.8 to 1.25 nT400 ft (120 m)
Automobile1 to 2 nT100 ft (30 m)
Light Aircraft0.5 to 2 nT40 ft (12 m)
Pipeline (12 inch)1 to 2 nT200 ft (60 m)
Pipeline (6 inch)1 to 2 nT100 ft (30 m )
100 kg of iron1 to 2 nT50 ft (15 m)
100 lbs of iron0.5 to 1 nT30 ft (9 m)
10 lbs of iron0.5 to 1 nT20 ft (6 m)
1 lb of iron0.5 to 1 nT10 ft (3 m)
Screwdriver 5 inch0.5 to 2 nT12 ft (4 m)
1000 lb bomb1 to 5 nT100 ft (30 m)
500 lb bomb0.5 to 5 nT50 ft (16 m )
Grenade0.5 to 2 nT10 ft (3 m )
20 mm shell0.5 to 2 nT5 ft (1.8 m)
Of course, their product is trying to do a high-level survey, where the magnetometer is tethered to a ship and is well into the water column.  In my application, I intend to have a hand-held device that I'm moving along the sea/lake bed, so I'm not going to be trying to detect a screwdriver from 4 meters, I'm going to be trying to detect a screwdriver from, maybe 1m.

I have no idea how much one would have to pay for the geometrics magnetometer, but I figure it's one of those situations where "if you have to ask...".

There is still plenty to do to even approach a usable metal detector.  Unlike traditional inductive detectors, magnetometers are measuring minute changes in the magnetic field flux density (no, really, I'm not trying to go all technobabble).  Inductive detectors can detect non-ferrous metals, but have a much lower sensitivity and therefore lower range than a magnetometer.  Obviously, then, the magnetometer is restricted to ferrous (iron-based) metals but can have a significantly higher range of detection.  The other issue, and probably the one more significant, is that any rotation of the sensor along any axis will show up in the data.  The trick will be to distinguish between movement of the detector and actual metal.

Monday, September 12, 2011

"9" degrees of freedom IMU on Arduino

This past weekend I finally got around to playing with the "9 degrees of freedom" sensor board (SEN-10724) I'd purchased from sparkfun.com.  I'm not sure if it makes sense to call it that, but what you get is a 3-axis gyro, a 3-axis accelerometer and a 3-axis magnetometer.  The gyros give you information about rotational acceleration, the accelerometer measures linear acceleration, and the magnetometer works like a 3-axis compass.

The circuit off to the side there shows how I managed to get it hooked up and talking through my Arduino UNO.  The short of it is this: it's a 3.3V device (well, 3 devices) and it needs pull-up resistors (I used 4.7K) on the data and clock lines in order to function.  It's wired up to the analog inputs 4 and 5 on the Arduino board, which is what the provided "Wire" library uses to talk to devices like this, that use the I2C protocol.

Honestly, I'm a little bit unclear as to how it's actually able to talk to the sensor stick/IMU (inertial measurement unit) without doing level conversion between the 5V Arduino and the 3.3V IMU, but it does seem to work reliably in this configuration.  If I were doing something more significant (a production board, for example) I'd probably be a bit more careful about matching the signal levels.

The Wire library for Arduino is just a basic library for talking to I2C devices.  Getting into the specific interfaces is another matter, though for a quick start, I used the HMC588L compass library provided by Love Electronics in the UK.  This was enough to get me started and verify that I was able to communicate with the magnetometer.  There's a pretty decent tutorial on that page on how to use the library, though the sample works pretty well as-is.  If you have trouble compiling the sample code, I found that for some reason it has a period (".") at the very beginning of the file - remove that period to make it compile.

Quick update - looking at the example .pde file in linux using hd (hex dump), it turned out there were three unprintable characters at the beginning of the file.  The easiest way I found to fix the problem was to remove any odd characters before the /* at the start of the file, then save it as a new sketch.  That new .pde file can then be used to replace the original example .pde file in the library, or you can just use the fixed version in your sketchbook.

Sunday, September 11, 2011

George Lucas is one stupid mother fucker.

Just can't leave well enough alone, can you George?  New release, new changes.  Guess I'll be keeping my laser discs and ignoring this new pile of shit: http://www.blu-ray.com/news/?id=7234

Can't fix shitty dialogue or bad acting in episodes 1-3, so let's just fuck everything up with CGI.. oh, and add even more stupid dialogue to the original 3.

Hey George:

Wednesday, September 7, 2011

a whole bunch of leagues...

Digging through Netflix streaming selections this evening, I ran across a movie called "30,000 Leagues Under the Sea"... I had to wonder about that, so I did a bit of research and math.

A "league" is an ill-defined measurement term, but it varies from 3 miles, or 15,840 feet, to 18,240 feet, or in nautical terms, which would make more sense in this context, 1 mile is 6000ft, so a league would be 18,000 ft.

The deepest part of the ocean is 35,838 feet, which is slightly less than *two* leagues.  The original Jules Verne tale was only 20,000 league, but the Earth's diameter is still only about 8000 miles, about 1/7th of the "20,000 leagues"... The circumference of the Earth (and the fact that it was round) had been known since around 200B.C. to be around 25,000 miles, or around 8000 leagues.  Even if the value of pi hadn't been known nor the exact relationship between a radius and circumference, just looking at a circle will tell you that the length of a circle's circumference can't possibly be smaller than its diameter... Which begs the question "what was Jules Verne thinking?"

The director of "30,000 Leagues..." (aptly named Bologna) most likely was thinking "here's an old idea we can recycle and make some money with"...

Thursday, September 1, 2011

"I can't find a pulse!"

"Well dammit, keep looking!"

(this post brought to you by Tired Clichés, Inc.)