LSM303C 6DoF Hookup Guide
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.