Sparkfun 9dof Block

The Sparkfun Blocks

I’ve started experimenting with the SparkFun blocks for Intel Edison. To use them, you’re going to need a Base Block or a Console Block. If you’re deciding which one to get let me save you the trouble…buy the Base Block. While the console block will allow you to connect to other blocks and tether to your Edison, it won’t let you access Edison as a mass storage device, upload Arduino sketches, or connect other peripheral USB devices (webcams, usb headsets, etc.). For $4 more the base block is definitely worth it.

image2

Hooked up to the Intel Edison Base Block

 

For this experiment I’ll be hooking up the 9dof block to the console block and reading the values from it. A quick image of the 9dof block is shown below.

image1

The 9dof block. The Edison Base Block is connected on the other side.

You might notice that the 9dof block page contains no libraries and very little hardware information. In a nutshell, the 9dof block contains the LSM9DS0 sensor from STMicroelectronics. The datasheet can be found here. The sensor has a combined 6-axis accelerometer and magnetometer and a separate 3-axis gyroscope. The 6-axis accel/mag also has an integrated temperature sensor. The LSM9DS0 is the same sensor that resides on many 9-axis IMU arduino breakouts, including the ones from Adafruit and Sparkfun.

These breakouts have associated Arduino libraries and there are a few other C/C++ github repositories that people have developed, mostly for flight controllers. I’ve listed a couple that will work below…some require more work than others to run them on Edison. The ones listed as Arduino require you to load the library into Intel’s Arduino IDE and run it from there:

  1. Adafruit Library for Arduino
  2. Sparkfun Library for Arduino
  3. Library for Interfacing with Teensy
  4. A C library written for Intel Edison
  5. An expansive C library for 9-axis IMU sensors on different platforms [Note: also has python bindings]

Setup and Configuration

I didn’t find any stand-alone python libraries for the 9dof block and, to be honest, I just like figuring things out on my own. So, I started investigating writing my own library for python.

The next few paragraphs describe a bit of setup in order to get my library to work. If you’re looking for an easier solve, download my entire repo from github and run the shell script for these installs with

root@edison:~# sh ./dependencies.sh

That’ll take care of everything in one shot, so long as you’re on a recent build and connected to the internet. For the detailed manual instructions, read on.

I’m going to connect to the 9dof block using the I2C interface on Edison. Writing these connections is cumbersome, so we’re going to use Intel’s mraa library and python bindings to handle the connection and protocol for us. If you don’t have Intel’s mraa library installed yet, install it with

root@edison:~# echo "src mraa-upm http://iotdk.intel.com/repos/1.1/intelgalactic" > /etc/opkg/mraa-upm.conf
root@edison:~# opkg update
root@edison:~# opkg install libmraa0

Obviously, make sure you’re connected to the internet.

We’re going to be shifting and recombining bytes read from our sensor into signed integers. Python doesn’t like signed integers, it defaults to unsigned. While you could write a function to handle all this shifting with the base python modules, easier is to exploit Numerical Python (numpy), which has many more types implicit to it, including signed integers. So, let’s first install numpy using Alex T’s amazing repo. If you don’t already have it, you can add the repo to your opkg package manager:

root@edison:~# echo "src/gz all http://repo.opkg.net/edison/repo/all 
src/gz edison http://repo.opkg.net/edison/repo/edison 
src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32" >> /etc/opkg/base-feeds.conf
root@edison:~# opkg update

Then installing numpy is as simple as:

root@edison:~# opkg install python-numpy

Connecting to 9DOF Block using the Python Shell

After install, open up your python prompt:

root@edison:~# python
Python 2.7.3 (default, Dec  9 2014, 18:06:54) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

First thing is to import the mraa library and connect to our device over I2C

>>> import mraa as m
>>> x = m.I2c(1)

The accel/mag (XM) and gyro (G) are separate, so let’s define the byte addresses of each sensor now

>>> G = 0x6B   # gyro
>>> XM = 0x1D  # accel/mag/thermometer

To check for the connection to XM and G, check the register 0x0F. You should receive the XM response 0xD4 = 212L and the G response 0x49 = 73L if you’re hooked up and talking properly:

>>> WHO_AM_I = 0x0F
>>> x.address(XM)
>>> x.readReg(WHO_AM_I)
73L
>>> x.address(G)
>>> x.readReg(WHO_AM_I)
212L

Great. So we’re all connected and everything is working well. Now we need to configure the settings for our accel, mag, and gyro. To enable the accelerometer at 100 Hz continuously on all three axes write the bytes 0x67 and 0xF0 to the registers 0x20 and 0x24 respectively. Be sure to first set the I2C address to the accel/mag sensor.

>>> x.address(XM)
>>> x.writeReg(0x20, 0x67)
>>> x.address(XM)
>>> x.writeReg(0x24, 0xF0)

We use the same idea to enable the mag and gyro to log continuously:

>>> # Enable the mag continuous
>>> x.address(XM)
>>> x.writeReg(0x26, 0x00)
>>> # Enable the gyro continuous
>>> x.address(G)
>>> x.writeReg(0x20, 0x0F)

If you’re interested, we can also configure the temperature sensor to log at the same rate as the mag

>>> x.address(XM)
>>> tempReg = x.readReg(0x24)
>>> x.address(0x1d)
>>> x.writeReg(0x24, tempReg | (1<<7))

For a full list of the configuration setting and registers, have a look at the product datasheet.

The default range for the accelerometer is +/- 2Gs, for the mag is +/- 2 Gauss, and for the Gyro is +/- 245 degrees per second. An example of how to set the accelerometer to 2Gs is given below. To set a different range, simply replace the line “Arange = ” with the corresponding range provided up top in the comments.

# Range the accel
#  2G: Arange = (0b000 << 3)
#  4G: Arange = (0b001 << 3)
#  6G: Arange = (0b010 << 3)
#  8G: Arange = (0b011 << 3)
# 16G: Arange = (0b100 << 3)
Arange = (0b000 << 3)    # 2 Gs
x.address(XM)
accelReg = x.readReg(0x21)
accelReg |= Arange
x.address(XM)
x.writeReg(0x21, accelReg)

Similar code for the mag and gyro, with comments listing other possible ranges:

# Range the Mag
#  2GAUSS: Mrange = (0b00 << 5),  // +/- 2 gauss
#  4GAUSS: Mrange = (0b01 << 5),  // +/- 4 gauss
#  8GAUSS: Mrange = (0b10 << 5),  // +/- 8 gauss
# 12GAUSS: Mrange = (0b11 << 5)   // +/- 12 gauss
Mrange = (0b00 << 5)    # 2 Gauss
x.address(XM)
magReg = x.readReg(0x25)
magReg &= ~(0b01100000)
magReg |= Mrange
x.address(XM)
x.writeReg(0x25, magReg)

# Range the gyro
#  245DPS: Grange = (0b00 << 4),  // +/- 245 degrees per second rotation
#  500DPS: Grange = (0b01 << 4),  // +/- 500 degrees per second rotation
# 2000DPS: Grange = (0b10 << 4)   // +/- 2000 degrees per second rotation
Grange = (0b00 << 4)
x.address(G)
gyroReg = x.readReg(0x23)
gyroReg &= ~(0b00110000)
gyroReg |= Grange;
x.address(G)
x.writeReg(0x23, gyroReg)

Finally, we’re on to reading the data from our sensors!

Data from the accel, mag, and gyro comes in as 6-bytes apiece: the first 2 of each are the x-axis data, the next 2 are the y-axis data, and the last 2 are the z-axis data. To grab the 6-bytes for any one sensor, set the address and then read 6-bytes from the corresponding register. For the accel, this looks like

x.address(XM)
data = x.readBytesReg(0x80 | 0x28, 6)

Then we need to take the 2-byte pairs, bit shift the high-byte by 8 and combine it with the low byte.

x = np.int16(data[0] | (data[1] << 8))
y = np.int16(data[2] | (data[3] << 8))
z = np.int16(data[4] | (data[5] << 8))

And finally, we need to calibrate the data to actual units. The calibration for the accelerometer at 2Gs is 2 / 32768.0 – the positive range divided by the maximum positive 2-byte signed integer units. If you were using the 8G range, you’d use a calibration factor of 2 / 32768.0. Basically, it’s always a trade-off between range and precision: higher range gives lower precision and vice versa.

Going Further

Alright! That’s it for this lesson on SparkFun’s 9dof block. Of course, it’s not super fun to code hardware by reading and writing registers by hand, so I’ve created a library and posted it on github that does all this stuff for you. An example script and instructions for use are also given in the repo. Happy hacking!

Posted in Edison, Tech
9 comments on “Sparkfun 9dof Block
  1. Chris says:

    I recently got an Edison along with the 9dof board and this page is easily the best information around for using it with Python, thanks!

    A couple questions:

    1) “To enable the accelerometer at 100 Hz continuously on all three axes write the bytes 0x67 and 0xF0 to the registers 0x20 and 0x24 respectively.” Isn’t 0x24 for the temperature sensor? A few lines down you set 0x24 again to the same thing with `x.writeReg(0x24, tempReg | (1<<7))`

    2) How did you determine the byte address of each sensor? I don't see it in the docs anywhere. I see 0x6B and 0x1D with `i2cdetect -y 1 -r` but I don't see how you interpret that for one to be the gyro and on the mag/accel/temp.

    • Stephanie Moyerman says:

      Hi Chris –

      Thanks for the endorsement :)

      (1) This could be an error, but I believe that the temperature sensor is embedded with the accelerometer, which is why you’re writing to the same I2C address for both of them. They are physically the same sensor on the line.
      (2) Ahhhhh a huge pain in the butt here. It’s really hard to track this all down, since the documentation really isn’t there. So, this uses the LSM9DS0 (http://www.st.com/web/en/catalog/sense_power/FM89/SC1448/PF258556 – pages 38-41) and you can go directly from the datasheet for I2C addresses and calibration information. Alternatively, you can look in the libraries for Arduino that also use this block, such as https://www.sparkfun.com/products/12636 or http://www.adafruit.com/products/2021 or just try to find some other home grown libraries that list the registers. I know it’s kind of a pain :-/

      Hope this helps.

  2. Chris says:

    3) I think I get how the calibration factor is, “the positive range divided by the maximum positive 2-byte signed integer” but I have no idea how to get the temperature. I’m not seeing that info anywhere in the datasheet. Playing around with it, it sort of looks like it’s coming out in Fahrenheit already. What do you think?

    • Chris says:

      Edit: never mind about the Fahrenheit comment. I thought maybe because it’s reading about 60 when sitting out. I put it in the freezer and it’s reading crazy numbers.

      • Thomas says:

        Why not take a look at the Raspberry Pi? A little more work to coecnnt to things, but it’s as cheap as Arduino, runs Linux, and you can program it in Python.

    • Jean says:

      good tip, thanks. they seem to be sold out eeehywrvre. or i would order, the ability to run linux and python is awesome. i would like some integrated I/O pins like the arduino boards provide oh and hey I see you are working on the I/O port stuff! Let me know when it is available and I will try out

  3. Kehlin Swain says:

    Hey thanks for the write up! Is there anyway we could use tightvnc with yocto to create a gui, and use visual python to model data graphically?

  4. Stephen Brown says:

    Great work Stephanie!

    I’ve been getting my new Edison up and running and using python to eventually replace a raspberry pi in a geophysical magnetometer instrument I’ve built (see http://www.aprovechar.org/blog). I’ve had trouble all day long following part of your “example.py” … in particular reading the temperature. I don’t really care about the temperature as much as just making sure things work. I discovered an oversight in your library … the temperature values direct from your code (12 bit two’s-complement value scaled by 1/8) also needs the baseline value of 21.0 deg-C added, then things seem ok. I gleaned this from an arduino c library I found a link to. Maybe this is what the commenter “Chris” was having trouble with too. Also along these lines, the magnetometer will undoubtedly have offsets too from the well-known hard and soft iron effects due to your wiring, etc (see my blog — this means a calibration is required by rotating the device 360 degrees about all axes in the presence of the Earth’s field and fitting a sphere, or more generally an ellipsoid, to the results to find the principal axes)

    • Stephanie Moyerman says:

      Hey Thanks Stephen! I’ve been (woefully) painfully slow at blogging and responding to comments since I got sucked down the work rabbit hole. I will adjust my code with the +21 degrees and re-post here and on the github repo.

      Definitely understand about calibration. It’s a great idea to add a utility like this to the library. If I find the time to do it, I think I’ll add utilities for calibration on accel (gain and offset along all 3 axes), calibration for gyro (offsets for all 3 axes), and magnetometer (as you describe). Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *

*