LSM303C 6DoF Hookup Guide
Introduction
The LSM303C is a 6 degrees of freedom (6DOF) inertial measurement unit (IMU) in a sigle package. It houses a 3-axis accelerometer, and a 3-axis magnetometer. The range of each sensor is configurable: the accelerometer's scale can be set to ±2g, ±4g, ±6g, or ±8g, and the magnetometer has full-scale range of ±16 gauss.
The LSM303C supports I2C and SPI. This tutorial focuses on using this device in I2C mode, but will briefly describe how to use SPI.
Covered In This Tutorial
First we'll introduce you to the breakout board. Then we'll switch over to example code and show you how to interface with the board using an Arduino and our SparkFun LSM303C 6 DOF IMU Breakout Arduino Library.
The tutorial is split into the following sections:
- Breakout Board Overview -- This page examines the LSM303C Breakout Board -- topics like the pinout, jumpers, and schematic are covered.
- Hardware Assembly -- How to assemble the hardware to run some example code.
- Installing the Arduino Library -- How to install the Arduino library, and use a simple example sketch to verify that your hookup works.
- Resources & Going Further -- Resources for learning and doing more with the LSM303C.
Required Materials
This tutorial explains how to use the LSM303C Breakout Board with an Arduino. To follow along, you'll need the following materials:
Suggested Reading
If you're not familiar with some of the concepts below, we recommend checking out that tutorial before continuing on.
Pull-up Resistors
Hardware Overview
The Pinout
The LSM303C 6 DOF Breakout has 10 plated through hole connections.
The following table summarizes all of the plated through hole connections on the breakout board:
Pin Label | Pin Function | Notes |
---|---|---|
GND | Ground reference | +0V |
VDD_IO | Power supply for I/O pins | 1.71V up to VDD + 0.1V |
SDA/ SDI/ SDO | I2C serial data SPI serial data input 3-wire interface serial data output | ST calls the second serial interface SPI, but it's really a half-duplex variant that uses the same pin for MISO and for MOSI. Note that all 3 data signals are the same pin. |
SCL/ SCLK | I2C serial clock SPI serial port clock | 100 or 400 kHz I2C Up to 10 MHz SPI |
INT_XL | Accelerometer interrupt signal | The functions, the threshold and the timing of this interrupt are configurable. |
DRDY | Data ready | Configurable output to indicate when accelerometer or magnetometer data is ready. |
CS_XL | Accelerometer: SPI enable I2C/SPI mode selection | 1: SPI idle mode / I2C communication enabled; 0: SPI communication mode / I2C disabled |
VDD | Power supply | 1.9V to 3.6V |
CS_MAG | Magnetometer: SPI enable I2C/SPI mode selection |
1: SPI idle mode / I2C communication enabled; 0: SPI communication mode / I2C disabled |
INT_MAG | Magnetometer interrupt signal | The functions, the threshold and the timing of this interrupt are configurable. |
Power Supply
The LSM303C breakout has three power supply plated thru-hole connections: a 0V reference (GND), a core supply (VDD), and an IO supply (VDD_IO). The core of the IC can be powered from 1.9-3.6V. The IO must be given a potential of at least 1.71V up to the core supply voltage plus 0.1V. This dual supply setup eliminates the need for external voltage level translation. A 3.3V rail can power most of the device while still being able to communicate with a 1.8V processor without drawing all of its power from that lower voltage rail.
Communication
The LSM303C communicates over I2C or 'SPI' using the same plated thru-hole connections. The implementation of 'SPI' on the LSM303C isn't standard; it's a half-duplex variant. Standard SPI has a MOSI and a MISO signal. Both of these are found on the single SDA/SDI/SDO connection. The more common Arduino variants don't have hardware that directly supports this, so we are bit banging in our library. Your system may be compatible, so we didn't add external components to get the hardware to work with the Atmel SPI hardware. This connection is also used as the SDA connection for I2C. Testing showed that the implementation of this IO acts like an open-drain like is common with I2C. This means that a pull-up resistor is needed for both SPI and I2C. The breakout includes this pull-up. Both communication modes share the same clock line (SCL/SCLK).
The LSM303C is implemented as two separate cores on the same die. The accelerometer and magnetometer have their own chip select lines. In I2C mode, they have their own unique addresses. The accelerometer is at 0x1D, and the magnetometer is at 0x1E.
Interrupts
There are a variety of interrupts on the LSM303C. The system can be configured to generate an interrupt signal for free-fall, motion detection and magnetic field detection. The actual function of the two interrupt pins (INT_XL & INT_MAG) are highly configurable through either the I2C or SPI interfaces. They can be active high or low, latching or non-latching, etc. This advanced topic won't be covered in this hookup guide. Please reference the datasheet for more information.
The Jumper
In many cases, especially Arduino related, you won't have multiple lower voltage rails. For these cases we've included SJ2. Your board comes with this jumper closed with a trace by default. This connects VDD_IO and VDD.
The intention of this jumper is to allow the end user to power use the board and begin developing right out of the box. To disable any of these jumpers, whip out your handy hobby knife, and carefully cut the small traces between the two pads. You may then connect VDD_IO to whatever power rial you desire.
Hardware Assembly
I2C Example
The basic use case for the LSM303C requires 4 connections to the µController or µProcessor; power, ground, I2C clock and data. The following images shows how we used a SparkFun FTDI Basic Breakout, and an 3.3V Arduino Pro Mini to power and interface to a LSM303C 6 DOF Breakout board.
Make connections to the breakout anyway that makes you happy. The board in the above photo has a straight header soldered to it. We could have used a right angle header, or wire, etc. Please note that different mounting orientations will alter the orientation of the axes. Make sure your code matches the physical orientation for your projects.
For this demo, we made the following connections:
Arduino Pro Mini | LSM303C Breakout | Notes |
---|---|---|
VCC | VDD | +3.3V |
GND | GND | +0V |
SDA | SDA/SDI/SDO | Serial data @ +3.3V CMOS logic |
SCL | SCL/SCLK | Serial clock @ +3.3V CMOS logic |
The whole system in our testing was powered via USB through the FTDI basic.
SPI Example
Four hardware changes need to be made to interface the sensor using SPI. Move the SDA/SDI/SDO connection from SDA on the Arduino Pro Mini to digital pin 10, move the SCK/SCLK connection from SCL on the Arduino Pro Mini to digital pin 11, and add the two chip select lines.
Arduino Pro Mini | LSM303C Breakout | Notes |
---|---|---|
VCC | VDD | +3.3V |
GND | GND | +0V |
Digital 10 | SDA/SDI/SDO | Serial data @ +3.3V CMOS logic |
Digital 11 | SCL/SCLK | Serial clock @ +3.3V CMOS logic |
Digital 12 | CS_XL | Accelerometer chip select @ +3.3V CMOS logic |
Digital 13 | CS_MAG | Magnetometer chip select @ +3.3V CMOS logic |
Installing the Arduino Library
Download and Install the Library
If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.
Visit the GitHub repository to download the most recent version of the libraries, or click the link below to manually install:
The example Arduino code allows you to do things like read the magnetometer in all 3 axis, read the accelerometer in all 3 axis, and read the temperature of the die in Fahrenheit and Celsius.
Minimalist Example
Now, you can now run the example sketches. Open File ⇒ Examples ⇒ SparkFun LSM303C 6 DOF IMU Breakout Arduino Library ⇒ MinimalistExample. This sketch is a simple as possible other than a little error checking. We'll be using the example in I2C mode.
The setup function configures the Arduino's serial port to 57600 baud and configures the LSM303C to some reasonable defaults.
LSM303C myIMU;
void setup() {
Wire.begin();//set up I2C bus, comment out if using SPI mode
Wire.setClock(400000L);//clock stretching, comment out if using SPI mode
Serial.begin(57600);//initialize serial monitor, maximum reliable baud for 3.3V/8Mhz ATmega328P is 57600
if (myIMU.begin() != IMU_SUCCESS)
{
Serial.println("Failed setup.");
while(1);
}
}
The loop function sequentially prints out the x, y, and z vales measured by the accelerometer, then the gyroscope, and then the magnetometer. This is followed up by the temperature of the die in degrees Celsius and Fahrenheit. All values are rounded to 4 digits past the decimal point.
void loop()
{
//Get all parameters
Serial.print("\nAccelerometer:\n");
Serial.print(" X = ");
Serial.println(myIMU.readAccelX(), 4);
Serial.print(" Y = ");
Serial.println(myIMU.readAccelY(), 4);
Serial.print(" Z = ");
Serial.println(myIMU.readAccelZ(), 4);
// Not supported by hardware, so will return NAN
Serial.print("\nGyroscope:\n");
Serial.print(" X = ");
Serial.println(myIMU.readGyroX(), 4);
Serial.print(" Y = ");
Serial.println(myIMU.readGyroY(), 4);
Serial.print(" Z = ");
Serial.println(myIMU.readGyroZ(), 4);
Serial.print("\nMagnetometer:\n");
Serial.print(" X = ");
Serial.println(myIMU.readMagX(), 4);
Serial.print(" Y = ");
Serial.println(myIMU.readMagY(), 4);
Serial.print(" Z = ");
Serial.println(myIMU.readMagZ(), 4);
Serial.print("\nThermometer:\n");
Serial.print(" Degrees C = ");
Serial.println(myIMU.readTempC(), 4);
Serial.print(" Degrees F = ");
Serial.println(myIMU.readTempF(), 4);
delay(1000);
}
Here is some sample output:
Accelerometer:
X = 56.7017
Y = 42.7856
Z = 946.2891
Gyroscope:
X = nan
Y = nan
Z = nan
Magnetometer:
X = -0.2051
Y = 0.0527
Z = 0.0742
Thermometer:
Degrees C = 24.5000
Degrees F = 76.1000
You may have noticed that gyro data is printed despite there not being a gyro on this board. If a value is not available, the library functions will return nan (not a number). In this case the LSM303C doesn't have a gyroscope, but it still returns a value for a consistent IMU interface. Any IMU library that implements the SparkFunIMU abstract class can be swapped out without having to change the code that uses the library. If at some point in the future it is determined that a project needs more or less degrees of freedom (and is coded with error checking) the code change is trivial. Change #include "SparkFunLSM303C.h"
to #include "SparkFun<some other sensor>.h"
, and LSM303C myIMU;
to <some other sensor> myIMU
. If for whatever reason the sensor doesn't have valid data ready for a sensor that does exist, it will also return nan. This indicates that the value is undefined.
Configure Example
By default, the easy to configure example is configured exactly the same as the minimalist example. We'll be using the example in I2C mode. Here is the code that differentiates the two examples, the setup:
void setup() {
Wire.begin();//set up I2C bus, comment out if using SPI mode
Wire.setClock(400000L);//clock stretching, comment out if using SPI mode
Serial.begin(57600);//initialize serial monitor, maximum reliable baud for 3.3V/8Mhz ATmega328P is 57600
if (myIMU.begin(
///// Interface mode options
//MODE_SPI,
MODE_I2C,
///// Magnetometer output data rate options
//MAG_DO_0_625_Hz,
//MAG_DO_1_25_Hz,
//MAG_DO_2_5_Hz,
//MAG_DO_5_Hz,
//MAG_DO_10_Hz,
//MAG_DO_20_hZ,
MAG_DO_40_Hz,
//MAG_DO_80_Hz,
///// Magnetic field full scale options
//MAG_FS_4_Ga,
//MAG_FS_8_Ga,
//MAG_FS_12_Ga,
MAG_FS_16_Ga,
///// Magnetometer block data updating options
//MAG_BDU_DISABLE,
MAG_BDU_ENABLE,
///// Magnetometer X/Y axes ouput data rate
//MAG_OMXY_LOW_POWER,
//MAG_OMXY_MEDIUM_PERFORMANCE,
MAG_OMXY_HIGH_PERFORMANCE,
//MAG_OMXY_ULTRA_HIGH_PERFORMANCE,
///// Magnetometer Z axis ouput data rate
//MAG_OMZ_LOW_PW,
//MAG_OMZ_MEDIUM_PERFORMANCE,
MAG_OMZ_HIGH_PERFORMANCE,
//MAG_OMZ_ULTRA_HIGH_PERFORMANCE,
///// Magnetometer run mode
MAG_MD_CONTINUOUS,
//MAG_MD_SINGLE,
//MAG_MD_POWER_DOWN_1,
//MAG_MD_POWER_DOWN_2,
///// Acceleration full scale
ACC_FS_2g,
//ACC_FS_4g,
//ACC_FS_8g,
///// Accelerometer block data updating
//ACC_BDU_DISABLE,
ACC_BDU_ENABLE,
///// Enable X, Y, and/or Z axis
//ACC_DISABLE_ALL,
//ACC_X_ENABLE,
//ACC_Y_ENABLE,
//ACC_Z_ENABLE,
//ACC_X_ENABLE|ACC_Y_ENABLE,
//ACC_X_ENABLE|ACC_Z_ENABLE,
//ACC_Y_ENABLE|ACC_Z_ENABLE,
ACC_X_ENABLE|ACC_Y_ENABLE|ACC_Z_ENABLE,
///// Accelerometer output data rate
//ACC_ODR_POWER_DOWN
//ACC_ODR_10_Hz
//ACC_ODR_50_Hz
ACC_ODR_100_Hz
//ACC_ODR_200_Hz
//ACC_ODR_400_Hz
//ACC_ODR_800_Hz
) != IMU_SUCCESS)
{
Serial.println("Failed setup.");
while(1); // Stop here
}
}
Configuring for SPI Mode
This setup function exposes all of the configuration options necessary to read the magnetometer and accelerometer. See the datasheet for more advanced configuration. To change a configuration option, uncomment the desired option in that section, and remove or comment out all other options in that section. For example, if you wanted to use the SPI interface, you would change that section to look like the following.
///// Interface mode options
MODE_SPI,
//MODE_I2C,
After uploading the sketch, open the serial monitor or your favorite terminal emulator and you should start seeing something similar to the following repeating over and over once per second.
Accelerometer:
X = 64.5752
Y = 31.4941
Z = 943.1152
Magnetometer:
X = -0.2085
Y = 0.0425
Z = 0.0972
Thermometer:
Degrees C = 25.3750
Degrees F = 77.6750
In the setup that this data capture came from, the breakout board was sitting roughly flat on a desk. This orients the z-axis parallel to the earths gravitational field of 1,000 mg. This is seen by the z-axis value of around 943. If the LSM303C were in free fall, the z-axis component would be 0 g, because it wouldn't be accelerating. Since the sensor isn't in free fall, it is measuring an effective acceleration of 1 g in the positive z direction up out of the table.
More Library Details
Common IMU Interface
This library does the C++ 'equivalent' of implementing a common interface or template. It does this by implementing the pure virtual methods of the SparkFunIMU class. This strays from the pure definition of an abstract because not all of the methods are purely virtual. We've strayed from this to give unimplemented methods a default behavior. In less technical terms, we've provided a common set of basic functions that any IMU should have.
virtual float readGyroX() { return NAN; }
virtual float readGyroY() { return NAN; }
virtual float readGyroZ() { return NAN; }
virtual float readAccelX() { return NAN; }
virtual float readAccelY() { return NAN; }
virtual float readAccelZ() { return NAN; }
virtual float readMagX() { return NAN; }
virtual float readMagY() { return NAN; }
virtual float readMagZ() { return NAN; }
virtual float readTempC() { return NAN; }
virtual float readTempF() { return NAN; }
The LSM303C provides useful definitions for all of these methods except for the ones that read the gyroscope, since the LSM303C doesn't have a gyroscope. If you were to call readGyroX()
, the default definition would be used, and it would return Not A Number (NAN). This is useful because, if you write your code to use these functions and later decide to use a different IMU that also implements this interface, you only have to change a few words, and all of the code will work with the new sensor.
LSM303C Types
This library is written a little to the computer science object-oriented, encapsulation-type safety side, and a little less to the code size or speed optimized side. To provide type safety and improve readability, LSM303CTypes.h was written. In this header file, many types are defined, all registers are defined to types with descriptive names. As are all of the values you might want to write to the registers. Here is a simple example:
typedef enum
{
ACC_I2C_ADDR = 0x1D,
MAG_I2C_ADDR = 0x1E
} I2C_ADDR_t
The first keyword there, typedef
, is used in C and C++ to define more complex types out of existing types. In this case a type named I2C_ADDR_t is defined to be an enumeration with the enum
keyword. An enumeration is a list of explicitly named integral type constants. They are guarenteed to be a variable large enough to hold an int
type, but what they really are depends on the compiler. For avr-gcc there are compiler switches that can change the actual value used. In this case there are two valid values for a variable of type I2C_ADDR_t
; ACC_I2C_ADDR and MAG_I2C_ADDR. ACC_I2C_ADDR is short for "accelerometer (Inter-Integrated Circuit (I2C) address". Arduino will interrpret that value to be 0x1D
.
Consider the following two function prototypes:
uint8_t I2C_ByteWrite(I2C_ADDR_t, uint8_t, uint8_t);
uint8_t I2C_ByteWrite(int, uint8_t, uint8_t);
Both versions of that function are capable of accepting the values 0x1D
and 0x1E
. The main difference is that the first prototype is type safe. The first parameter has to be of type I2C_ADDR_t
. This type only has two valid values, ACC_I2C_ADDR
and ACC_I2C_ADDR
. If you try and pass any other value without explicitly casting it your code won't compile. You cannot make a mistake that can be loaded onto your Arduino. The avr-gcc toolchain that comes with the Arduino IDE will not let you make that mistake.
The first parameter of the second function prototype can be any int
, including the values 0x1D
and 0x1E
, but not limited to those valid values. Sure your code could handle unexpected values, but what should it do about it? Strobe out an error message in Morse code on an LED? Lock up? This type unsafe code can allow runtime errors to get onto your microcontroller and cause strange bugs. The configure example was designed to show all of the common options, so referencing this header isn't typically needed. The extra complexity is ‘hidden’ away where you only see it if you go looking for it.
Debug Macros
Also in the library is a header file named DebugMacros.h. As the name suggests this files contains the definitions of 4 macro functions used for debugging. This is a very simple tool hacked together for the use in developing this library, but is useful none the less. They don't follow the GNU coding style (case), so they blend in more like standard functions in an attempt to hide the complexity from the beginner programmer.
Prototype | Description |
---|---|
debug_print(msg, ...) | Prints a labeled debug message to the serial monitor. This macro function prepends the name of function & '::' to what Serial.print would do. E.g. loop::Debug message |
debug_prints(msg, ...) | Very similar to the above function except it's shorter. No function label here. Basically the same function as Serial.print(). |
debug_println(msg, ...) | Very similar to the first macro function except it appends a newline character. |
debug_printlns(msg, ...) | Basically Serial.println(). No function label here. |
These macro functions have a few advantages over the built in serial printing functions. The first is that all you have to do is change a single character to turn all of your debug statements on or off. They will be completely removed from the compiled code if turned off. This is done by defining DEBUG
to be 1
(or non-zero) to enable the debug output. If DEBUG
is defined to be 0
, all of the debug_print<options> statements will be removed from the code by the preprocessor before compilation. The place to define the DEBUG
macro is at the top of SparkFunLSM303C.cpp.
Another useful trick they can be used for is generating something similar to a stack trace. To do this, simply add debug_print(EMPTY);
to each function. Pretend that there are a bunch of functions defined. Here is a stripped down code example:
void loop()
{
debug_print(EMPTY); // This is kind of unnecessary
level_1_funct();
}
void level_1_funct(void)
{
debug_print(EMPTY);
level_2_funct();
}
void level_2_funct(void)
{
debug_println("Example error message");
}
Running the sketch containing this code would produce the following output:
loop::level_1_funct::level_2_funct::Example error message
Along with the message saying what went wrong the code provides an in order list of all of the functions that called it. This is helpful for tracing back where the error occurred. It tells all of the recent functions involved. This isn't as useful as a real stack trace, but it's worth the 20 lines of code.
Resources and Going Further
Hopefully that info dump was enough to get you rolling with the LSM303C. If you need any more information, here are some more resources:
- Schematic (PDF)
- Eagle Files (PDF)
- LSM303C Datasheet (PDF) -- This datasheet covers everything from the hardware and pinout of the IC, to the register mapping of the accelerometer, and magnetometer.
- Arduino Library
- GitHub Repository -- Your revision-controled source for all things LSM303C. Here you'll find our most up-to-date hardware layouts.
- SFE Product Showcase
Now that you've got the LSM303C up-and-running, what project are you going to incorporate motion-sensing into? Need a little inspiration? Check out some of these tutorials!
- Dungeons and Dragons Dice Gauntlet -- This project uses an accelerometer to sense a "rolling the dice" motion. You could swap in the LSM303C to add more functionality -- like compass-based damage multipliers!
- Are You Okay? Widget -- Use an Electric Imp and accelerometer to create an "Are You OK" widget. A cozy piece of technology your friend or loved one can nudge to let you know they're OK from half-a-world away.
- Leap Motion Teardown -- An IMU sensor is cool, but image-based motion sensing is the future. Check out this teardown of the miniature-Kinect-like Leap Motion!