Driver for the LSM303D digital compass. Permits access to all LSM303D registers by name, including multi-byte register sets, and has convenience methods for floating-point accelerometer and magnetometer reads scaled according to the device settings. Written from scratch for Mbed 6.0

Dependents:   UoY-32C-lab8-exercise UoY-32C-lab5-lsm303d

Driver for the LSM303D digital compass

Permits access to all LSM303D registers by name, including multi-byte register sets. Convenience methods are included for sensor reads, including floating-point accelerometer and magnetometer reads scaled according to the device settings, and for the more common settings (data rates and scales).

I2C connection only. Written from scratch for Mbed 6.0.

The interface is subject to minor changes with each version. Deprecation warnings will be issued where possible.

Defaults

The class constructor checks that an LSM303D is present on the I2C bus, then enables all sensors. The accelerometer and magnetometer update rates are both set to 50Hz, and the scales are left at their device defaults of +-2g for the accelerometer and +-4gauss for the magnetometer.

Basic usage

Some usage examples

#include "lsm303d.h"

LSM303D lsm303d(D14, D15);  // SDA and SCL pins for I2C

lsm303d.setAccelScale(LSM303D::AccelScale::scale_4g); // set +-4g range

lsm303d.INT1_enable_DRDY_A(); // set DRDY_A bit in CTRL3 register (enable INT1 on accel data ready)

short rawX = lsm303d.getAccelX(); // returns 16-bit signed result, unscaled

float x, y, z;
lsm303d.getAccelInto(x, y, z); // overwrites parameters, values in g

float mX = lsm303d.getMagX(); // returns value in gauss

Files at this revision

API Documentation at this revision

Comitter:
ajp109
Date:
Wed Feb 17 13:13:13 2021 +0000
Parent:
10:392fa35ca17c
Commit message:
Capitalise filenames to match class name

Changed in this revision

LSM303D.cpp Show annotated file Show diff for this revision Revisions of this file
LSM303D.h Show annotated file Show diff for this revision Revisions of this file
lsm303d.cpp Show diff for this revision Revisions of this file
lsm303d.h Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LSM303D.cpp	Wed Feb 17 13:13:13 2021 +0000
@@ -0,0 +1,156 @@
+#include "LSM303D.h"
+
+static unsigned char const accel_lookup[] = {2, 4, 6, 8, 16};
+static unsigned char const mag_lookup[] = {2, 4, 8, 12};
+
+LSM303D::LSM303D(PinName sda, PinName scl, unsigned char i2c_addr) :
+    i2c_(sda, scl), addr_8bit_(i2c_addr << 1),
+    TEMP_OUT    (*this, 0x05),
+    STATUS_M    (*this, 0x07),
+    OUT_X_M     (*this, 0x08),
+    OUT_Y_M     (*this, 0x0A),
+    OUT_Z_M     (*this, 0x0C),
+    OUT_M       (*this, 0x08),
+    WHO_AM_I    (*this, 0x0F),
+    INT_CTRL_M  (*this, 0x12),
+    INT_SRC_M   (*this, 0x13),
+    INT_THS_M   (*this, 0x14),
+    OFFSET_X_M  (*this, 0x16),
+    OFFSET_Y_M  (*this, 0x18),
+    OFFSET_Z_M  (*this, 0x1A),
+    OFFSET_M    (*this, 0x16),
+    REFERENCE_X (*this, 0x1C),
+    REFERENCE_Y (*this, 0x1D),
+    REFERENCE_Z (*this, 0x1E),
+    REFERENCE   (*this, 0x1C),
+    CTRL0       (*this, 0x1F),
+    CTRL1       (*this, 0x20),
+    CTRL2       (*this, 0x21),
+    CTRL3       (*this, 0x22),
+    CTRL4       (*this, 0x23),
+    CTRL5       (*this, 0x24),
+    CTRL6       (*this, 0x25),
+    CTRL7       (*this, 0x26),
+    STATUS_A    (*this, 0x27),
+    OUT_X_A     (*this, 0x28),
+    OUT_Y_A     (*this, 0x2A),
+    OUT_Z_A     (*this, 0x2C),
+    OUT_A       (*this, 0x28),
+    FIFO_CTRL   (*this, 0x2E),
+    FIFO_SRC    (*this, 0x2F),
+    IG_CFG1     (*this, 0x30),
+    IG_SRC1     (*this, 0x31),
+    IG_THS1     (*this, 0x32),
+    IG_DUR1     (*this, 0x33),
+    IG_CFG2     (*this, 0x34),
+    IG_SRC2     (*this, 0x35),
+    IG_THS2     (*this, 0x36),
+    IG_DUR2     (*this, 0x37),
+    CLICK_CFG   (*this, 0x38),
+    CLICK_SRC   (*this, 0x39),
+    CLICK_THS   (*this, 0x3A),
+    TIME_LIMIT  (*this, 0x3B),
+    TIME_LATENCY(*this, 0x3C),
+    TIME_WINDOW (*this, 0x3D),
+    Act_THS     (*this, 0x3E),
+    Act_DUR     (*this, 0x3F),
+
+    accel_enableX   (CTRL1, 0x01),
+    accel_enableY   (CTRL1, 0x02),
+    accel_enableZ   (CTRL1, 0x04),
+    accel_enableAll (CTRL1, 0x07),
+
+    INT1_enable_BOOT     (CTRL3, 0x80),
+    INT1_enable_CLICK    (CTRL3, 0x40),
+    INT1_enable_IG1      (CTRL3, 0x20),
+    INT1_enable_IG2      (CTRL3, 0x10),
+    INT1_enable_IGM      (CTRL3, 0x08),
+    INT1_enable_DRDY_A   (CTRL3, 0x04),
+    INT1_enable_DRDY_M   (CTRL3, 0x02),
+    INT1_enable_EMPTY    (CTRL3, 0x01),
+
+    temp_enable         (CTRL5, 0x80),
+    LIR1_enable         (CTRL5, 0x01)
+
+ {
+    // Check device responds correctly
+    MBED_ASSERT(WHO_AM_I == 0x49);
+
+    // Grab current accelerometer and magnetometer scales from registers
+    accel_scale_ = accel_lookup[(CTRL2 & 0x3E) >> 3];
+    mag_scale_ = mag_lookup[(CTRL6 & 0x60) >> 5];
+    
+    // Useful default config
+    accel_enableAll();
+    setAccelRate(AccelRate::rate_50Hz);
+    temp_enable();
+    setMagRate(MagRate::rate_50Hz);
+    setMagMode(MagMode::continuous);
+}
+
+void LSM303D::getRawAccelInto(short &x, short &y, short &z) {
+    i16_3 xyz = OUT_A;
+    x = xyz.x;
+    y = xyz.y;
+    z = xyz.z;
+}
+
+void LSM303D::getAccelInto(float &x, float &y, float &z) {
+    i16_3 xyz = OUT_A;
+    float scale = accel_scale_ / 32768.0;
+    x = xyz.x * scale;
+    y = xyz.y * scale;
+    z = xyz.z * scale;
+}
+
+void LSM303D::getRawMagInto(short &x, short &y, short &z) {
+    i16_3 xyz = OUT_M;
+    x = xyz.x;
+    y = xyz.y;
+    z = xyz.z;
+}
+
+void LSM303D::getMagInto(float &x, float &y, float &z) {
+    i16_3 xyz = OUT_M;
+    float scale = mag_scale_ / 32768.0;
+    x = xyz.x * scale;
+    y = xyz.y * scale;
+    z = xyz.z * scale;        
+}
+
+void LSM303D::setAccelRate(AccelRate const &rate) {
+    u8 set = static_cast<u8>(rate);
+    CTRL1 = (CTRL1 & ~0xF0) | set;
+}
+
+void LSM303D::setAccelFilterFreq(AccelFilter const &freq) {
+    u8 set = static_cast<u8>(freq);
+    CTRL2 = (CTRL2 & ~0xC0) | set;
+}
+
+void LSM303D::setAccelScale(AccelScale const &scale) {
+    u8 set = static_cast<u8>(scale);
+    CTRL2 = (CTRL2 & ~0x38) | set;
+    accel_scale_ = accel_lookup[set >> 3];
+}
+
+void LSM303D::setMagRes(MagRes const &res) {
+    u8 set = static_cast<u8>(res);
+    CTRL5 = (CTRL5 & ~0x60) | set;
+}
+
+void LSM303D::setMagRate(MagRate const &rate) {
+    u8 set = static_cast<u8>(rate);
+    CTRL5 = (CTRL5 & ~0x1C) | set;
+}
+
+void LSM303D::setMagScale(MagScale const &scale) {
+    u8 set = static_cast<u8>(scale);
+    CTRL6 = (CTRL6 & ~0x60) | set;
+    mag_scale_ = mag_lookup[set >> 5];
+}
+
+void LSM303D::setMagMode(MagMode const &mode) {
+    u8 set = static_cast<u8>(mode);
+    CTRL7 = (CTRL7 & ~0x03) | set;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LSM303D.h	Wed Feb 17 13:13:13 2021 +0000
@@ -0,0 +1,530 @@
+#include "mbed.h"
+
+typedef unsigned char u8;
+typedef signed short i16;
+typedef unsigned short u16;
+
+#pragma pack(push)
+#pragma pack(1)
+typedef struct {
+    u8 x;
+    u8 y;
+    u8 z;
+} u8_3;
+typedef struct {
+    i16 x;
+    i16 y;
+    i16 z;
+} i16_3;
+#pragma pack(pop)
+
+/** LSM303D class.
+ * Communicates with LSM303D electronic compass over I<sup>2</sup>C.  Accelerometer,
+ * magnetometer and temperature sensor supported.  Configurable sample rates
+ * and full-scale ranges.  All LSM303D config registers available for raw
+ * reads and writes if needed.
+ *
+ * Example:
+ * @code
+ * #include "mbed.h"
+ * #include "lsm303d.h"
+ *
+ * LSM303D lsm303d(SDA, SCL);
+ * lsm303d.setAccelRate(LSM303D::AccelRate::rate_25Hz);
+ * lsm303d.setAccelScale(LSM303D::AccelScale::scale_8g);
+ * 
+ * int main() {
+ *     short x, y, z;
+ *     lsm303d.getRawAccelInto(x, y, z); // 16-bit signed, unscaled
+ *     float xf, yf, zf;
+ *     lsm303d.getAccelInto(xf, yf, zf); // floats, in g
+ * }
+ * @endcode
+ */
+class LSM303D {
+    
+    friend class AccelScaleOption;
+    friend class MagScaleOption;
+    
+    unsigned char const addr_8bit_;
+    unsigned char accel_scale_;
+    unsigned char mag_scale_;
+    I2C i2c_;
+
+protected:
+    // These classes are protected (rather than private) for inlusion in docs.
+    // Wouldn't be necessary if mbed online toolchain allowed local Doxyfiles.
+
+    template <class T>
+    class RWRegister;
+
+    /** Read-only register class.
+     * @tparam T The underlying data type of the register.
+     * Provides an interface onto the read-only registers of the LSM303D.
+     * Operator overloading means that it can be treated as a const variable of
+     * type T for most purposes.  Every read initiates an explicit
+     * I<sup>2</sup>C transfer.
+     */
+    template <class T>
+    class Register {
+        friend class LSM303D;
+        friend class RWRegister<T>;
+    private:
+        unsigned char addr_;
+        LSM303D &parent_;
+        Register(LSM303D &parent, unsigned char addr) :
+            parent_(parent),
+            addr_(addr | (sizeof(T)>1 ? 0x80 : 0x00)) {}
+        /// Explicit read method.
+        /// \return The current value of the register.
+        virtual T read() const {
+            T result;
+            parent_.i2c_.lock();
+            parent_.i2c_.write(parent_.addr_8bit_, (char *)&addr_, 1);
+            parent_.i2c_.read(parent_.addr_8bit_, (char *)&result, sizeof(T));
+            parent_.i2c_.unlock();
+            return result;
+        }
+        /// Explicit cast to type T
+        inline operator T() const {
+            return read();
+        }
+    };
+    
+    /** Read-write register class.
+     * @tparam T The underlying data type of the register.
+     * Provides an interface onto the writable registers of the LSM303D.
+     * Operator overloading means that it can be treated as a variable of type
+     * T for most purposes.  Assignment from int is also explicitly supported
+     * to allow integer literals to be used without qualification or casting.
+     * The register value is cached on first read and updated on write, so read
+     * accesses do not use the I<sup>2</sup>C bus.
+     */
+    template<class T>
+    class RWRegister : public Register<T> {
+        friend class LSM303D;
+        using Register<T>::parent_;
+        using Register<T>::addr_;
+    private:
+        mutable T cache_;
+        mutable bool cached_;
+    protected:
+        RWRegister(LSM303D &parent, unsigned char addr) :
+            Register<T>(parent, addr), cached_(false) {}
+        /// Explicit write method.
+        /// @param data The new value to write to the register.
+        void write(T const &data) {
+            char packet[sizeof(T)+1];
+            packet[0] = addr_;
+            memcpy(&packet[1], &data, sizeof(T));
+            cache_ = data;
+            cached_ = true;
+            parent_.i2c_.lock();
+            parent_.i2c_.write(parent_.addr_8bit_, packet, sizeof(T)+1);
+            parent_.i2c_.unlock();
+        }
+        /// Explicit read method with cache.
+        /// @return The current value of the register, from cache if available.
+        virtual T read() const {
+            if (!cached_) {
+                cache_ = Register<T>::read();
+                cached_ = true;
+            }
+            return cache_;
+        }
+        /// Equivalent to write().
+        /// @param data The new value to write to the register.
+        /// @return A reference to this object.
+        inline RWRegister & operator= (T const &data) {
+            write(data);
+            return *this;
+        }
+        /// Equivalent to write(), with an explicit int parameter.
+        /// @param data The new value to write to the register.
+        /// @return A reference to this object.
+        inline RWRegister & operator= (int const &data) {
+            write(T(data));
+            return *this;
+        }
+        /// Or-assignment operator.
+        /// @param data The value to OR with the register's existing contents.
+        /// @return A reference to this object.
+        inline RWRegister & operator|= (T const &data) {
+            write(read() | data);
+            return *this;
+        }
+        /// Or-assignment operator, with an explicit int parameter.
+        /// @param data The value to OR with the register's existing contents.
+        /// @return A reference to this object.
+        inline RWRegister & operator|= (int const &data) {
+            write(read() | T(data));
+            return *this;
+        }
+        /// And-assignment operator.
+        /// @param data The value to AND with the register's existing contents.
+        /// @return A reference to this object.
+        inline RWRegister & operator&= (T const &data) {
+            write(read() & data);
+            return *this;
+        }
+        /// And-assignment operator, with an explicit int parameter.
+        /// @param data The value to AND with the register's existing contents.
+        /// @return A reference to this object.
+        inline RWRegister & operator&= (int const &data) {
+            write(read() & T(data));
+            return *this;
+        }
+    };
+
+    /** Class representing binary (on/off) options.
+     * @tparam T The underlying data type of the register used to set the option.
+     * Provides a user-friendly interface onto LSM303D options.
+     */
+    template <class T>
+    class Option {
+        friend class LSM303D;
+    protected:
+        T const bits_;
+        RWRegister<T> & reg_;
+        Option(RWRegister<T> & reg, int bits) :
+            reg_(reg), bits_(T(bits)) {}
+    public:
+        /// Set the option 'on'.
+        inline void operator() () const { reg_ |= bits_; };
+        /// Set the option 'on' or 'off'.
+        /// @param sr Option status: set to true for 'on', false for 'off'.
+        inline void operator() (bool sr) const
+            { if (sr) reg_ |= bits_; else reg_ &= ~bits_; };
+    };
+        
+    const Register   <i16_3> OUT_M;         ///< Triple magnetometer register
+    const Register   <i16_3> OFFSET_M;      ///< Triple magnetometer offset register
+    const Register   <u8_3>  REFERENCE;     ///< Triple reference register
+    const Register   <i16_3> OUT_A;         ///< Triple accelerometer register
+
+public:
+    const Register   <i16>   TEMP_OUT;      ///< TEMP_OUT register, read-only
+    const Register   <u8>    STATUS_M;      ///< STATUS_M register, read-only
+    const Register   <i16>   OUT_X_M;       ///< OUT_X_M register, read-only
+    const Register   <i16>   OUT_Y_M;       ///< OUT_Y_M register, read-only
+    const Register   <i16>   OUT_Z_M;       ///< OUT_Z_M register, read-only
+    const Register   <u8>    WHO_AM_I;      ///< WHO_AM_I register, read-only
+    const Register   <u8>    INT_SRC_M;     ///< INT_SRC_M register, read-only
+    const Register   <i16>   OFFSET_X_M;    ///< OFFSET_X_M register, read-only
+    const Register   <i16>   OFFSET_Y_M;    ///< OFFSET_Y_M register, read-only
+    const Register   <i16>   OFFSET_Z_M;    ///< OFFSET_Z_M register, read-only
+    const Register   <u8>    STATUS_A;      ///< STATUS_A register, read-only
+    const Register   <i16>   OUT_X_A;       ///< OUT_X_A register, read-only
+    const Register   <i16>   OUT_Y_A;       ///< OUT_Y_A register, read-only
+    const Register   <i16>   OUT_Z_A;       ///< OUT_Z_A register, read-only
+    const Register   <u8>    FIFO_SRC;      ///< FIFO_SRC register, read-only
+    const Register   <u8>    IG_SRC1;       ///< IG_SRC1 register, read-only
+    const Register   <u8>    IG_SRC2;       ///< IG_SRC2 register, read-only
+    const Register   <u8>    CLICK_SRC;     ///< CLICK_SRC register, read-only
+
+    RWRegister <u8>    INT_CTRL_M;  ///< INT_CTRL_M register, read-write
+    RWRegister <u16>   INT_THS_M;   ///< INT_THS_M register, read-write
+    RWRegister <u8>    REFERENCE_X; ///< REFERENCE_X register, read-write
+    RWRegister <u8>    REFERENCE_Y; ///< REFERENCE_Y register, read-write
+    RWRegister <u8>    REFERENCE_Z; ///< REFERENCE_Z register, read-write
+    RWRegister <u8>    CTRL0;       ///< CTRL0 register, read-write
+    RWRegister <u8>    CTRL1;       ///< CTRL1 register, read-write
+    RWRegister <u8>    CTRL2;       ///< CTRL2 register, read-write
+    RWRegister <u8>    CTRL3;       ///< CTRL3 register, read-write
+    RWRegister <u8>    CTRL4;       ///< CTRL4 register, read-write
+    RWRegister <u8>    CTRL5;       ///< CTRL5 register, read-write
+    RWRegister <u8>    CTRL6;       ///< CTRL6 register, read-write
+    RWRegister <u8>    CTRL7;       ///< CTRL7 register, read-write
+    RWRegister <u8>    FIFO_CTRL;   ///< FIFO_CTRL register, read-write
+    RWRegister <u8>    IG_CFG1;     ///< IG_CFG1 register, read-write
+    RWRegister <u8>    IG_THS1;     ///< IG_THS1 register, read-write
+    RWRegister <u8>    IG_DUR1;     ///< IG_DUR1 register, read-write
+    RWRegister <u8>    IG_CFG2;     ///< IG_CFG2 register, read-write
+    RWRegister <u8>    IG_THS2;     ///< IG_THS2 register, read-write
+    RWRegister <u8>    IG_DUR2;     ///< IG_DUR2 register, read-write
+    RWRegister <u8>    CLICK_CFG;   ///< CLICK_CFG register, read-write
+    RWRegister <u8>    CLICK_THS;   ///< CLICK_THS register, read-write
+    RWRegister <u8>    TIME_LIMIT;  ///< TIME_LIMIT register, read-write
+    RWRegister <u8>    TIME_LATENCY;///< TIME_LATENCY register, read-write
+    RWRegister <u8>    TIME_WINDOW; ///< TIME_WINDOW register, read-write
+    RWRegister <u8>    Act_THS;     ///< Act_THS register, read-write
+    RWRegister <u8>    Act_DUR;     ///< Act_DUR register, read-write
+    
+    // CTRL1
+    /** AccelRate enum.
+     * Provides friendly names for the accelerometer update rates
+     * that are provided by the device.
+     */
+    enum class AccelRate {
+        powerdown    = 0x00,    ///< power-down mode
+        rate_3_125Hz = 0x10,    ///< 3.125Hz update rate
+        rate_6_25Hz  = 0x20,    ///< 6.25Hz update rate
+        rate_12_5Hz  = 0x30,    ///< 12.5Hz update rate
+        rate_25Hz    = 0x40,    ///< 25Hz update rate
+        rate_50Hz    = 0x50,    ///< 50Hz update rate
+        rate_100Hz   = 0x60,    ///< 100Hz update rate
+        rate_200Hz   = 0x70,    ///< 200Hz update rate
+        rate_400Hz   = 0x80,    ///< 400Hz update rate
+        rate_800Hz   = 0x90,    ///< 800Hz update rate
+        rate_1600Hz  = 0xA0     ///< 1600Hz update rate
+    };
+    /** Sets the accelerometer update rate.
+     * @param rate The new update rate.
+     * Available values are:
+     * - AccelRate::powerdown (power-down mode)
+     * - AccelRate::rate_3_125Hz (3.125Hz)
+     * - AccelRate::rate_6_25Hz (6.25Hz)
+     * - AccelRate::rate_12_5Hz (12.5Hz)
+     * - AccelRate::rate_25Hz (25Hz)
+     * - AccelRate::rate_50Hz (50Hz)
+     * - AccelRate::rate_100Hz (100Hz)
+     * - AccelRate::rate_200Hz (200Hz)
+     * - AccelRate::rate_400Hz (400Hz)
+     * - AccelRate::rate_800Hz (800Hz)
+     * - AccelRate::rate_1600Hz (1600Hz)
+     */
+    void setAccelRate(AccelRate const &rate);
+
+    
+    const Option<u8> accel_enableX;     ///< Enable or disable x-axis accelerometer
+    const Option<u8> accel_enableY;     ///< Enable or disable y-axis accelerometer
+    const Option<u8> accel_enableZ;     ///< Enable or disable z-axis accelerometer
+    const Option<u8> accel_enableAll;   ///< Enable or disable all accelerometers
+
+    // CTRL2
+    /** AccelFilter enum.
+     * Provides friendly names for the accelerometer antialiasing filter
+     * frequencies that are provided by the device.
+     */
+    enum class AccelFilter {
+        filter_773Hz = 0x00,    ///< 773Hz corner frequency
+        filter_194Hz = 0x40,    ///< 194Hz corner frequency
+        filter_362Hz = 0x80,    ///< 362Hz corner frequency
+        filter_50Hz  = 0xC0     ///< 50Hz corner frequency
+    };
+    /** Sets the accelerometer antialiasing filter frequency.
+     * @param freq The new frequency.
+     * Available values are:
+     * - AccelFilter::filter_773Hz
+     * - AccelFilter::filter_194Hz
+     * - AccelFilter::filter_362Hz
+     * - AccelFilter::filter_50Hz
+     */
+    void setAccelFilterFreq(AccelFilter const &freq);
+    /** AccelScale enum.
+     * Provides friendly names for the accelerometer scale ranges
+     * that are provided by the device.
+     */
+    enum class AccelScale {
+        scale_2g  = 0x00,   ///< +-2g full scale
+        scale_4g  = 0x08,   ///< +-4g full scale
+        scale_6g  = 0x10,   ///< +-6g full scale
+        scale_8g  = 0x18,   ///< +-8g full scale
+        scale_16g = 0x20    ///< +-16g full scale
+    };
+    /** Sets the accelerometer scale range.
+     * @param scale The new scale.
+     * Available values are:
+     * - AccelScale::scale_2g (+-2g)
+     * - AccelScale::scale_4g (+-4g)
+     * - AccelScale::scale_6g (+-6g)
+     * - AccelScale::scale_8g (+-8g)
+     * - AccelScale::scale_16g (+-16g)
+     */
+    void setAccelScale(AccelScale const &scale);
+
+    // CTRL3
+    const Option<u8> INT1_enable_BOOT;  ///< Enable or disable "boot" source for INT1
+    const Option<u8> INT1_enable_CLICK; ///< Enable or disable "click" source for INT1
+    const Option<u8> INT1_enable_IG1;   ///< Enable or disable "inertial 1" source for INT1
+    const Option<u8> INT1_enable_IG2;   ///< Enable or disable "inertial 2" source for INT1
+    const Option<u8> INT1_enable_IGM;   ///< Enable or disable "magnetic" source for INT1
+    const Option<u8> INT1_enable_DRDY_A;///< Enable or disable "accelerometer data ready" source for INT1
+    const Option<u8> INT1_enable_DRDY_M;///< Enable or disable "magnetometer data ready" source for INT1
+    const Option<u8> INT1_enable_EMPTY; ///< Enable or disable "FIFO empty" source for INT1
+    
+    // CTRL4 concerns INT2 which is not broken out on the Pimoroni board
+    
+    // CTRL5
+    const Option<u8> temp_enable;   ///< Enable or disable temperature sensor
+    const Option<u8> LIR1_enable;   ///< Enable or disable latching interrupt requests for INT1
+    
+    /** MagRes enum.
+     * Provides friendly names for the magnetometer resolution options
+     * that are provided by the device.
+     */
+    enum class MagRes {
+        high = 0x60,    ///< High resolution
+        low  = 0x00,    ///< Low resolution
+    };
+    /** Sets the magnetometer resolution.
+     * @param res The new resolution.
+     * Available values are:
+     * - MagRes::high (high resolution)
+     * - MagRes::low (low resolution)
+     */
+    void setMagRes(MagRes const &res);
+
+    /** MagRate enum.
+     * Provides friendly names for the magnetometer update rates
+     * that are provided by the device.
+     */
+    enum class MagRate {
+        rate_3_125Hz = 0x00,    ///< 3.125Hz update rate
+        rate_6_25Hz  = 0x04,    ///< 6.25Hz update rate
+        rate_12_5Hz  = 0x08,    ///< 12.5Hz update rate
+        rate_25Hz    = 0x0C,    ///< 25Hz update rate
+        rate_50Hz    = 0x10,    ///< 50Hz update rate
+        rate_100Hz   = 0x14     ///< 100Hz update rate
+    };    
+    /** Sets the magnetometer update rate.
+     * @param rate The new update rate.
+     * Available values are:
+     * - MagRate::rate_3_125Hz (3.125Hz)
+     * - MagRate::rate_6_25Hz (6.25Hz)
+     * - MagRate::rate_12_5Hz (12.5Hz)
+     * - MagRate::rate_25Hz (25Hz)
+     * - MagRate::rate_50Hz (50Hz)
+     * - MagRate::rate_100Hz (100Hz)
+     * Note that the 100Hz update rate is available only if the accelerometer
+     * update rate is set to 50Hz or higher or the accelerometer is set to
+     * power-down mode (see setAccelRate()).  Failing to meet these conditions
+     * will lead to undefined behaviour.
+     */
+    void setMagRate(MagRate const &rate);
+
+    // CTRL6
+    /** MagScale enum.
+     * Provides friendly names for the magnetometer scale ranges
+     * that are provided by the device.
+     */
+    enum class MagScale {
+        scale_2G  = 0x00,   ///< +-2 Gauss
+        scale_4G  = 0x20,   ///< +-4 Gauss
+        scale_8G  = 0x40,   ///< +-8 Gauss
+        scale_12G = 0x60    ///< +-12 Gauss
+    };
+    /** Sets the magnetometer scale range.
+     * @param scale The new scale.
+     * Available values are:
+     * - MagScale::scale_2G (+-2 Gauss)
+     * - MagScale::scale_4G (+-4 Gauss)
+     * - MagScale::scale_8G (+-8 Gauss)
+     * - MagScale::scale_12G (+-16 Gauss)
+     */
+    void setMagScale(MagScale const &scale);
+
+    // CTRL7
+    /** MagScale enum.
+     * Provides friendly names for the magnetometer modes that are supported
+     * by the device.
+     */
+    enum class MagMode {
+        continuous  = 0x00, ///< Continous conversion mode
+        single      = 0x01, ///< Single conversion mode
+        powerdown   = 0x02  ///< Magnetometer power-down mode
+    };
+    /** Sets the magnetometer mode.
+     * @param mode The new mode.
+     * Available values are:
+     * - MagMode::continuous (continuous conversion mode)
+     * - MagMode::single (single-conversion mode)
+     * - MagMode::powerdown (power-down mode)
+     */
+    void setMagMode(MagMode const &mode);
+
+    /** Constructor.
+     * @param sda The pin to use for the I^^2^^C SDA signal.
+     * @param scl The pin to use for the I^^2^^C SCL signal.
+     * @param i2c_addr The 7-bit I<sup>2</sup>C address.  Defaults to 0x1D, but
+     * this can be overridden to 0x1E with a jumper setting on the device allowing
+     * two LSM303D devices to coexist on a bus.
+     */
+    LSM303D(PinName sda, PinName scl, unsigned char i2c_addr = 0x1D);
+    
+    /** Gets the raw (signed 16-bit integer) X-axis acceleration value.
+     * @return The raw acceleration value.
+     */
+    inline i16 getRawAccelX() { return OUT_X_A; };
+    /** Gets the raw (signed 16-bit integer) Y-axis acceleration value.
+     * @return The raw acceleration value.
+     */
+    inline i16 getRawAccelY() { return OUT_Y_A; };
+    /** Gets the raw (signed 16-bit integer) Z-axis acceleration value.
+     * @return The raw acceleration value.
+     */
+    inline i16 getRawAccelZ() { return OUT_Z_A; };
+    /** Gets the scaled (float) X-axis acceleration value in g.
+     * @return The scaled acceleration value in g.
+     */
+    inline float getAccelX() { return (float)OUT_X_A / accel_scale_ / 32768; };
+    /** Gets the scaled (float) Y-axis acceleration value in g.
+     * @return The scaled acceleration value in g.
+     */
+    inline float getAccelY() { return (float)OUT_Y_A / accel_scale_ / 32768; };
+    /** Gets the scaled (float) Z-axis acceleration value in g.
+     * @return The scaled acceleration value in g.
+     */
+    inline float getAccelZ() { return (float)OUT_Z_A / accel_scale_ / 32768; };
+    /** Reads the raw (signed 16-bit integer) acceleration values into the three
+     * axis value containers provided.
+     * @param x Container for the X value.
+     * @param y Container for the Y value.
+     * @param z Container for the Z value.
+     */
+    void getRawAccelInto(short &x, short &y, short &z);
+    /** Reads the scaled (float) acceleration values in g into the three
+     * axis value containers provided.
+     * @param x Container for the X value.
+     * @param y Container for the Y value.
+     * @param z Container for the Z value.
+     */
+    void getAccelInto(float &x, float &y, float &z);
+    
+    /** Gets the raw (signed 16-bit integer) X-axis magnetometer value.
+     * @return The raw magnetometer value.
+     */
+    inline i16 getRawMagX() { return OUT_X_M; };
+    /** Gets the raw (signed 16-bit integer) Y-axis magnetometer value.
+     * @return The raw magnetometer value.
+     */
+    inline i16 getRawMagY() { return OUT_Y_M; };
+    /** Gets the raw (signed 16-bit integer) Z-axis magnetometer value.
+     * @return The raw magnetometer value.
+     */
+    inline i16 getRawMagZ() { return OUT_Z_M; };
+    /** Gets the scaled (float) X-axis magnetometer value in Gauss.
+     * @return The scaled magnetometer value in Gauss.
+     */
+    inline float getMagX() { return (float)OUT_X_M / mag_scale_ / 32768; };
+    /** Gets the scaled (float) Y-axis magnetometer value in Gauss.
+     * @return The scaled magnetometer value in Gauss.
+     */
+    inline float getMagY() { return (float)OUT_Y_M / mag_scale_ / 32768; };
+    /** Gets the scaled (float) Z-axis magnetometer value in Gauss.
+     * @return The scaled magnetometer value in Gauss.
+     */
+    inline float getMagZ() { return (float)OUT_Z_M / mag_scale_ / 32768; };
+    /** Reads the raw (signed 16-bit integer) magnetometer values into the three
+     * axis value containers provided.
+     * @param x Container for the X value.
+     * @param y Container for the Y value.
+     * @param z Container for the Z value.
+     */
+    void getRawMagInto(short &x, short &y, short &z);
+    /** Reads the scaled (float) magnetometer values in Gauss into the three
+     * axis value containers provided.
+     * @param x Container for the X value.
+     * @param y Container for the Y value.
+     * @param z Container for the Z value.
+     */
+    void getMagInto(float &x, float &y, float &z);
+    
+    /** Reads the scaled (float) temperature values in &deg; C.
+     * @return The current temperature.
+     * Note that testing suggests this to be an inaccurate temperature sensor!
+     * To read the raw temperature value, simply use the TEMP_OUT register directly.
+     */
+    inline float getTemp() { return TEMP_OUT / 8.0 + 25; };
+    
+};
--- a/lsm303d.cpp	Wed Feb 10 21:27:54 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-#include "lsm303d.h"
-
-static unsigned char const accel_lookup[] = {2, 4, 6, 8, 16};
-static unsigned char const mag_lookup[] = {2, 4, 8, 12};
-
-LSM303D::LSM303D(PinName sda, PinName scl, unsigned char i2c_addr) :
-    i2c_(sda, scl), addr_8bit_(i2c_addr << 1),
-    TEMP_OUT    (*this, 0x05),
-    STATUS_M    (*this, 0x07),
-    OUT_X_M     (*this, 0x08),
-    OUT_Y_M     (*this, 0x0A),
-    OUT_Z_M     (*this, 0x0C),
-    OUT_M       (*this, 0x08),
-    WHO_AM_I    (*this, 0x0F),
-    INT_CTRL_M  (*this, 0x12),
-    INT_SRC_M   (*this, 0x13),
-    INT_THS_M   (*this, 0x14),
-    OFFSET_X_M  (*this, 0x16),
-    OFFSET_Y_M  (*this, 0x18),
-    OFFSET_Z_M  (*this, 0x1A),
-    OFFSET_M    (*this, 0x16),
-    REFERENCE_X (*this, 0x1C),
-    REFERENCE_Y (*this, 0x1D),
-    REFERENCE_Z (*this, 0x1E),
-    REFERENCE   (*this, 0x1C),
-    CTRL0       (*this, 0x1F),
-    CTRL1       (*this, 0x20),
-    CTRL2       (*this, 0x21),
-    CTRL3       (*this, 0x22),
-    CTRL4       (*this, 0x23),
-    CTRL5       (*this, 0x24),
-    CTRL6       (*this, 0x25),
-    CTRL7       (*this, 0x26),
-    STATUS_A    (*this, 0x27),
-    OUT_X_A     (*this, 0x28),
-    OUT_Y_A     (*this, 0x2A),
-    OUT_Z_A     (*this, 0x2C),
-    OUT_A       (*this, 0x28),
-    FIFO_CTRL   (*this, 0x2E),
-    FIFO_SRC    (*this, 0x2F),
-    IG_CFG1     (*this, 0x30),
-    IG_SRC1     (*this, 0x31),
-    IG_THS1     (*this, 0x32),
-    IG_DUR1     (*this, 0x33),
-    IG_CFG2     (*this, 0x34),
-    IG_SRC2     (*this, 0x35),
-    IG_THS2     (*this, 0x36),
-    IG_DUR2     (*this, 0x37),
-    CLICK_CFG   (*this, 0x38),
-    CLICK_SRC   (*this, 0x39),
-    CLICK_THS   (*this, 0x3A),
-    TIME_LIMIT  (*this, 0x3B),
-    TIME_LATENCY(*this, 0x3C),
-    TIME_WINDOW (*this, 0x3D),
-    Act_THS     (*this, 0x3E),
-    Act_DUR     (*this, 0x3F),
-
-    accel_enableX   (CTRL1, 0x01),
-    accel_enableY   (CTRL1, 0x02),
-    accel_enableZ   (CTRL1, 0x04),
-    accel_enableAll (CTRL1, 0x07),
-
-    INT1_enable_BOOT     (CTRL3, 0x80),
-    INT1_enable_CLICK    (CTRL3, 0x40),
-    INT1_enable_IG1      (CTRL3, 0x20),
-    INT1_enable_IG2      (CTRL3, 0x10),
-    INT1_enable_IGM      (CTRL3, 0x08),
-    INT1_enable_DRDY_A   (CTRL3, 0x04),
-    INT1_enable_DRDY_M   (CTRL3, 0x02),
-    INT1_enable_EMPTY    (CTRL3, 0x01),
-
-    temp_enable         (CTRL5, 0x80),
-    LIR1_enable         (CTRL5, 0x01)
-
- {
-    // Check device responds correctly
-    MBED_ASSERT(WHO_AM_I == 0x49);
-
-    // Grab current accelerometer and magnetometer scales from registers
-    accel_scale_ = accel_lookup[(CTRL2 & 0x3E) >> 3];
-    mag_scale_ = mag_lookup[(CTRL6 & 0x60) >> 5];
-    
-    // Useful default config
-    accel_enableAll();
-    setAccelRate(AccelRate::rate_50Hz);
-    temp_enable();
-    setMagRate(MagRate::rate_50Hz);
-    setMagMode(MagMode::continuous);
-}
-
-void LSM303D::getRawAccelInto(short &x, short &y, short &z) {
-    i16_3 xyz = OUT_A;
-    x = xyz.x;
-    y = xyz.y;
-    z = xyz.z;
-}
-
-void LSM303D::getAccelInto(float &x, float &y, float &z) {
-    i16_3 xyz = OUT_A;
-    float scale = accel_scale_ / 32768.0;
-    x = xyz.x * scale;
-    y = xyz.y * scale;
-    z = xyz.z * scale;
-}
-
-void LSM303D::getRawMagInto(short &x, short &y, short &z) {
-    i16_3 xyz = OUT_M;
-    x = xyz.x;
-    y = xyz.y;
-    z = xyz.z;
-}
-
-void LSM303D::getMagInto(float &x, float &y, float &z) {
-    i16_3 xyz = OUT_M;
-    float scale = mag_scale_ / 32768.0;
-    x = xyz.x * scale;
-    y = xyz.y * scale;
-    z = xyz.z * scale;        
-}
-
-void LSM303D::setAccelRate(AccelRate const &rate) {
-    u8 set = static_cast<u8>(rate);
-    CTRL1 = (CTRL1 & ~0xF0) | set;
-}
-
-void LSM303D::setAccelFilterFreq(AccelFilter const &freq) {
-    u8 set = static_cast<u8>(freq);
-    CTRL2 = (CTRL2 & ~0xC0) | set;
-}
-
-void LSM303D::setAccelScale(AccelScale const &scale) {
-    u8 set = static_cast<u8>(scale);
-    CTRL2 = (CTRL2 & ~0x38) | set;
-    accel_scale_ = accel_lookup[set >> 3];
-}
-
-void LSM303D::setMagRes(MagRes const &res) {
-    u8 set = static_cast<u8>(res);
-    CTRL5 = (CTRL5 & ~0x60) | set;
-}
-
-void LSM303D::setMagRate(MagRate const &rate) {
-    u8 set = static_cast<u8>(rate);
-    CTRL5 = (CTRL5 & ~0x1C) | set;
-}
-
-void LSM303D::setMagScale(MagScale const &scale) {
-    u8 set = static_cast<u8>(scale);
-    CTRL6 = (CTRL6 & ~0x60) | set;
-    mag_scale_ = mag_lookup[set >> 5];
-}
-
-void LSM303D::setMagMode(MagMode const &mode) {
-    u8 set = static_cast<u8>(mode);
-    CTRL7 = (CTRL7 & ~0x03) | set;
-}
--- a/lsm303d.h	Wed Feb 10 21:27:54 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,530 +0,0 @@
-#include "mbed.h"
-
-typedef unsigned char u8;
-typedef signed short i16;
-typedef unsigned short u16;
-
-#pragma pack(push)
-#pragma pack(1)
-typedef struct {
-    u8 x;
-    u8 y;
-    u8 z;
-} u8_3;
-typedef struct {
-    i16 x;
-    i16 y;
-    i16 z;
-} i16_3;
-#pragma pack(pop)
-
-/** LSM303D class.
- * Communicates with LSM303D electronic compass over I<sup>2</sup>C.  Accelerometer,
- * magnetometer and temperature sensor supported.  Configurable sample rates
- * and full-scale ranges.  All LSM303D config registers available for raw
- * reads and writes if needed.
- *
- * Example:
- * @code
- * #include "mbed.h"
- * #include "lsm303d.h"
- *
- * LSM303D lsm303d(SDA, SCL);
- * lsm303d.setAccelRate(LSM303D::AccelRate::rate_25Hz);
- * lsm303d.setAccelScale(LSM303D::AccelScale::scale_8g);
- * 
- * int main() {
- *     short x, y, z;
- *     lsm303d.getRawAccelInto(x, y, z); // 16-bit signed, unscaled
- *     float xf, yf, zf;
- *     lsm303d.getAccelInto(xf, yf, zf); // floats, in g
- * }
- * @endcode
- */
-class LSM303D {
-    
-    friend class AccelScaleOption;
-    friend class MagScaleOption;
-    
-    unsigned char const addr_8bit_;
-    unsigned char accel_scale_;
-    unsigned char mag_scale_;
-    I2C i2c_;
-
-protected:
-    // These classes are protected (rather than private) for inlusion in docs.
-    // Wouldn't be necessary if mbed online toolchain allowed local Doxyfiles.
-
-    template <class T>
-    class RWRegister;
-
-    /** Read-only register class.
-     * @tparam T The underlying data type of the register.
-     * Provides an interface onto the read-only registers of the LSM303D.
-     * Operator overloading means that it can be treated as a const variable of
-     * type T for most purposes.  Every read initiates an explicit
-     * I<sup>2</sup>C transfer.
-     */
-    template <class T>
-    class Register {
-        friend class LSM303D;
-        friend class RWRegister<T>;
-    private:
-        unsigned char addr_;
-        LSM303D &parent_;
-        Register(LSM303D &parent, unsigned char addr) :
-            parent_(parent),
-            addr_(addr | (sizeof(T)>1 ? 0x80 : 0x00)) {}
-        /// Explicit read method.
-        /// \return The current value of the register.
-        virtual T read() const {
-            T result;
-            parent_.i2c_.lock();
-            parent_.i2c_.write(parent_.addr_8bit_, (char *)&addr_, 1);
-            parent_.i2c_.read(parent_.addr_8bit_, (char *)&result, sizeof(T));
-            parent_.i2c_.unlock();
-            return result;
-        }
-        /// Explicit cast to type T
-        inline operator T() const {
-            return read();
-        }
-    };
-    
-    /** Read-write register class.
-     * @tparam T The underlying data type of the register.
-     * Provides an interface onto the writable registers of the LSM303D.
-     * Operator overloading means that it can be treated as a variable of type
-     * T for most purposes.  Assignment from int is also explicitly supported
-     * to allow integer literals to be used without qualification or casting.
-     * The register value is cached on first read and updated on write, so read
-     * accesses do not use the I<sup>2</sup>C bus.
-     */
-    template<class T>
-    class RWRegister : public Register<T> {
-        friend class LSM303D;
-        using Register<T>::parent_;
-        using Register<T>::addr_;
-    private:
-        mutable T cache_;
-        mutable bool cached_;
-    protected:
-        RWRegister(LSM303D &parent, unsigned char addr) :
-            Register<T>(parent, addr), cached_(false) {}
-        /// Explicit write method.
-        /// @param data The new value to write to the register.
-        void write(T const &data) {
-            char packet[sizeof(T)+1];
-            packet[0] = addr_;
-            memcpy(&packet[1], &data, sizeof(T));
-            cache_ = data;
-            cached_ = true;
-            parent_.i2c_.lock();
-            parent_.i2c_.write(parent_.addr_8bit_, packet, sizeof(T)+1);
-            parent_.i2c_.unlock();
-        }
-        /// Explicit read method with cache.
-        /// @return The current value of the register, from cache if available.
-        virtual T read() const {
-            if (!cached_) {
-                cache_ = Register<T>::read();
-                cached_ = true;
-            }
-            return cache_;
-        }
-        /// Equivalent to write().
-        /// @param data The new value to write to the register.
-        /// @return A reference to this object.
-        inline RWRegister & operator= (T const &data) {
-            write(data);
-            return *this;
-        }
-        /// Equivalent to write(), with an explicit int parameter.
-        /// @param data The new value to write to the register.
-        /// @return A reference to this object.
-        inline RWRegister & operator= (int const &data) {
-            write(T(data));
-            return *this;
-        }
-        /// Or-assignment operator.
-        /// @param data The value to OR with the register's existing contents.
-        /// @return A reference to this object.
-        inline RWRegister & operator|= (T const &data) {
-            write(read() | data);
-            return *this;
-        }
-        /// Or-assignment operator, with an explicit int parameter.
-        /// @param data The value to OR with the register's existing contents.
-        /// @return A reference to this object.
-        inline RWRegister & operator|= (int const &data) {
-            write(read() | T(data));
-            return *this;
-        }
-        /// And-assignment operator.
-        /// @param data The value to AND with the register's existing contents.
-        /// @return A reference to this object.
-        inline RWRegister & operator&= (T const &data) {
-            write(read() & data);
-            return *this;
-        }
-        /// And-assignment operator, with an explicit int parameter.
-        /// @param data The value to AND with the register's existing contents.
-        /// @return A reference to this object.
-        inline RWRegister & operator&= (int const &data) {
-            write(read() & T(data));
-            return *this;
-        }
-    };
-
-    /** Class representing binary (on/off) options.
-     * @tparam T The underlying data type of the register used to set the option.
-     * Provides a user-friendly interface onto LSM303D options.
-     */
-    template <class T>
-    class Option {
-        friend class LSM303D;
-    protected:
-        T const bits_;
-        RWRegister<T> & reg_;
-        Option(RWRegister<T> & reg, int bits) :
-            reg_(reg), bits_(T(bits)) {}
-    public:
-        /// Set the option 'on'.
-        inline void operator() () const { reg_ |= bits_; };
-        /// Set the option 'on' or 'off'.
-        /// @param sr Option status: set to true for 'on', false for 'off'.
-        inline void operator() (bool sr) const
-            { if (sr) reg_ |= bits_; else reg_ &= ~bits_; };
-    };
-        
-    const Register   <i16_3> OUT_M;         ///< Triple magnetometer register
-    const Register   <i16_3> OFFSET_M;      ///< Triple magnetometer offset register
-    const Register   <u8_3>  REFERENCE;     ///< Triple reference register
-    const Register   <i16_3> OUT_A;         ///< Triple accelerometer register
-
-public:
-    const Register   <i16>   TEMP_OUT;      ///< TEMP_OUT register, read-only
-    const Register   <u8>    STATUS_M;      ///< STATUS_M register, read-only
-    const Register   <i16>   OUT_X_M;       ///< OUT_X_M register, read-only
-    const Register   <i16>   OUT_Y_M;       ///< OUT_Y_M register, read-only
-    const Register   <i16>   OUT_Z_M;       ///< OUT_Z_M register, read-only
-    const Register   <u8>    WHO_AM_I;      ///< WHO_AM_I register, read-only
-    const Register   <u8>    INT_SRC_M;     ///< INT_SRC_M register, read-only
-    const Register   <i16>   OFFSET_X_M;    ///< OFFSET_X_M register, read-only
-    const Register   <i16>   OFFSET_Y_M;    ///< OFFSET_Y_M register, read-only
-    const Register   <i16>   OFFSET_Z_M;    ///< OFFSET_Z_M register, read-only
-    const Register   <u8>    STATUS_A;      ///< STATUS_A register, read-only
-    const Register   <i16>   OUT_X_A;       ///< OUT_X_A register, read-only
-    const Register   <i16>   OUT_Y_A;       ///< OUT_Y_A register, read-only
-    const Register   <i16>   OUT_Z_A;       ///< OUT_Z_A register, read-only
-    const Register   <u8>    FIFO_SRC;      ///< FIFO_SRC register, read-only
-    const Register   <u8>    IG_SRC1;       ///< IG_SRC1 register, read-only
-    const Register   <u8>    IG_SRC2;       ///< IG_SRC2 register, read-only
-    const Register   <u8>    CLICK_SRC;     ///< CLICK_SRC register, read-only
-
-    RWRegister <u8>    INT_CTRL_M;  ///< INT_CTRL_M register, read-write
-    RWRegister <u16>   INT_THS_M;   ///< INT_THS_M register, read-write
-    RWRegister <u8>    REFERENCE_X; ///< REFERENCE_X register, read-write
-    RWRegister <u8>    REFERENCE_Y; ///< REFERENCE_Y register, read-write
-    RWRegister <u8>    REFERENCE_Z; ///< REFERENCE_Z register, read-write
-    RWRegister <u8>    CTRL0;       ///< CTRL0 register, read-write
-    RWRegister <u8>    CTRL1;       ///< CTRL1 register, read-write
-    RWRegister <u8>    CTRL2;       ///< CTRL2 register, read-write
-    RWRegister <u8>    CTRL3;       ///< CTRL3 register, read-write
-    RWRegister <u8>    CTRL4;       ///< CTRL4 register, read-write
-    RWRegister <u8>    CTRL5;       ///< CTRL5 register, read-write
-    RWRegister <u8>    CTRL6;       ///< CTRL6 register, read-write
-    RWRegister <u8>    CTRL7;       ///< CTRL7 register, read-write
-    RWRegister <u8>    FIFO_CTRL;   ///< FIFO_CTRL register, read-write
-    RWRegister <u8>    IG_CFG1;     ///< IG_CFG1 register, read-write
-    RWRegister <u8>    IG_THS1;     ///< IG_THS1 register, read-write
-    RWRegister <u8>    IG_DUR1;     ///< IG_DUR1 register, read-write
-    RWRegister <u8>    IG_CFG2;     ///< IG_CFG2 register, read-write
-    RWRegister <u8>    IG_THS2;     ///< IG_THS2 register, read-write
-    RWRegister <u8>    IG_DUR2;     ///< IG_DUR2 register, read-write
-    RWRegister <u8>    CLICK_CFG;   ///< CLICK_CFG register, read-write
-    RWRegister <u8>    CLICK_THS;   ///< CLICK_THS register, read-write
-    RWRegister <u8>    TIME_LIMIT;  ///< TIME_LIMIT register, read-write
-    RWRegister <u8>    TIME_LATENCY;///< TIME_LATENCY register, read-write
-    RWRegister <u8>    TIME_WINDOW; ///< TIME_WINDOW register, read-write
-    RWRegister <u8>    Act_THS;     ///< Act_THS register, read-write
-    RWRegister <u8>    Act_DUR;     ///< Act_DUR register, read-write
-    
-    // CTRL1
-    /** AccelRate enum.
-     * Provides friendly names for the accelerometer update rates
-     * that are provided by the device.
-     */
-    enum class AccelRate {
-        powerdown    = 0x00,    ///< power-down mode
-        rate_3_125Hz = 0x10,    ///< 3.125Hz update rate
-        rate_6_25Hz  = 0x20,    ///< 6.25Hz update rate
-        rate_12_5Hz  = 0x30,    ///< 12.5Hz update rate
-        rate_25Hz    = 0x40,    ///< 25Hz update rate
-        rate_50Hz    = 0x50,    ///< 50Hz update rate
-        rate_100Hz   = 0x60,    ///< 100Hz update rate
-        rate_200Hz   = 0x70,    ///< 200Hz update rate
-        rate_400Hz   = 0x80,    ///< 400Hz update rate
-        rate_800Hz   = 0x90,    ///< 800Hz update rate
-        rate_1600Hz  = 0xA0     ///< 1600Hz update rate
-    };
-    /** Sets the accelerometer update rate.
-     * @param rate The new update rate.
-     * Available values are:
-     * - AccelRate::powerdown (power-down mode)
-     * - AccelRate::rate_3_125Hz (3.125Hz)
-     * - AccelRate::rate_6_25Hz (6.25Hz)
-     * - AccelRate::rate_12_5Hz (12.5Hz)
-     * - AccelRate::rate_25Hz (25Hz)
-     * - AccelRate::rate_50Hz (50Hz)
-     * - AccelRate::rate_100Hz (100Hz)
-     * - AccelRate::rate_200Hz (200Hz)
-     * - AccelRate::rate_400Hz (400Hz)
-     * - AccelRate::rate_800Hz (800Hz)
-     * - AccelRate::rate_1600Hz (1600Hz)
-     */
-    void setAccelRate(AccelRate const &rate);
-
-    
-    const Option<u8> accel_enableX;     ///< Enable or disable x-axis accelerometer
-    const Option<u8> accel_enableY;     ///< Enable or disable y-axis accelerometer
-    const Option<u8> accel_enableZ;     ///< Enable or disable z-axis accelerometer
-    const Option<u8> accel_enableAll;   ///< Enable or disable all accelerometers
-
-    // CTRL2
-    /** AccelFilter enum.
-     * Provides friendly names for the accelerometer antialiasing filter
-     * frequencies that are provided by the device.
-     */
-    enum class AccelFilter {
-        filter_773Hz = 0x00,    ///< 773Hz corner frequency
-        filter_194Hz = 0x40,    ///< 194Hz corner frequency
-        filter_362Hz = 0x80,    ///< 362Hz corner frequency
-        filter_50Hz  = 0xC0     ///< 50Hz corner frequency
-    };
-    /** Sets the accelerometer antialiasing filter frequency.
-     * @param freq The new frequency.
-     * Available values are:
-     * - AccelFilter::filter_773Hz
-     * - AccelFilter::filter_194Hz
-     * - AccelFilter::filter_362Hz
-     * - AccelFilter::filter_50Hz
-     */
-    void setAccelFilterFreq(AccelFilter const &freq);
-    /** AccelScale enum.
-     * Provides friendly names for the accelerometer scale ranges
-     * that are provided by the device.
-     */
-    enum class AccelScale {
-        scale_2g  = 0x00,   ///< +-2g full scale
-        scale_4g  = 0x08,   ///< +-4g full scale
-        scale_6g  = 0x10,   ///< +-6g full scale
-        scale_8g  = 0x18,   ///< +-8g full scale
-        scale_16g = 0x20    ///< +-16g full scale
-    };
-    /** Sets the accelerometer scale range.
-     * @param scale The new scale.
-     * Available values are:
-     * - AccelScale::scale_2g (+-2g)
-     * - AccelScale::scale_4g (+-4g)
-     * - AccelScale::scale_6g (+-6g)
-     * - AccelScale::scale_8g (+-8g)
-     * - AccelScale::scale_16g (+-16g)
-     */
-    void setAccelScale(AccelScale const &scale);
-
-    // CTRL3
-    const Option<u8> INT1_enable_BOOT;  ///< Enable or disable "boot" source for INT1
-    const Option<u8> INT1_enable_CLICK; ///< Enable or disable "click" source for INT1
-    const Option<u8> INT1_enable_IG1;   ///< Enable or disable "inertial 1" source for INT1
-    const Option<u8> INT1_enable_IG2;   ///< Enable or disable "inertial 2" source for INT1
-    const Option<u8> INT1_enable_IGM;   ///< Enable or disable "magnetic" source for INT1
-    const Option<u8> INT1_enable_DRDY_A;///< Enable or disable "accelerometer data ready" source for INT1
-    const Option<u8> INT1_enable_DRDY_M;///< Enable or disable "magnetometer data ready" source for INT1
-    const Option<u8> INT1_enable_EMPTY; ///< Enable or disable "FIFO empty" source for INT1
-    
-    // CTRL4 concerns INT2 which is not broken out on the Pimoroni board
-    
-    // CTRL5
-    const Option<u8> temp_enable;   ///< Enable or disable temperature sensor
-    const Option<u8> LIR1_enable;   ///< Enable or disable latching interrupt requests for INT1
-    
-    /** MagRes enum.
-     * Provides friendly names for the magnetometer resolution options
-     * that are provided by the device.
-     */
-    enum class MagRes {
-        high = 0x60,    ///< High resolution
-        low  = 0x00,    ///< Low resolution
-    };
-    /** Sets the magnetometer resolution.
-     * @param res The new resolution.
-     * Available values are:
-     * - MagRes::high (high resolution)
-     * - MagRes::low (low resolution)
-     */
-    void setMagRes(MagRes const &res);
-
-    /** MagRate enum.
-     * Provides friendly names for the magnetometer update rates
-     * that are provided by the device.
-     */
-    enum class MagRate {
-        rate_3_125Hz = 0x00,    ///< 3.125Hz update rate
-        rate_6_25Hz  = 0x04,    ///< 6.25Hz update rate
-        rate_12_5Hz  = 0x08,    ///< 12.5Hz update rate
-        rate_25Hz    = 0x0C,    ///< 25Hz update rate
-        rate_50Hz    = 0x10,    ///< 50Hz update rate
-        rate_100Hz   = 0x14     ///< 100Hz update rate
-    };    
-    /** Sets the magnetometer update rate.
-     * @param rate The new update rate.
-     * Available values are:
-     * - MagRate::rate_3_125Hz (3.125Hz)
-     * - MagRate::rate_6_25Hz (6.25Hz)
-     * - MagRate::rate_12_5Hz (12.5Hz)
-     * - MagRate::rate_25Hz (25Hz)
-     * - MagRate::rate_50Hz (50Hz)
-     * - MagRate::rate_100Hz (100Hz)
-     * Note that the 100Hz update rate is available only if the accelerometer
-     * update rate is set to 50Hz or higher or the accelerometer is set to
-     * power-down mode (see setAccelRate()).  Failing to meet these conditions
-     * will lead to undefined behaviour.
-     */
-    void setMagRate(MagRate const &rate);
-
-    // CTRL6
-    /** MagScale enum.
-     * Provides friendly names for the magnetometer scale ranges
-     * that are provided by the device.
-     */
-    enum class MagScale {
-        scale_2G  = 0x00,   ///< +-2 Gauss
-        scale_4G  = 0x20,   ///< +-4 Gauss
-        scale_8G  = 0x40,   ///< +-8 Gauss
-        scale_12G = 0x60    ///< +-12 Gauss
-    };
-    /** Sets the magnetometer scale range.
-     * @param scale The new scale.
-     * Available values are:
-     * - MagScale::scale_2G (+-2 Gauss)
-     * - MagScale::scale_4G (+-4 Gauss)
-     * - MagScale::scale_8G (+-8 Gauss)
-     * - MagScale::scale_12G (+-16 Gauss)
-     */
-    void setMagScale(MagScale const &scale);
-
-    // CTRL7
-    /** MagScale enum.
-     * Provides friendly names for the magnetometer modes that are supported
-     * by the device.
-     */
-    enum class MagMode {
-        continuous  = 0x00, ///< Continous conversion mode
-        single      = 0x01, ///< Single conversion mode
-        powerdown   = 0x02  ///< Magnetometer power-down mode
-    };
-    /** Sets the magnetometer mode.
-     * @param mode The new mode.
-     * Available values are:
-     * - MagMode::continuous (continuous conversion mode)
-     * - MagMode::single (single-conversion mode)
-     * - MagMode::powerdown (power-down mode)
-     */
-    void setMagMode(MagMode const &mode);
-
-    /** Constructor.
-     * @param sda The pin to use for the I^^2^^C SDA signal.
-     * @param scl The pin to use for the I^^2^^C SCL signal.
-     * @param i2c_addr The 7-bit I<sup>2</sup>C address.  Defaults to 0x1D, but
-     * this can be overridden to 0x1E with a jumper setting on the device allowing
-     * two LSM303D devices to coexist on a bus.
-     */
-    LSM303D(PinName sda, PinName scl, unsigned char i2c_addr = 0x1D);
-    
-    /** Gets the raw (signed 16-bit integer) X-axis acceleration value.
-     * @return The raw acceleration value.
-     */
-    inline i16 getRawAccelX() { return OUT_X_A; };
-    /** Gets the raw (signed 16-bit integer) Y-axis acceleration value.
-     * @return The raw acceleration value.
-     */
-    inline i16 getRawAccelY() { return OUT_Y_A; };
-    /** Gets the raw (signed 16-bit integer) Z-axis acceleration value.
-     * @return The raw acceleration value.
-     */
-    inline i16 getRawAccelZ() { return OUT_Z_A; };
-    /** Gets the scaled (float) X-axis acceleration value in g.
-     * @return The scaled acceleration value in g.
-     */
-    inline float getAccelX() { return (float)OUT_X_A / accel_scale_ / 32768; };
-    /** Gets the scaled (float) Y-axis acceleration value in g.
-     * @return The scaled acceleration value in g.
-     */
-    inline float getAccelY() { return (float)OUT_Y_A / accel_scale_ / 32768; };
-    /** Gets the scaled (float) Z-axis acceleration value in g.
-     * @return The scaled acceleration value in g.
-     */
-    inline float getAccelZ() { return (float)OUT_Z_A / accel_scale_ / 32768; };
-    /** Reads the raw (signed 16-bit integer) acceleration values into the three
-     * axis value containers provided.
-     * @param x Container for the X value.
-     * @param y Container for the Y value.
-     * @param z Container for the Z value.
-     */
-    void getRawAccelInto(short &x, short &y, short &z);
-    /** Reads the scaled (float) acceleration values in g into the three
-     * axis value containers provided.
-     * @param x Container for the X value.
-     * @param y Container for the Y value.
-     * @param z Container for the Z value.
-     */
-    void getAccelInto(float &x, float &y, float &z);
-    
-    /** Gets the raw (signed 16-bit integer) X-axis magnetometer value.
-     * @return The raw magnetometer value.
-     */
-    inline i16 getRawMagX() { return OUT_X_M; };
-    /** Gets the raw (signed 16-bit integer) Y-axis magnetometer value.
-     * @return The raw magnetometer value.
-     */
-    inline i16 getRawMagY() { return OUT_Y_M; };
-    /** Gets the raw (signed 16-bit integer) Z-axis magnetometer value.
-     * @return The raw magnetometer value.
-     */
-    inline i16 getRawMagZ() { return OUT_Z_M; };
-    /** Gets the scaled (float) X-axis magnetometer value in Gauss.
-     * @return The scaled magnetometer value in Gauss.
-     */
-    inline float getMagX() { return (float)OUT_X_M / mag_scale_ / 32768; };
-    /** Gets the scaled (float) Y-axis magnetometer value in Gauss.
-     * @return The scaled magnetometer value in Gauss.
-     */
-    inline float getMagY() { return (float)OUT_Y_M / mag_scale_ / 32768; };
-    /** Gets the scaled (float) Z-axis magnetometer value in Gauss.
-     * @return The scaled magnetometer value in Gauss.
-     */
-    inline float getMagZ() { return (float)OUT_Z_M / mag_scale_ / 32768; };
-    /** Reads the raw (signed 16-bit integer) magnetometer values into the three
-     * axis value containers provided.
-     * @param x Container for the X value.
-     * @param y Container for the Y value.
-     * @param z Container for the Z value.
-     */
-    void getRawMagInto(short &x, short &y, short &z);
-    /** Reads the scaled (float) magnetometer values in Gauss into the three
-     * axis value containers provided.
-     * @param x Container for the X value.
-     * @param y Container for the Y value.
-     * @param z Container for the Z value.
-     */
-    void getMagInto(float &x, float &y, float &z);
-    
-    /** Reads the scaled (float) temperature values in &deg; C.
-     * @return The current temperature.
-     * Note that testing suggests this to be an inaccurate temperature sensor!
-     * To read the raw temperature value, simply use the TEMP_OUT register directly.
-     */
-    inline float getTemp() { return TEMP_OUT / 8.0 + 25; };
-    
-};