├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── examples ├── arduino │ ├── lib │ │ └── i2c-sensor-hal │ ├── platformio.ini │ └── src │ │ └── main.ino ├── blynk │ ├── README.md │ ├── lib │ │ └── i2c-sensor-hal │ ├── platformio.ini │ └── src │ │ └── main.ino ├── mbed │ ├── lib │ │ └── i2c-sensor-hal │ ├── platformio.ini │ └── src │ │ └── main.cpp ├── particle │ ├── Makefile │ ├── lib │ │ └── i2c-sensor-hal │ ├── package.json │ └── src │ │ └── main.ino └── wiringpi │ ├── lib │ └── i2c-sensor-hal │ ├── platformio.ini │ └── src │ └── main.cpp ├── library.json ├── library.properties └── src ├── AK8963.h ├── Accelerometer.cpp ├── Accelerometer.h ├── BMP085.h ├── Barometer.cpp ├── Barometer.h ├── Gyroscope.h ├── HMC5883L.h ├── I2CDevice.cpp ├── I2CDevice.h ├── I2CDevice_arduino.cpp ├── I2CDevice_mbed.cpp ├── I2CDevice_wiringpi.cpp ├── MPU6050.h ├── Magnetometer.cpp ├── Magnetometer.h ├── Sensors.h ├── Thermometer.h └── Vector3.h /.editorconfig: -------------------------------------------------------------------------------- 1 | ; indicate this is the root of the project 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | 9 | [Makefile] 10 | indent_style = tab 11 | indent_size = 8 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .pioenvs 3 | *.bin 4 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | 5 | cache: 6 | directories: 7 | - "~/.platformio" 8 | 9 | env: 10 | - PLATFORMIO_PROJECT_DIR=examples/arduino 11 | - PLATFORMIO_PROJECT_DIR=examples/mbed 12 | 13 | install: 14 | - pip install -U platformio 15 | 16 | script: 17 | - platformio run -d $PLATFORMIO_PROJECT_DIR 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopj/i2c-hal/efcbfc0cd3571831c8d50d28bba8161c1cadc4bb/CHANGELOG.md -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - [Fork](https://help.github.com/articles/fork-a-repo) the library 4 | - Build and test your changes 5 | - Commit and push until you are happy with your contribution 6 | - [Make a pull request](https://help.github.com/articles/using-pull-requests) 7 | - Thanks! 8 | 9 | ### Adding a New Sensor Device 10 | 11 | Adding a new sensor device is fairly simple if you are familiar with I2C operations and device datasheets. Make a new [header only](https://en.wikipedia.org/wiki/Header-only) class, and make sure to inherit from the correct sensor type(s), eg `Thermometer`. 12 | 13 | You'll need to implement any virtual methods, eg `getTemperature`, being sure to return data in the [SI units](https://en.wikipedia.org/wiki/International_System_of_Unit) required by the sensor type. 14 | 15 | Try to follow the conventions used in existing sensor devices, take a look at [`BMP085.h`](https://github.com/loopj/i2c-sensor-hal/blob/master/src/BMP085.h) for a full example. 16 | 17 | ### Adding a New Sensor Type 18 | 19 | If you'd like to add a new sensor type, for example you'd like to add an [Anemometer](https://en.wikipedia.org/wiki/Anemometer) to measure wind speed, you'll need to create a new class with virtual methods. 20 | 21 | Make sure that your class defines a method which returns sensor results in [SI units](https://en.wikipedia.org/wiki/International_System_of_Units), eg meters per second. 22 | 23 | Take a look at [`Accelerometer.h`](https://github.com/loopj/i2c-sensor-hal/blob/master/src/Accelerometer.h) for a full example. 24 | 25 | 26 | ### Adding a New Framework 27 | 28 | Adding support for a new development framework is usually relatively simple. You'll need to implement the `readBytes`, `writeBytes` and `usleep` I2C functions in a new file named `I2CDevice_yourplatform.cpp`. 29 | 30 | Make sure to wrap the implementation in framework-specific include guards, for example: 31 | 32 | ```c++ 33 | #ifdef ARDUINO 34 | // Your code 35 | #endif 36 | ``` 37 | 38 | Take a look at [`I2CDevice_arduino.cpp`](https://github.com/loopj/i2c-sensor-hal/blob/master/src/I2CDevice_arduino.cpp) for a full example. 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 James Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I2C Abstraction Layer 2 | 3 | [![Build Status](https://travis-ci.org/loopj/i2c-hal.svg?branch=master)](https://travis-ci.org/loopj/i2c-hal) 4 | 5 | This library allows you to use I2C devices such as accelerometers, gyroscopes, and barometers in your [Arduino][1], [ESP8266][2], [mbed][9], [Particle][3] and [Raspberry Pi][4] projects, without knowing the intimate details about the sensor chip. Say goodbye to reading device data-sheets or learning complex I2C interactions. 6 | 7 | Prototype on Arduino or Particle and use the same code when moving to production. 8 | 9 | Sensor functions always return [SI units](https://en.wikipedia.org/wiki/International_System_of_Units), so no extra conversions are required in most situations. 10 | 11 | ## Contents 12 | 13 | - [Getting Started](#getting-started) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Sensors](#sensors) 17 | - [Accelerometer](#accelerometer) 18 | - [Barometer](#barometer) 19 | - [Gyroscope](#gyroscope) 20 | - [Magnetometer](#magnetometer) 21 | - [Thermometer](#thermometer) 22 | - [Supported Devices](#supported-devices) 23 | - [Platforms, Frameworks & Boards](#platforms-frameworks--boards) 24 | - [Contributing](#contributing) 25 | - [License](#license) 26 | 27 | ## Getting Started 28 | 29 | ### Installation 30 | 31 | #### Using PlatformIO 32 | 33 | The recommended way to use this library is with [PlatformIO](http://platformio.org/): 34 | 35 | ```shell 36 | $ platformio lib install 578 37 | ``` 38 | 39 | #### Using the Arduino Library Manager 40 | 41 | If you are using the Arduino IDE, find `Sensors` by James Smith in the Library Manager and click install. 42 | 43 | #### Manual Installation 44 | 45 | If you can't use PlatformIO or Arduino Library Manager, you can always simply copy the library into your project or include it as a git submodule. This is the preferred approach for the Particle platform, as you can see in the [particle example app ](https://github.com/loopj/i2c-sensor-hal/tree/master/examples/particle). 46 | 47 | ## Usage 48 | 49 | Once you've installed the library, you'll need to define which sensor devices are installed (see [supported devices](#supported-devices)) and then include `Sensors.h`: 50 | 51 | ```c++ 52 | #define SENSORS_MPU6050_ATTACHED 53 | #define SENSORS_BMP085_ATTACHED 54 | #include 55 | ``` 56 | 57 | You'll then be able to use sensors as follows: 58 | 59 | ```c++ 60 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 61 | Vector3 acceleration = accelerometer->getAcceleration(); 62 | ``` 63 | 64 | See the [sensor types](#sensor-types) section below for details on each sensor type, and check out the [example apps folder](https://github.com/loopj/i2c-sensor-hal/tree/master/examples) for some complete examples. 65 | 66 | 67 | ## Sensors 68 | 69 | The following sensor types are currently supported by this library: 70 | 71 | ### Accelerometer 72 | 73 | An [accelerometer](https://en.wikipedia.org/wiki/Accelerometer) measures [proper acceleration](https://en.wikipedia.org/wiki/Proper_acceleration) (including gravity) in m/s², on three axes. 74 | 75 | ```c++ 76 | // Get access to the accelerometer 77 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 78 | 79 | // Get the current acceleration vector (x/y/z), in m/s^2 80 | Vector3 acceleration = accelerometer->getAcceleration(); 81 | ``` 82 | 83 | ### Barometer 84 | 85 | A [barometer](https://en.wikipedia.org/wiki/Barometer) measures [atmospheric pressure](https://en.wikipedia.org/wiki/Atmospheric_pressure) in [hPa](https://en.wikipedia.org/wiki/Pascal_(unit)) (or millibars). 86 | 87 | Using atmospheric pressure from a barometer, you can also compute the [altitude](https://en.wikipedia.org/wiki/Altitude) in meters. 88 | 89 | ```c++ 90 | // Get access to the barometer 91 | Barometer *barometer = Sensors::getBarometer(); 92 | 93 | // Get the current ambient air pressure in hPA (mbar) 94 | float pressure = barometer->getPressure(); 95 | 96 | // Get the current altitude in m, based on the standard baseline pressure 97 | float altitude = barometer->getAltitude(); 98 | 99 | // Get the current altitude in m, based on a provided baseline pressure 100 | float altitude = barometer->getAltitude(baselinePressure); 101 | 102 | // Get the pressure at sea-level in hPa, given the current altitude 103 | float sealevelPressure = barometer->getSealevelPressure(altitude); 104 | ``` 105 | 106 | ### Gyroscope 107 | 108 | A [gyroscope](https://en.wikipedia.org/wiki/Gyroscope) measures the [rotational speed](https://en.wikipedia.org/wiki/Rotational_speed) in [rad/s](https://en.wikipedia.org/wiki/Radian_per_second), on three axes. 109 | 110 | ```c++ 111 | // Get access to the gyroscope 112 | Gyroscope *gyroscope = Sensors::getGyroscope(); 113 | 114 | // Get the current rotation rate vector (x/y/z), in rad/s 115 | Vector3 rotation = gyroscope->getRotation(); 116 | ``` 117 | 118 | ### Magnetometer 119 | 120 | A [magnetometer](https://en.wikipedia.org/wiki/Magnetometer) measures the strength and direction of [magnetic fields](https://en.wikipedia.org/wiki/Magnetic_field) in [μT](https://en.wikipedia.org/wiki/Tesla_(unit)), on three axes. 121 | 122 | Using magnetic field readings from a magnetometer, you can also compute the [azimuth](https://en.wikipedia.org/wiki/Azimuth) (or compass direction) of your device. 123 | 124 | ```c++ 125 | // Get access to the magnetometer 126 | Magnetometer *magnetometer = Sensors::getMagnetometer(); 127 | 128 | // Get the current magnetic field strength vector (x/y/z), in μT 129 | Vector3 magneticField = magnetometer->getMagneticField(); 130 | 131 | // Get the current azimuth (compass direction), optionally adjusting for declination 132 | float azimuth = magnetometer->getAzimuth(); 133 | ``` 134 | 135 | ### Thermometer 136 | 137 | A [thermometer](https://en.wikipedia.org/wiki/Thermometer) measures [temperature](https://en.wikipedia.org/wiki/Temperature) in [°C](https://en.wikipedia.org/wiki/Celsius). 138 | 139 | ```c++ 140 | // Get access to the thermometer 141 | Thermometer *thermometer = Sensors::getThermometer(); 142 | 143 | // Get the current temperature, in °C 144 | float temperature = thermometer->getTemperature(); 145 | ``` 146 | 147 | 148 | ## Supported Devices 149 | 150 | The following I2C devices are currently supported by this library: 151 | 152 | | Device | Provides Sensors | #define 153 | |---------- |---------------------------------------- |-------------------------- 154 | | AK8963 | Magnetometer | SENSORS_AK8963_ATTACHED 155 | | BMP085 | Barometer, Thermometer | SENSORS_BMP085_ATTACHED 156 | | BMP180 | Barometer, Thermometer | SENSORS_BMP180_ATTACHED 157 | | HMC5883L | Magnetometer | SENSORS_HMC5883L_ATTACHED 158 | | MPU6050 | Accelerometer, Gyroscope | SENSORS_MPU6050_ATTACHED 159 | | MPU6500 | Accelerometer, Gyroscope | SENSORS_MPU6500_ATTACHED 160 | | MPU9150 | Accelerometer, Gyroscope, Magnetometer | SENSORS_MPU9150_ATTACHED 161 | | MPU9250 | Accelerometer, Gyroscope, Magnetometer | SENSORS_MPU9250_ATTACHED 162 | 163 | If you'd like to see another sensor device supported here, see the [contributing](#contributing) section below. 164 | 165 | 166 | ## Platforms, Frameworks & Boards 167 | 168 | This library supports almost every popular embedded platform, including the following development boards: 169 | 170 | | Platform | Boards 171 | |-------------------|---------------------------------------------------------- 172 | | [Arduino][1] | Any [Atmel AVR][6] or [Atmel SAM][10] based Ardunio, or Arduino-like board 173 | | [ESP8266][2] | Any [ESP8266 based][7] board 174 | | [mbed][9] | Most boards using the ARM mbed framework 175 | | [Particle][3] | Particle Core, Particle Photon, Particle Electron 176 | | [Raspberry Pi][4] | Any board which support [WiringPi][8] 177 | | [Teensy][5] | Any Teensy board 178 | 179 | If you test a new platform or development board and can confirm it works, please let me know in [an issue](https://github.com/loopj/i2cdevlib-hal/issues) and I'll update this documentation. 180 | 181 | 182 | ## Contributing 183 | 184 | I'd love you to file issues and send pull requests. The [contributing guidelines](CONTRIBUTING.md) details the process of adding support for sensors, devices and frameworks, building and testing the library, as well as the pull request process. Feel free to comment on [existing issues](https://github.com/loopj/i2c-hal/issues) for clarification or starting points. 185 | 186 | 187 | ## License 188 | 189 | This library is free software released under the MIT License. See [LICENSE.txt](LICENSE.txt) for details. 190 | 191 | 192 | [1]: https://www.arduino.cc/ 193 | [2]: https://en.wikipedia.org/wiki/ESP8266 194 | [3]: https://www.particle.io/ 195 | [4]: https://www.raspberrypi.org/ 196 | [5]: https://www.pjrc.com/teensy/ 197 | [6]: http://platformio.org/#!/boards?filter%5Bplatform%5D=atmelavr 198 | [7]: http://platformio.org/#!/boards?filter%5Bplatform%5D=espressif 199 | [8]: http://wiringpi.com/ 200 | [9]: https://www.mbed.com/ 201 | [10]: http://platformio.org/#!/boards?filter%5Bplatform%5D=atmelsam 202 | -------------------------------------------------------------------------------- /examples/arduino/lib/i2c-sensor-hal: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /examples/arduino/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | [env:micro] 9 | platform = atmelavr 10 | framework = arduino 11 | board = micro 12 | 13 | [env:due] 14 | platform = atmelsam 15 | framework = arduino 16 | board = due 17 | 18 | [env:teensy31] 19 | platform = teensy 20 | framework = arduino 21 | board = teensy31 22 | 23 | [env:nodemcu] 24 | platform = espressif 25 | framework = arduino 26 | board = nodemcu 27 | -------------------------------------------------------------------------------- /examples/arduino/src/main.ino: -------------------------------------------------------------------------------- 1 | // Define which sensors are attached 2 | #define SENSORS_BMP180_ATTACHED 3 | #define SENSORS_HMC5883L_ATTACHED 4 | #define SENSORS_MPU6050_ATTACHED 5 | 6 | #include 7 | #include 8 | 9 | void setup() { 10 | // Activate serial port (for debug printing) 11 | Serial.begin(9600); 12 | 13 | // Activate i2c 14 | Wire.begin(); 15 | 16 | // Initialize devices 17 | Sensors::initialize(); 18 | } 19 | 20 | void loop() { 21 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 22 | if(accelerometer) { 23 | Vector3 a = accelerometer->getAcceleration(); 24 | Serial.print("Acceleration (m/s^2) "); 25 | Serial.print(a.x); 26 | Serial.print(", "); 27 | Serial.print(a.y); 28 | Serial.print(", "); 29 | Serial.println(a.z); 30 | } 31 | 32 | Barometer *barometer = Sensors::getBarometer(); 33 | if(barometer) { 34 | float p = barometer->getPressure(); 35 | Serial.print("Pressure (hPa) "); 36 | Serial.println(p); 37 | 38 | float a = barometer->getAltitude(); 39 | Serial.print("Altitude (m) "); 40 | Serial.println(a); 41 | } 42 | 43 | Gyroscope *gyroscope = Sensors::getGyroscope(); 44 | if(gyroscope) { 45 | Vector3 g = gyroscope->getRotation(); 46 | Serial.print("Rotation (rad/s) "); 47 | Serial.print(g.x); 48 | Serial.print(", "); 49 | Serial.print(g.y); 50 | Serial.print(", "); 51 | Serial.println(g.z); 52 | } 53 | 54 | Magnetometer *magnetometer = Sensors::getMagnetometer(); 55 | if(magnetometer) { 56 | Vector3 m = magnetometer->getMagneticField(); 57 | Serial.print("Magnetic Field (uT) "); 58 | Serial.print(m.x); 59 | Serial.print(", "); 60 | Serial.print(m.y); 61 | Serial.print(", "); 62 | Serial.println(m.z); 63 | 64 | float azimuth = magnetometer->getAzimuth(); 65 | Serial.print("Azimuth (deg) "); 66 | Serial.println(azimuth); 67 | } 68 | 69 | Thermometer *thermometer = Sensors::getThermometer(); 70 | if(thermometer) { 71 | float t = thermometer->getTemperature(); 72 | Serial.print("Temperature (C) "); 73 | Serial.println(t); 74 | } 75 | 76 | delay(50); 77 | } 78 | -------------------------------------------------------------------------------- /examples/blynk/README.md: -------------------------------------------------------------------------------- 1 | # Blynk Sensors Example App 2 | 3 | Display sensor values on your iOS/Android device using [Blynk](http://www.blynk.cc/). 4 | 5 | This example uses a ESP8266 device with a BMP085 sensor connected, but can easily be modified for other microcontrollers or sensors. 6 | 7 | ![Screenshot of Blynk app with Sensor data](http://i.imgur.com/YeAeTOH.png) 8 | 9 | ## Download the Blynk App 10 | 11 | See Blynk's [getting started](http://www.blynk.cc/getting-started/) guide to download the Blynk app, and create a project with two charts (like the screenshot above). 12 | 13 | ## Install PlatformIO 14 | 15 | ```shell 16 | python -c "$(curl -fsSL https://raw.githubusercontent.com/platformio/platformio/master/scripts/get-platformio.py)" 17 | ``` 18 | 19 | ## Set your Blynk auth token in `main.ino` 20 | 21 | ```c++ 22 | // Blynk auth token (from the blynk app) 23 | char blynkToken[] = "13efc9ffb16b40ae97f6b2f7726358be"; 24 | 25 | // WiFi access point credentials 26 | char ssid[] = "my-wifi-network"; 27 | char password[] = "p455w0rd"; 28 | ``` 29 | 30 | ## Build and upload to ESP8266 31 | 32 | ```shell 33 | $ platformio run -t upload 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/blynk/lib/i2c-sensor-hal: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /examples/blynk/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | [env:nodemcu] 9 | platform = espressif 10 | framework = arduino 11 | board = nodemcu 12 | 13 | # Install the Blynk library 14 | lib_install = 415 15 | -------------------------------------------------------------------------------- /examples/blynk/src/main.ino: -------------------------------------------------------------------------------- 1 | // Define which sensors are attached 2 | #define SENSORS_BMP085_ATTACHED 3 | 4 | // Debug printing 5 | #define BLYNK_PRINT Serial 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | // Blynk auth token (from the blynk app) 12 | char blynkToken[] = "your-blynk-token"; 13 | 14 | // WiFi access point credentials 15 | char ssid[] = "your-wifi-ssid"; 16 | char password[] = "your-wifi-password"; 17 | 18 | void setup() { 19 | // Activate serial port (for debug printing) 20 | Serial.begin(9600); 21 | 22 | // Activate i2c 23 | Wire.begin(); 24 | 25 | // Initialize WiFi and connect to Blynk 26 | Blynk.begin(blynkToken, ssid, password); 27 | 28 | // Initialize devices 29 | Sensors::initialize(); 30 | } 31 | 32 | // When Blynk requests the value of virtual pin 0, send back the air pressure 33 | BLYNK_READ(V0) { 34 | Barometer *barometer = Sensors::getBarometer(); 35 | if(barometer) { 36 | float p = barometer->getPressure(); 37 | Blynk.virtualWrite(V0, p); 38 | } 39 | } 40 | 41 | // When Blynk requests the value of virtual pin 1, send back the temperature 42 | BLYNK_READ(V1) { 43 | Thermometer *thermometer = Sensors::getThermometer(); 44 | if(thermometer) { 45 | float t = thermometer->getTemperature(); 46 | Blynk.virtualWrite(V1, t); 47 | } 48 | } 49 | 50 | void loop() { 51 | Blynk.run(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/mbed/lib/i2c-sensor-hal: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /examples/mbed/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | [env:teensy31] 9 | platform = teensy 10 | framework = mbed 11 | board = teensy31 12 | -------------------------------------------------------------------------------- /examples/mbed/src/main.cpp: -------------------------------------------------------------------------------- 1 | // Define which sensors are attached 2 | #define SENSORS_BMP085_ATTACHED 3 | #define SENSORS_HMC5883L_ATTACHED 4 | #define SENSORS_MPU6050_ATTACHED 5 | 6 | #include "mbed.h" 7 | #include "USBSerial.h" 8 | #include "Sensors.h" 9 | 10 | USBSerial serial; 11 | 12 | // Quick and dirty way to print floats 13 | void printFloat(float flt, int precision=2) { 14 | serial.printf("%d.%d", (int)flt, abs((int)((flt - (int)flt) * pow(10,precision)))); 15 | } 16 | 17 | int main() { 18 | // Initialize devices 19 | Sensors::initialize(); 20 | 21 | while(1) { 22 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 23 | if(accelerometer) { 24 | Vector3 a = accelerometer->getAcceleration(); 25 | serial.printf("Acceleration (m/s^2) "); 26 | printFloat(a.x); serial.printf(", "); 27 | printFloat(a.y); serial.printf(", "); 28 | printFloat(a.z); serial.printf("\n"); 29 | } 30 | 31 | Barometer *barometer = Sensors::getBarometer(); 32 | if(barometer) { 33 | float p = barometer->getPressure(); 34 | serial.printf("Pressure (hPa) "); 35 | printFloat(p); serial.printf("\n"); 36 | 37 | float a = barometer->getAltitude(); 38 | serial.printf("Altitude (m) "); 39 | printFloat(a); serial.printf("\n"); 40 | } 41 | 42 | Gyroscope *gyroscope = Sensors::getGyroscope(); 43 | if(gyroscope) { 44 | Vector3 g = gyroscope->getRotation(); 45 | serial.printf("Rotation (rad/s) "); 46 | printFloat(g.x); serial.printf(", "); 47 | printFloat(g.y); serial.printf(", "); 48 | printFloat(g.z); serial.printf("\n"); 49 | } 50 | 51 | Magnetometer *magnetometer = Sensors::getMagnetometer(); 52 | if(magnetometer) { 53 | Vector3 m = magnetometer->getMagneticField(); 54 | serial.printf("Magnetic Field (uT) "); 55 | printFloat(m.x); serial.printf(", "); 56 | printFloat(m.y); serial.printf(", "); 57 | printFloat(m.z); serial.printf("\n"); 58 | 59 | float azimuth = magnetometer->getAzimuth(); 60 | serial.printf("Azimuth (deg) "); 61 | printFloat(azimuth); serial.printf("\n"); 62 | } 63 | 64 | Thermometer *thermometer = Sensors::getThermometer(); 65 | if(thermometer) { 66 | float t = thermometer->getTemperature(); 67 | serial.printf("Temperature (C) "); 68 | printFloat(t); serial.printf("\n"); 69 | } 70 | 71 | wait_ms(50); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/particle/Makefile: -------------------------------------------------------------------------------- 1 | BINARY_NAME = i2c-sensor-hal 2 | PARTICLE_CLI ?= npx particle 3 | DEVICE_TYPE ?= photon 4 | FIRMWARE = $(BINARY_NAME)-$(DEVICE_TYPE).bin 5 | SOURCES = $(shell find -L lib src -type f -name '*.h' -o -name '*.cpp' -o -name '*.ino') 6 | 7 | .PHONY: all install install-usb clean checkenv enter-dfu-mode 8 | 9 | $(FIRMWARE): 10 | $(PARTICLE_CLI) compile $(DEVICE_TYPE) $(SOURCES) --saveTo $(FIRMWARE) 11 | 12 | install: checkenv $(FIRMWARE) ## Build the firmware and flash it via the Particle Cloud 13 | $(PARTICLE_CLI) flash $(DEVICE_NAME) $(FIRMWARE) 14 | 15 | install-usb: $(FIRMWARE) enter-dfu-mode ## Build the firmware and flash it via USB 16 | $(PARTICLE_CLI) flash --usb $(FIRMWARE) 17 | 18 | clean: ## Delete all firmware files 19 | rm -f *.bin 20 | 21 | enter-dfu-mode: ## Enter DFU mode on the attached USB device 22 | -stty -f /dev/tty.usbmodem1411 14400 23 | 24 | checkenv: 25 | ifndef DEVICE_NAME 26 | $(error DEVICE_NAME is not set) 27 | endif 28 | 29 | help: 30 | @awk -F ':|##' '/^[^\t].+?:.*?##/ {\ 31 | printf "\033[36m%-20s\033[0m %s\n", $$1, $$NF \ 32 | }' $(MAKEFILE_LIST) 33 | -------------------------------------------------------------------------------- /examples/particle/lib/i2c-sensor-hal: -------------------------------------------------------------------------------- 1 | ../../../src -------------------------------------------------------------------------------- /examples/particle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "particle-cli": "*" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/particle/src/main.ino: -------------------------------------------------------------------------------- 1 | // Define which sensors are attached 2 | #define SENSORS_MPU9150_ATTACHED 3 | 4 | #include 5 | 6 | void setup() { 7 | // Activate serial port (for debug printing) 8 | Serial.begin(115200); 9 | 10 | // Activate high-speed i2c 11 | Wire.setSpeed(CLOCK_SPEED_400KHZ); 12 | Wire.begin(); 13 | 14 | // Initialize devices 15 | Sensors::initialize(); 16 | } 17 | 18 | void loop() { 19 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 20 | if(accelerometer) { 21 | Vector3 a = accelerometer->getAcceleration(); 22 | Serial.printlnf("Acceleration (m/s^2) %+7.3f, %+7.3f, %+7.3f", a.x, a.y, a.z); 23 | } 24 | 25 | Barometer *barometer = Sensors::getBarometer(); 26 | if(barometer) { 27 | float p = barometer->getPressure(); 28 | Serial.printlnf("Pressure (hPa) %+7.3f", p); 29 | 30 | float a = barometer->getAltitude(); 31 | Serial.printlnf("Altitude (m) %+7.3f", a); 32 | } 33 | 34 | Gyroscope *gyroscope = Sensors::getGyroscope(); 35 | if(gyroscope) { 36 | Vector3 g = gyroscope->getRotation(); 37 | Serial.printlnf("Rotation (rad/s) %+7.3f, %+7.3f, %+7.3f", g.x, g.y, g.z); 38 | } 39 | 40 | Magnetometer *magnetometer = Sensors::getMagnetometer(); 41 | if(magnetometer) { 42 | Vector3 m = magnetometer->getMagneticField(); 43 | Serial.printlnf("Magnetic Field (uT) %+7.3f, %+7.3f, %+7.3f", m.x, m.y, m.z); 44 | 45 | float azimuth = magnetometer->getAzimuth(); 46 | Serial.printlnf("Azimuth (deg) %+7.3f", azimuth); 47 | } 48 | 49 | Thermometer *thermometer = Sensors::getThermometer(); 50 | if(thermometer) { 51 | float t = thermometer->getTemperature(); 52 | Serial.printlnf("Temperature (C) %+7.3f", t); 53 | } 54 | 55 | delay(50); 56 | } 57 | -------------------------------------------------------------------------------- /examples/wiringpi/lib/i2c-sensor-hal: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /examples/wiringpi/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | [env:raspberrypi_2b] 9 | platform = linux_arm 10 | framework = wiringpi 11 | board = raspberrypi_2b 12 | -------------------------------------------------------------------------------- /examples/wiringpi/src/main.cpp: -------------------------------------------------------------------------------- 1 | // Define which sensors are attached 2 | #define SENSORS_BMP085_ATTACHED 3 | #define SENSORS_HMC5883L_ATTACHED 4 | #define SENSORS_MPU6050_ATTACHED 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int main() { 11 | // Initialize WiringPi pins 12 | wiringPiSetup(); 13 | 14 | // Initialize devices 15 | Sensors::initialize(); 16 | 17 | while(true) { 18 | Accelerometer *accelerometer = Sensors::getAccelerometer(); 19 | if(accelerometer) { 20 | Vector3 a = accelerometer->getAcceleration(); 21 | printf("Acceleration (m/s^2) %+7.3f, %+7.3f, %+7.3f\n", a.x, a.y, a.z); 22 | } 23 | 24 | Barometer *barometer = Sensors::getBarometer(); 25 | if(barometer) { 26 | float p = barometer->getPressure(); 27 | printf("Pressure (hPa) %+7.3f\n", p); 28 | 29 | float a = barometer->getAltitude(); 30 | printf("Altitude (m) %+7.3f\n", a); 31 | } 32 | 33 | Gyroscope *gyroscope = Sensors::getGyroscope(); 34 | if(gyroscope) { 35 | Vector3 g = gyroscope->getRotation(); 36 | printf("Rotation (rad/s) %+7.3f, %+7.3f, %+7.3f\n", g.x, g.y, g.z); 37 | } 38 | 39 | Magnetometer *magnetometer = Sensors::getMagnetometer(); 40 | if(magnetometer) { 41 | Vector3 m = magnetometer->getMagneticField(); 42 | printf("Magnetic Field (uT) %+7.3f, %+7.3f, %+7.3f\n", m.x, m.y, m.z); 43 | 44 | float azimuth = magnetometer->getAzimuth(); 45 | printf("Azimuth (deg) %+7.3f\n", azimuth); 46 | } 47 | 48 | Thermometer *thermometer = Sensors::getThermometer(); 49 | if(thermometer) { 50 | float t = thermometer->getTemperature(); 51 | printf("Temperature (C) %+7.3f\n", t); 52 | } 53 | 54 | delay(50); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sensors", 3 | "keywords": "i2c, sensors, accelerometer, gyroscope, barometer, ak8963, bmp085, hmc5883l, mpu6050", 4 | "description": "Use I2C-connected sensors like accelerometers, gyroscopes, and barometers in your projects, without knowing the intimate details about the actual device connected.", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/loopj/i2c-sensor-hal.git" 9 | }, 10 | "frameworks": ["arduino", "mbed", "wiringpi"], 11 | "platforms": ["atmelavr", "espressif", "teensy", "linux_arm"] 12 | } 13 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=i2c-hal 2 | version=1.0.0 3 | author=James Smith 4 | maintainer=James Smith 5 | sentence=Use I2C-connected devices like accelerometers, gyroscopes, and barometers in your projects, without knowing the intimate details about the actual device connected. 6 | paragraph=Use I2C-connected devices like accelerometers, gyroscopes, and barometers in your projects, without knowing the intimate details about the actual device connected. 7 | category=Sensors 8 | url=https://github.com/loopj/i2c-hal 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/AK8963.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "I2CDevice.h" 4 | #include "Magnetometer.h" 5 | #include "Vector3.h" 6 | 7 | // Device info 8 | #define AK8963_ADDRESS_00 0x0C 9 | #define AK8963_ADDRESS_01 0x0D 10 | #define AK8963_ADDRESS_10 0x0E 11 | #define AK8963_ADDRESS_11 0x0F 12 | #define AK8963_DEFAULT_ADDRESS AK8963_ADDRESS_00 13 | #define AK8963_DEVICE_ID 0x48 14 | #define AK8963_TWAT_US 100 15 | 16 | // Register map 17 | enum { 18 | AK8963_RA_WIA = 0x00, // R 19 | AK8963_RA_INFO = 0x01, // R 20 | AK8963_RA_ST1 = 0x02, // R 21 | AK8963_RA_HXL = 0x03, // R 22 | AK8963_RA_HXH = 0x04, // R 23 | AK8963_RA_HYL = 0x05, // R 24 | AK8963_RA_HYH = 0x06, // R 25 | AK8963_RA_HZL = 0x07, // R 26 | AK8963_RA_HZH = 0x08, // R 27 | AK8963_RA_ST2 = 0x09, // R 28 | AK8963_RA_CNTL1 = 0x0A, // R/W 29 | AK8963_RA_CNTL2 = 0x0B, // R/W 30 | AK8963_RA_ASTC = 0x0C, // R/W 31 | AK8963_RA_TS1 = 0x0D, // R/W - test registers for shipment test, do not use 32 | AK8963_RA_TS2 = 0x0E, // R/W - test registers for shipment test, do not use 33 | AK8963_RA_I2CDIS = 0x0F, // R/W 34 | AK8963_RA_ASAX = 0x10, // R 35 | AK8963_RA_ASAY = 0x11, // R 36 | AK8963_RA_ASAZ = 0x12 // R 37 | }; 38 | 39 | // ST1 40 | #define AK8963_ST1_DRDY_BIT 0 41 | #define AK8963_ST1_DOR_BIT 1 42 | 43 | // ST2 44 | #define AK8963_ST2_HOFL_BIT 3 45 | #define AK8963_ST2_BITM_BIT 4 46 | 47 | // CNTL1 48 | #define AK8963_CNTL1_MODE_BIT 3 49 | #define AK8963_CNTL1_MODE_LEN 4 50 | #define AK8963_CNTL1_BIT_BIT 4 51 | 52 | // CNTL1 MODE 53 | #define AK8963_MODE_POWERDOWN 0x0 54 | #define AK8963_MODE_SINGLE 0x1 55 | #define AK8963_MODE_CONTINUOUS_8HZ 0x2 56 | #define AK8963_MODE_EXTERNAL 0x4 57 | #define AK8963_MODE_CONTINUOUS_100HZ 0x6 58 | #define AK8963_MODE_SELFTEST 0x8 59 | #define AK8963_MODE_FUSEROM 0xF 60 | 61 | // CNTL1 BIT (resolution) 62 | #define AK8963_BIT_14 0 63 | #define AK8963_BIT_16 1 64 | 65 | // CNTL2 66 | #define AK8963_CNTL2_SRST 0x01 67 | 68 | class AK8963 : public I2CDevice, public Magnetometer { 69 | public: 70 | static AK8963& getInstance() { 71 | static AK8963 instance; 72 | return instance; 73 | } 74 | 75 | // Initialization 76 | AK8963(uint8_t address = AK8963_DEFAULT_ADDRESS) : I2CDevice(address) { 77 | 78 | } 79 | 80 | void initialize() { 81 | // Fetch sensitivity adjustment values from the fuse-rom 82 | setMode(AK8963_MODE_FUSEROM); 83 | getSensitivityAdjustment(asa); 84 | 85 | // Enable continuous measurement at maximum resolution 86 | setMode(AK8963_MODE_CONTINUOUS_100HZ); 87 | setResolution(AK8963_BIT_16); 88 | 89 | // Calculate the scale factor from the configured resolution 90 | uint8_t resolution = getResolution(); 91 | scale.x = getScale(asa[0], resolution); 92 | scale.y = getScale(asa[1], resolution); 93 | scale.z = getScale(asa[2], resolution); 94 | } 95 | 96 | bool testConnection() { 97 | return getDeviceID() == AK8963_DEVICE_ID; 98 | } 99 | 100 | // Magnetometer 101 | Vector3 getMagneticField() { 102 | Vector3 magneticField; 103 | 104 | // Read raw measurement data 105 | int16_t rawField[3]; 106 | getRawMeasurement(rawField); 107 | 108 | // Apply sensitivity adjustments, scale to get uT 109 | magneticField.x = rawField[0] * scale.x; 110 | magneticField.y = rawField[1] * scale.y; 111 | magneticField.z = rawField[2] * scale.z; 112 | 113 | return magneticField; 114 | } 115 | 116 | // WIA register 117 | uint8_t getDeviceID() { 118 | readByte(AK8963_RA_WIA, buffer); 119 | return buffer[0]; 120 | } 121 | 122 | // ST1 register 123 | bool getDataReady() { 124 | readBit(AK8963_RA_ST1, AK8963_ST1_DRDY_BIT, buffer); 125 | return buffer[0]; 126 | } 127 | 128 | bool getDataOverrun() { 129 | readBit(AK8963_RA_ST1, AK8963_ST1_DOR_BIT, buffer); 130 | return buffer[0]; 131 | } 132 | 133 | // H registers 134 | void getRawMeasurement(int16_t *rawField) { 135 | // Read data and mark data reading as finished by also reading the ST2 register 136 | readBytes(AK8963_RA_HXL, 7, buffer); 137 | rawField[0] = (((int16_t)buffer[1]) << 8) | buffer[0]; 138 | rawField[1] = (((int16_t)buffer[3]) << 8) | buffer[2]; 139 | rawField[2] = (((int16_t)buffer[5]) << 8) | buffer[4]; 140 | } 141 | 142 | // ST2 register 143 | bool getOverflowStatus() { 144 | readBit(AK8963_RA_ST2, AK8963_ST2_HOFL_BIT, buffer); 145 | return buffer[0]; 146 | } 147 | 148 | bool getOutputBit() { 149 | readBit(AK8963_RA_ST2, AK8963_ST2_BITM_BIT, buffer); 150 | return buffer[0]; 151 | } 152 | 153 | // CNTL1 register 154 | uint8_t getMode() { 155 | readBits(AK8963_RA_CNTL1, AK8963_CNTL1_MODE_BIT, AK8963_CNTL1_MODE_LEN, buffer); 156 | return buffer[0]; 157 | } 158 | 159 | void setMode(uint8_t mode) { 160 | // When user wants to change operation mode, transit to power-down mode 161 | // first and then transit to other modes. After power-down mode is set, at 162 | // least 100us(Twat) is needed before setting another mode. 163 | writeBits(AK8963_RA_CNTL1, AK8963_CNTL1_MODE_BIT, AK8963_CNTL1_MODE_LEN, AK8963_MODE_POWERDOWN); 164 | usleep(AK8963_TWAT_US); 165 | writeBits(AK8963_RA_CNTL1, AK8963_CNTL1_MODE_BIT, AK8963_CNTL1_MODE_LEN, mode); 166 | } 167 | 168 | uint8_t getResolution() { 169 | readBit(AK8963_RA_CNTL1, AK8963_CNTL1_BIT_BIT, buffer); 170 | return buffer[0]; 171 | } 172 | 173 | void setResolution(uint8_t resolution) { 174 | writeBit(AK8963_RA_CNTL1, AK8963_CNTL1_BIT_BIT, resolution); 175 | scale.x = getScale(asa[0], resolution); 176 | scale.y = getScale(asa[1], resolution); 177 | scale.z = getScale(asa[2], resolution); 178 | } 179 | 180 | // CNTL2 register 181 | void reset() { 182 | writeByte(AK8963_RA_CNTL2, AK8963_CNTL2_SRST); 183 | } 184 | 185 | // ASA registers 186 | void getSensitivityAdjustment(uint8_t *asa) { 187 | readBytes(AK8963_RA_ASAX, 3, asa); 188 | } 189 | 190 | protected: 191 | uint8_t asa[3]; 192 | Vector3 scale; 193 | 194 | float getScale(uint8_t asa, uint8_t resolution) { 195 | // Get the scale factor from raw to uT, depending on resolution 196 | float resScale; 197 | if(resolution == AK8963_BIT_16) { 198 | resScale = 4912.0 / 32760.0; 199 | } else { 200 | resScale = 4912.0 / 8190.0; 201 | } 202 | 203 | // Apply sensitivity adjustments according to datasheet 204 | // Hadj = H * (ASA + 128) / 256 205 | return ((asa + 128.0) / 256.0) * resScale; 206 | } 207 | }; 208 | -------------------------------------------------------------------------------- /src/Accelerometer.cpp: -------------------------------------------------------------------------------- 1 | #include "Accelerometer.h" 2 | 3 | const float Accelerometer::STANDARD_GRAVITY = 9.80665f; 4 | -------------------------------------------------------------------------------- /src/Accelerometer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vector3.h" 4 | 5 | /** 6 | * An accelerometer measures proper acceleration (including gravity) in m/s², 7 | * on three axes. 8 | */ 9 | 10 | class Accelerometer { 11 | public: 12 | // Earth's gravity in m/s^2 13 | static const float STANDARD_GRAVITY; 14 | 15 | // Get the current acceleration vector, in m/s^2 16 | virtual Vector3 getAcceleration() = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /src/BMP085.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "I2CDevice.h" 4 | #include "Barometer.h" 5 | #include "Thermometer.h" 6 | 7 | // Device info 8 | #define BMP085_ADDRESS 0x77 9 | 10 | // Device IDs for CHIPID register 11 | #define BMP085_DEVICE_ID 0x55 12 | #define BMP180_DEVICE_ID 0x55 13 | 14 | // Register map 15 | enum { 16 | BMP085_RA_CAL_AC1 = 0xAA, // R 17 | BMP085_RA_CAL_AC2 = 0xAC, // R 18 | BMP085_RA_CAL_AC3 = 0xAE, // R 19 | BMP085_RA_CAL_AC4 = 0xB0, // R 20 | BMP085_RA_CAL_AC5 = 0xB2, // R 21 | BMP085_RA_CAL_AC6 = 0xB4, // R 22 | BMP085_RA_CAL_B1 = 0xB6, // R 23 | BMP085_RA_CAL_B2 = 0xB8, // R 24 | BMP085_RA_CAL_MB = 0xBA, // R 25 | BMP085_RA_CAL_MC = 0xBC, // R 26 | BMP085_RA_CAL_MD = 0xBE, // R 27 | BMP085_RA_CHIPID = 0xD0, // R 28 | BMP085_RA_VERSION = 0xD1, // R 29 | BMP085_RA_SOFTRESET = 0xE0, // R/W 30 | BMP085_RA_CONTROL = 0xF4, // R/W 31 | BMP085_RA_DATA = 0xF6 // R 32 | }; 33 | 34 | // CONTROL 35 | #define BMP085_CONTROL_TEMPERATURE 0x2E 36 | #define BMP085_CONTROL_PRESSURE 0x34 37 | 38 | // CONTROL oversampling modes 39 | #define BMP085_MODE_ULTRALOWPOWER 0 40 | #define BMP085_MODE_STANDARD 1 41 | #define BMP085_MODE_HIGHRES 2 42 | #define BMP085_MODE_ULTRAHIGHRES 3 43 | 44 | class BMP085 : public I2CDevice, public Barometer, public Thermometer { 45 | public: 46 | static BMP085& getInstance() { 47 | static BMP085 instance; 48 | return instance; 49 | } 50 | 51 | // Initialization 52 | BMP085() : I2CDevice(BMP085_ADDRESS), oss(0) { 53 | 54 | } 55 | 56 | void initialize() { 57 | loadCalibration(); 58 | setOversamplingMode(BMP085_MODE_STANDARD); 59 | } 60 | 61 | bool testConnection() { 62 | return getDeviceID() == BMP085_DEVICE_ID; 63 | } 64 | 65 | // Barometer 66 | float getPressure() { 67 | int32_t ut = getRawTemperature(); 68 | int32_t up = getRawPressure(); 69 | 70 | // Calculate true pressure, according to datasheet rules 71 | int32_t b5 = computeB5(ut); 72 | int32_t b6 = b5 - 4000; 73 | int32_t x1 = (b2 * ((b6 * b6) >> 12)) >> 11; 74 | int32_t x2 = (ac2 * b6) >> 11; 75 | int32_t x3 = x1 + x2; 76 | int32_t b3 = (((((int32_t) ac1) * 4 + x3) << oss) + 2) >> 2; 77 | x1 = (ac3 * b6) >> 13; 78 | x2 = (b1 * ((b6 * b6) >> 12)) >> 16; 79 | x3 = ((x1 + x2) + 2) >> 2; 80 | uint32_t b4 = (ac4 * (uint32_t) (x3 + 32768)) >> 15; 81 | uint32_t b7 = ((uint32_t) (up - b3) * (50000 >> oss)); 82 | 83 | int32_t p; 84 | if (b7 < 0x80000000) { 85 | p = (b7 << 1) / b4; 86 | } else { 87 | p = (b7 / b4) << 1; 88 | } 89 | 90 | x1 = (p >> 8) * (p >> 8); 91 | x1 = (x1 * 3038) >> 16; 92 | x2 = (-7357 * p) >> 16; 93 | 94 | p = p + ((x1 + x2 + 3791) >> 4); 95 | 96 | // Convert to hPa 97 | return p / 100.0; 98 | } 99 | 100 | // Thermometer 101 | float getTemperature() { 102 | int16_t ut = getRawTemperature(); 103 | 104 | // Calculate true temperature, according to datasheet rules 105 | int32_t b5 = computeB5(ut); 106 | float t = (b5+8) >> 4; 107 | 108 | // Convert to C 109 | return t / 10.0; 110 | } 111 | 112 | // CAL registers 113 | void loadCalibration() { 114 | readWord(BMP085_RA_CAL_AC1, (uint16_t *)(&ac1)); 115 | readWord(BMP085_RA_CAL_AC2, (uint16_t *)(&ac2)); 116 | readWord(BMP085_RA_CAL_AC3, (uint16_t *)(&ac3)); 117 | readWord(BMP085_RA_CAL_AC4, &ac4); 118 | readWord(BMP085_RA_CAL_AC5, &ac5); 119 | readWord(BMP085_RA_CAL_AC6, &ac6); 120 | readWord(BMP085_RA_CAL_B1, (uint16_t *)(&b1)); 121 | readWord(BMP085_RA_CAL_B2, (uint16_t *)(&b2)); 122 | readWord(BMP085_RA_CAL_MB, (uint16_t *)(&mb)); 123 | readWord(BMP085_RA_CAL_MC, (uint16_t *)(&mc)); 124 | readWord(BMP085_RA_CAL_MD, (uint16_t *)(&md)); 125 | } 126 | 127 | // CHIPID register 128 | uint8_t getDeviceID() { 129 | readByte(BMP085_RA_CHIPID, buffer); 130 | return buffer[0]; 131 | } 132 | 133 | // CONTROL register 134 | uint8_t getControl() { 135 | readByte(BMP085_RA_CONTROL, buffer); 136 | return buffer[0]; 137 | } 138 | 139 | void setControl(uint8_t value) { 140 | writeByte(BMP085_RA_CONTROL, value); 141 | } 142 | 143 | void setOversamplingMode(uint8_t mode) { 144 | oss = mode; 145 | } 146 | 147 | // DATA register 148 | int32_t getRawTemperature() { 149 | int16_t rawTemperature; 150 | 151 | // Start temperature measurement 152 | setControl(BMP085_CONTROL_TEMPERATURE); 153 | 154 | // Wait for the conversion to complete 155 | usleep(4500); 156 | 157 | // Read the raw temperature data; 158 | readWord(BMP085_RA_DATA, (uint16_t *)(&rawTemperature)); 159 | 160 | return rawTemperature; 161 | } 162 | 163 | int32_t getRawPressure() { 164 | int32_t rawPressure; 165 | 166 | // Start pressure measurement, with the configured oversampling setting 167 | setControl(BMP085_CONTROL_PRESSURE + (oss << 6)); 168 | 169 | // Wait for the conversion to complete 170 | switch(oss) { 171 | case BMP085_MODE_ULTRALOWPOWER: 172 | usleep(4500); 173 | break; 174 | case BMP085_MODE_STANDARD: 175 | usleep(7500); 176 | break; 177 | case BMP085_MODE_HIGHRES: 178 | usleep(13500); 179 | break; 180 | case BMP085_MODE_ULTRAHIGHRES: 181 | usleep(25500); 182 | break; 183 | } 184 | 185 | // Read the raw pressure data 186 | readBytes(BMP085_RA_DATA, 3, buffer); 187 | rawPressure = (((uint32_t)buffer[0] << 16) + ((uint16_t)buffer[1] << 8) + buffer[2]) >> (8 - oss); 188 | 189 | return rawPressure; 190 | } 191 | 192 | protected: 193 | int16_t ac1, ac2, ac3; 194 | uint16_t ac4, ac5, ac6; 195 | int16_t b1, b2; 196 | int16_t mb, mc, md; 197 | 198 | uint8_t oss; 199 | 200 | int32_t computeB5(int32_t ut) { 201 | int32_t x1 = (ut - (int32_t)ac6) * ((int32_t)ac5) >> 15; 202 | int32_t x2 = ((int32_t)mc << 11) / (x1+(int32_t)md); 203 | return x1 + x2; 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /src/Barometer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Barometer.h" 4 | 5 | const float Barometer::PRESSURE_STANDARD_ATMOSPHERE = 1013.25f; 6 | 7 | float Barometer::getAltitude(float baselinePressure) { 8 | float pressure = getPressure(); 9 | float altitude = 44330 * (1.0 - pow(pressure / baselinePressure, 1 / 5.255)); 10 | return altitude; 11 | } 12 | 13 | float Barometer::getSealevelPressure(float altitude) { 14 | float pressure = getPressure(); 15 | return pressure / pow(1.0 - altitude / 44330, 5.255); 16 | } 17 | -------------------------------------------------------------------------------- /src/Barometer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vector3.h" 4 | 5 | /** 6 | * A barometer measures atmospheric pressure in hPa or mbar. 7 | * 8 | * Using atmospheric pressure from a barometer, you can also compute the 9 | * altitude in meters. 10 | */ 11 | 12 | class Barometer { 13 | public: 14 | // Standard atmosphere, or average sea-level pressure in hPa (millibars) 15 | static const float PRESSURE_STANDARD_ATMOSPHERE; 16 | 17 | // Get the current air pressure in hPa 18 | virtual float getPressure() = 0; 19 | 20 | // Get the current altitude in m, given a baseline pressure in hPa 21 | float getAltitude(float baselinePressure = PRESSURE_STANDARD_ATMOSPHERE); 22 | 23 | // Get the pressure at sea-level in hPa, given the current altitude in m 24 | float getSealevelPressure(float altitude); 25 | }; 26 | -------------------------------------------------------------------------------- /src/Gyroscope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vector3.h" 4 | 5 | /** 6 | * A gyroscope measures the rotational speed in rad/s, on three axes. 7 | */ 8 | 9 | class Gyroscope { 10 | public: 11 | // Get the current rotational speed vector, in rad/s 12 | virtual Vector3 getRotation() = 0; 13 | }; 14 | -------------------------------------------------------------------------------- /src/HMC5883L.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "I2CDevice.h" 6 | #include "Magnetometer.h" 7 | #include "Vector3.h" 8 | 9 | // Device info 10 | #define HMC5883L_ADDRESS 0x1E 11 | #define HMC5883L_ID "H43" 12 | 13 | // Register map 14 | enum { 15 | HMC5883L_RA_CONFIG_A = 0x00, // R/W 16 | HMC5883L_RA_CONFIG_B = 0x01, // R/W 17 | HMC5883L_RA_MODE = 0x02, // R/W 18 | HMC5883L_RA_DATAX_H = 0x03, // R 19 | HMC5883L_RA_DATAX_L = 0x04, // R 20 | HMC5883L_RA_DATAZ_H = 0x05, // R 21 | HMC5883L_RA_DATAZ_L = 0x06, // R 22 | HMC5883L_RA_DATAY_H = 0x07, // R 23 | HMC5883L_RA_DATAY_L = 0x08, // R 24 | HMC5883L_RA_STATUS = 0x09, // R 25 | HMC5883L_RA_ID_A = 0x0A, // R 26 | HMC5883L_RA_ID_B = 0x0B, // R 27 | HMC5883L_RA_ID_C = 0x0C // R 28 | }; 29 | 30 | // CONFIG_A 31 | #define HMC5883L_CONFIG_A_MA_BIT 6 32 | #define HMC5883L_CONFIG_A_MA_LEN 2 33 | #define HMC5883L_CONFIG_A_DO_BIT 4 34 | #define HMC5883L_CONFIG_A_DO_LEN 3 35 | #define HMC5883L_CONFIG_A_MS_BIT 1 36 | #define HMC5883L_CONFIG_A_MS_LEN 2 37 | 38 | // CONFIG_A MA 39 | #define HMC5883L_AVERAGE_1 0x00 40 | #define HMC5883L_AVERAGE_2 0x01 41 | #define HMC5883L_AVERAGE_4 0x02 42 | #define HMC5883L_AVERAGE_8 0x03 43 | 44 | // CONFIG_A DO 45 | #define HMC5883L_RATE_0P75 0x00 46 | #define HMC5883L_RATE_1P5 0x01 47 | #define HMC5883L_RATE_3 0x02 48 | #define HMC5883L_RATE_7P5 0x03 49 | #define HMC5883L_RATE_15 0x04 50 | #define HMC5883L_RATE_30 0x05 51 | #define HMC5883L_RATE_75 0x06 52 | 53 | // CONFIG_A MS 54 | #define HMC5883L_BIAS_NORMAL 0x00 55 | #define HMC5883L_BIAS_POSITIVE 0x01 56 | #define HMC5883L_BIAS_NEGATIVE 0x02 57 | 58 | // CONFIG_B 59 | #define HMC5883L_CONFIG_B_GN_BIT 7 60 | #define HMC5883L_CONFIG_B_GN_LEN 3 61 | 62 | // CONFIG_B GN 63 | #define HMC5883L_GAIN_1370 0x00 64 | #define HMC5883L_GAIN_1090 0x01 65 | #define HMC5883L_GAIN_820 0x02 66 | #define HMC5883L_GAIN_660 0x03 67 | #define HMC5883L_GAIN_440 0x04 68 | #define HMC5883L_GAIN_390 0x05 69 | #define HMC5883L_GAIN_330 0x06 70 | #define HMC5883L_GAIN_230 0x07 71 | 72 | // MODE 73 | #define HMC5883L_MODE_HS_BIT 7 74 | #define HMC5883L_MODE_MD_BIT 1 75 | #define HMC5883L_MODE_MD_LEN 2 76 | 77 | // MODE MD 78 | #define HMC5883L_MODE_CONTINUOUS 0x00 79 | #define HMC5883L_MODE_SINGLE 0x01 80 | #define HMC5883L_MODE_IDLE 0x02 81 | 82 | // STATUS 83 | #define HMC5883L_STATUS_LOCK_BIT 1 84 | #define HMC5883L_STATUS_RDY_BIT 0 85 | 86 | class HMC5883L : public I2CDevice, public Magnetometer { 87 | public: 88 | static HMC5883L& getInstance() { 89 | static HMC5883L instance; 90 | return instance; 91 | } 92 | 93 | // Initialization 94 | HMC5883L() : I2CDevice(HMC5883L_ADDRESS) { 95 | 96 | } 97 | 98 | void initialize() { 99 | setMode(HMC5883L_MODE_CONTINUOUS); 100 | setDataRate(HMC5883L_RATE_75); 101 | 102 | // Calculate the scale factor from the configured gain 103 | uint8_t gain = getGain(); 104 | scale = getScale(gain); 105 | } 106 | 107 | bool testConnection() { 108 | readBytes(HMC5883L_RA_ID_A, 3, buffer); 109 | return (strncmp((char *)buffer, HMC5883L_ID, 3) == 0); 110 | } 111 | 112 | // Magnetometer 113 | Vector3 getMagneticField() { 114 | Vector3 magneticField; 115 | 116 | // Read raw measurement data 117 | int16_t rawField[3]; 118 | getRawMeasurement(rawField); 119 | 120 | // Apply magnetometer scale to get Gauss, convert to microtesla 121 | magneticField.x = rawField[0]/scale * GAUSS_TO_MICROTESLA; 122 | magneticField.y = rawField[1]/scale * GAUSS_TO_MICROTESLA; 123 | magneticField.z = rawField[2]/scale * GAUSS_TO_MICROTESLA; 124 | 125 | return magneticField; 126 | } 127 | 128 | // CONFIG_A register 129 | uint8_t getSampleAveraging() { 130 | readBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_MA_BIT, HMC5883L_CONFIG_A_MA_LEN, buffer); 131 | return buffer[0]; 132 | } 133 | 134 | void setSampleAveraging(uint8_t averaging) { 135 | writeBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_MA_BIT, HMC5883L_CONFIG_A_MA_LEN, averaging); 136 | } 137 | 138 | uint8_t getDataRate() { 139 | readBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_DO_BIT, HMC5883L_CONFIG_A_DO_LEN, buffer); 140 | return buffer[0]; 141 | } 142 | 143 | void setDataRate(uint8_t rate) { 144 | writeBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_DO_BIT, HMC5883L_CONFIG_A_DO_LEN, rate); 145 | } 146 | 147 | uint8_t getMeasurementBias() { 148 | readBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_MS_BIT, HMC5883L_CONFIG_A_MS_LEN, buffer); 149 | return buffer[0]; 150 | } 151 | 152 | void setMeasurementBias(uint8_t bias) { 153 | writeBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_A_MS_BIT, HMC5883L_CONFIG_A_MS_LEN, bias); 154 | } 155 | 156 | // CONFIG_B register 157 | uint8_t getGain() { 158 | readBits(HMC5883L_RA_CONFIG_B, HMC5883L_CONFIG_B_GN_BIT, HMC5883L_CONFIG_B_GN_LEN, buffer); 159 | return buffer[0]; 160 | } 161 | 162 | void setGain(uint8_t gain) { 163 | writeBits(HMC5883L_RA_CONFIG_A, HMC5883L_CONFIG_B_GN_BIT, HMC5883L_CONFIG_B_GN_LEN, gain); 164 | scale = getScale(gain); 165 | } 166 | 167 | // MODE register 168 | uint8_t getMode() { 169 | readBits(HMC5883L_RA_MODE, HMC5883L_MODE_MD_BIT, HMC5883L_MODE_MD_LEN, buffer); 170 | return buffer[0]; 171 | } 172 | 173 | void setMode(uint8_t mode) { 174 | writeBits(HMC5883L_RA_MODE, HMC5883L_MODE_MD_BIT, HMC5883L_MODE_MD_LEN, mode); 175 | } 176 | 177 | // DATA registers 178 | void getRawMeasurement(int16_t *rawField) { 179 | readBytes(HMC5883L_RA_DATAX_H, 6, buffer); 180 | rawField[0] = (((int16_t)buffer[0]) << 8) | buffer[1]; 181 | rawField[1] = (((int16_t)buffer[4]) << 8) | buffer[5]; 182 | rawField[2] = (((int16_t)buffer[2]) << 8) | buffer[3]; 183 | } 184 | 185 | // STATUS registers 186 | bool getLockStatus() { 187 | readBit(HMC5883L_RA_STATUS, HMC5883L_STATUS_LOCK_BIT, buffer); 188 | return buffer[0]; 189 | } 190 | 191 | bool getReadyStatus() { 192 | readBit(HMC5883L_RA_STATUS, HMC5883L_STATUS_RDY_BIT, buffer); 193 | return buffer[0]; 194 | } 195 | 196 | protected: 197 | float scale; 198 | 199 | float getScale(uint8_t gain) { 200 | switch(gain) { 201 | case HMC5883L_GAIN_1370: 202 | return 1370.0; 203 | case HMC5883L_GAIN_1090: 204 | return 1090.0; 205 | case HMC5883L_GAIN_820: 206 | return 820.0; 207 | case HMC5883L_GAIN_660: 208 | return 660.0; 209 | case HMC5883L_GAIN_440: 210 | return 440.0; 211 | case HMC5883L_GAIN_390: 212 | return 390.0; 213 | case HMC5883L_GAIN_330: 214 | return 330.0; 215 | case HMC5883L_GAIN_230: 216 | return 230.0; 217 | default: 218 | return 1090.0; 219 | } 220 | } 221 | }; 222 | -------------------------------------------------------------------------------- /src/I2CDevice.cpp: -------------------------------------------------------------------------------- 1 | // Framework-independent I2C methods 2 | 3 | #include "I2CDevice.h" 4 | 5 | bool I2CDevice::readBit(uint8_t regAddr, uint8_t bitNum, uint8_t *data) { 6 | uint8_t b; 7 | bool status = readByte(regAddr, &b); 8 | *data = b & (1 << bitNum); 9 | 10 | return status; 11 | } 12 | 13 | bool I2CDevice::readBits(uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data) { 14 | bool status = false; 15 | uint8_t b; 16 | if ((status = readByte(regAddr, &b))) { 17 | uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1); 18 | b &= mask; 19 | b >>= (bitStart - length + 1); 20 | *data = b; 21 | } 22 | return status; 23 | } 24 | 25 | bool I2CDevice::readWordBit(uint8_t regAddr, uint8_t bitNum, uint8_t *data) { 26 | uint16_t b; 27 | bool status = readWord(regAddr, &b); 28 | *data = (uint8_t) (b & (1 << bitNum)); 29 | 30 | return status; 31 | } 32 | 33 | bool I2CDevice::readWordBits(uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data) { 34 | bool status = false; 35 | uint16_t b; 36 | if ((status = readWord(regAddr, &b))) { 37 | uint16_t mask = ((1 << length) - 1) << (bitStart - length + 1); 38 | b &= mask; 39 | b >>= (bitStart - length + 1); 40 | *data = b; 41 | } 42 | return status; 43 | } 44 | 45 | bool I2CDevice::readByte(uint8_t regAddr, uint8_t *data) { 46 | return readBytes(regAddr, 1, data); 47 | } 48 | 49 | bool I2CDevice::readWord(uint8_t regAddr, uint16_t *data) { 50 | return readWords(regAddr, 1, data); 51 | } 52 | 53 | bool I2CDevice::readWords(uint8_t regAddr, uint8_t length, uint16_t *data) { 54 | uint8_t temp[length*2]; 55 | bool status = readBytes(regAddr, length*2, temp); 56 | 57 | for(int i=0; i 4 | 5 | class I2CDevice { 6 | public: 7 | // Constructor 8 | I2CDevice(uint8_t address); 9 | 10 | // Perform any required device initialization 11 | virtual void initialize() = 0; 12 | 13 | // Confirm that this device is actually connected to the I2C bus 14 | virtual bool testConnection() = 0; 15 | 16 | protected: 17 | // Read data from the specified I2C register on this device 18 | bool readBit(uint8_t regAddr, uint8_t bitNum, uint8_t *data); 19 | bool readBits(uint8_t regAddr, uint8_t bitNum, uint8_t length, uint8_t *data); 20 | bool readWordBit(uint8_t regAddr, uint8_t bitNum, uint8_t *data); 21 | bool readWordBits(uint8_t regAddr, uint8_t bitNum, uint8_t length, uint16_t *data); 22 | bool readByte(uint8_t regAddr, uint8_t *data); 23 | bool readBytes(uint8_t regAddr, uint8_t length, uint8_t *data); 24 | bool readWord(uint8_t regAddr, uint16_t *data); 25 | bool readWords(uint8_t regAddr, uint8_t length, uint16_t *data); 26 | 27 | // Write data to the specified I2C register on this device 28 | bool writeBit(uint8_t regAddr, uint8_t bitNum, uint8_t data); 29 | bool writeBits(uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data); 30 | bool writeWordBit(uint8_t regAddr, uint8_t bitNum, uint8_t data); 31 | bool writeWordBits(uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data); 32 | bool writeByte(uint8_t regAddr, uint8_t data); 33 | bool writeBytes(uint8_t regAddr, uint8_t length, uint8_t *data); 34 | bool writeWord(uint16_t regAddr, uint16_t data); 35 | 36 | // Platform-independent sleep/delay 37 | void usleep(unsigned int us); 38 | 39 | // The I2C address for this device 40 | uint8_t address; 41 | 42 | // Convenient buffer for read operations 43 | uint8_t buffer[64]; 44 | 45 | // I2C device handle (internal) 46 | int handle; 47 | }; 48 | -------------------------------------------------------------------------------- /src/I2CDevice_arduino.cpp: -------------------------------------------------------------------------------- 1 | // Framework-specific I2C methods, for the Arduino and Particle platforms 2 | 3 | #if defined(ARDUINO) || defined(SPARK) 4 | 5 | #ifdef ARDUINO 6 | #include "Arduino.h" 7 | #include 8 | #endif 9 | 10 | #ifdef SPARK 11 | #include "application.h" 12 | #endif 13 | 14 | #include "I2CDevice.h" 15 | 16 | I2CDevice::I2CDevice(uint8_t address) : address(address) {} 17 | 18 | bool I2CDevice::readBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 19 | uint8_t count = 0; 20 | 21 | // Select the slave address and register to read from 22 | Wire.beginTransmission(address); 23 | Wire.write(regAddr); 24 | Wire.endTransmission(); 25 | 26 | // Request length bytes of data 27 | Wire.requestFrom(address, length); 28 | while(Wire.available()) { 29 | data[count] = Wire.read(); 30 | count++; 31 | } 32 | 33 | return (count == length); 34 | } 35 | 36 | bool I2CDevice::writeBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 37 | // Select the slave address and register to write to 38 | Wire.beginTransmission(address); 39 | Wire.write(regAddr); 40 | 41 | // Write the data 42 | Wire.write(data, length); 43 | 44 | // Finish transmission and return the status 45 | return (Wire.endTransmission() == 0); 46 | } 47 | 48 | void I2CDevice::usleep(unsigned int us) { 49 | delayMicroseconds(us); 50 | } 51 | 52 | #endif // defined(ARDUINO) || defined(SPARK) 53 | -------------------------------------------------------------------------------- /src/I2CDevice_mbed.cpp: -------------------------------------------------------------------------------- 1 | // Framework-specific I2C methods, for ARM mbed platform 2 | 3 | #ifdef __MBED__ 4 | 5 | #include "mbed.h" 6 | 7 | #include "I2CDevice.h" 8 | 9 | // I2C device handle (internal) 10 | I2C i2c(I2C_SDA, I2C_SCL); 11 | 12 | I2CDevice::I2CDevice(uint8_t address) : address(address) { 13 | // Use high-speed i2c 14 | i2c.frequency(400000); 15 | } 16 | 17 | bool I2CDevice::readBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 18 | // Start the i2c transaction 19 | i2c.start(); 20 | 21 | // Select the slave address and register to read from 22 | i2c.write(address << 1); 23 | i2c.write(regAddr); 24 | 25 | // // Read the data 26 | i2c.read(address << 1, (char *)data, length); 27 | 28 | return true; 29 | } 30 | 31 | bool I2CDevice::writeBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 32 | // Start the i2c transaction 33 | i2c.start(); 34 | 35 | // Select the slave address and register to write to 36 | i2c.write(address << 1); 37 | i2c.write(regAddr); 38 | 39 | // Write the data 40 | for(int i = 0; i < length; i++) { 41 | i2c.write(data[i]); 42 | } 43 | 44 | // Finish the i2c transaction 45 | i2c.stop(); 46 | 47 | return true; 48 | } 49 | 50 | void I2CDevice::usleep(unsigned int us) { 51 | wait_us(us); 52 | } 53 | 54 | #endif // defined(__MBED__) 55 | -------------------------------------------------------------------------------- /src/I2CDevice_wiringpi.cpp: -------------------------------------------------------------------------------- 1 | // Framework-specific I2C methods, for the WiringPi platform 2 | 3 | #ifdef RASPBERRYPI 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "I2CDevice.h" 12 | 13 | I2CDevice::I2CDevice(uint8_t address) : address(address) { 14 | handle = wiringPiI2CSetup(address); 15 | } 16 | 17 | bool I2CDevice::readBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 18 | uint8_t count = i2c_smbus_read_i2c_block_data(handle, regAddr, length, data); 19 | 20 | return (count == length); 21 | } 22 | 23 | bool I2CDevice::writeBytes(uint8_t regAddr, uint8_t length, uint8_t *data) { 24 | uint8_t count = i2c_smbus_write_i2c_block_data(handle, regAddr, length, data); 25 | 26 | return (count == length); 27 | } 28 | 29 | void I2CDevice::usleep(unsigned int us) { 30 | delayMicroseconds(us); 31 | } 32 | 33 | #endif // defined(RASPBERRYPI) 34 | -------------------------------------------------------------------------------- /src/MPU6050.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Accelerometer.h" 7 | #include "Gyroscope.h" 8 | #include "I2CDevice.h" 9 | #include "Vector3.h" 10 | 11 | // Device info 12 | #define MPU6050_ADDRESS_AD0_LOW 0x68 13 | #define MPU6050_ADDRESS_AD0_HIGH 0x69 14 | #define MPU6050_DEFAULT_ADDRESS MPU6050_ADDRESS_AD0_LOW 15 | 16 | // Device IDs for WHO_AM_I register 17 | #define MPU6050_DEVICE_ID 0x34 18 | #define MPU6500_DEVICE_ID 0x70 19 | #define MPU9150_DEVICE_ID 0x68 20 | #define MPU9250_DEVICE_ID 0x71 21 | 22 | // Register map 23 | enum { 24 | MPU6050_RA_GYRO_CONFIG = 0x1B, 25 | MPU6050_RA_ACCEL_CONFIG = 0x1C, 26 | MPU6050_RA_INT_PIN_CFG = 0x37, 27 | MPU6050_RA_ACCEL_XOUT_H = 0x3B, 28 | MPU6050_RA_TEMP_OUT_H = 0x41, 29 | MPU6050_RA_GYRO_XOUT_H = 0x43, 30 | MPU6050_RA_USER_CTRL = 0x6A, 31 | MPU6050_RA_PWR_MGMT_1 = 0x6B, 32 | MPU6050_RA_WHO_AM_I = 0x75 33 | }; 34 | 35 | // GYRO_CONFIG 36 | #define MPU6050_GYRO_FS_SEL_BIT 4 37 | #define MPU6050_GYRO_FS_SEL_LEN 2 38 | 39 | // GYRO_CONFIG FS_SEL 40 | #define MPU6050_GYRO_FS_250 0x00 41 | #define MPU6050_GYRO_FS_500 0x01 42 | #define MPU6050_GYRO_FS_1000 0x02 43 | #define MPU6050_GYRO_FS_2000 0x03 44 | 45 | // ACCEL_CONFIG 46 | #define MPU6050_ACCEL_FS_SEL_BIT 4 47 | #define MPU6050_ACCEL_FS_SEL_LEN 2 48 | 49 | // ACCEL_CONFIG AFS_SEL 50 | #define MPU6050_ACCEL_FS_2 0x00 51 | #define MPU6050_ACCEL_FS_4 0x01 52 | #define MPU6050_ACCEL_FS_8 0x02 53 | #define MPU6050_ACCEL_FS_16 0x03 54 | 55 | // INT_PIN_CFG 56 | #define MPU6050_BYPASS_EN_BIT 1 57 | 58 | // USER_CTRL 59 | #define MPU6050_DMP_EN_BIT 7 60 | #define MPU6050_FIFO_EN_BIT 6 61 | 62 | // PWR_MGMT_1 63 | #define MPU6050_DEVICE_RESET_BIT 7 64 | #define MPU6050_SLEEP_BIT 6 65 | #define MPU6050_CLKSEL_BIT 2 66 | #define MPU6050_CLKSEL_LEN 3 67 | #define MPU6050_CLOCK_INTERNAL 0x00 68 | #define MPU6050_CLOCK_PLL 0x01 69 | #define MPU6050_CLOCK_KEEP_RESET 0x07 70 | 71 | class MPU6050 : public I2CDevice, public Accelerometer, public Gyroscope { 72 | public: 73 | static MPU6050& getInstance() { 74 | static MPU6050 instance; 75 | return instance; 76 | } 77 | 78 | // Initialization 79 | MPU6050(uint8_t address = MPU6050_DEFAULT_ADDRESS) : I2CDevice(address) { 80 | 81 | } 82 | 83 | void initialize() { 84 | // Wake up the device 85 | setSleepEnabled(false); 86 | 87 | // Use the most accurate clock source 88 | setClockSource(MPU6050_CLOCK_PLL); 89 | 90 | // Set the sensitivity to max on gyro and accel 91 | setFullScaleGyroRange(MPU6050_GYRO_FS_250); 92 | setFullScaleAccelRange(MPU6050_ACCEL_FS_2); 93 | 94 | // Allow direct I2C access to devices connected to the MPU6050 aux bus 95 | setI2cBypassEnabled(true); 96 | 97 | // Calculate the scale factors from the configured ranges 98 | accelScale = getAccelScale(getFullScaleAccelRange()); 99 | gyroScale = getGyroScale(getFullScaleGyroRange()); 100 | } 101 | 102 | bool testConnection() { 103 | switch(getDeviceID()) { 104 | case MPU6050_DEVICE_ID: 105 | return true; 106 | case MPU6500_DEVICE_ID: 107 | return true; 108 | case MPU9150_DEVICE_ID: 109 | return true; 110 | case MPU9250_DEVICE_ID: 111 | return true; 112 | } 113 | 114 | return false; 115 | } 116 | 117 | // Accelerometer 118 | Vector3 getAcceleration() { 119 | Vector3 acceleration; 120 | 121 | // Convert raw data into signed 16-bit data 122 | int16_t rawAccel[3]; 123 | readWords(MPU6050_RA_ACCEL_XOUT_H, 3, (uint16_t *)rawAccel); 124 | 125 | // Apply accelerometer scale to get Gs, convert to m/s^2 126 | acceleration.x = (float)rawAccel[0]/accelScale * STANDARD_GRAVITY; 127 | acceleration.y = (float)rawAccel[1]/accelScale * STANDARD_GRAVITY; 128 | acceleration.z = (float)rawAccel[2]/accelScale * STANDARD_GRAVITY; 129 | 130 | return acceleration; 131 | } 132 | 133 | // Gyroscope 134 | Vector3 getRotation() { 135 | Vector3 rotation; 136 | 137 | // Convert raw data into signed 16-bit data 138 | int16_t rawRotation[3]; 139 | readWords(MPU6050_RA_GYRO_XOUT_H, 3, (uint16_t *)rawRotation); 140 | 141 | // Apply gyroscope scale to get deg/s, convert to rad/s 142 | rotation.x = (float)rawRotation[0]/gyroScale * M_PI/180.0; 143 | rotation.y = (float)rawRotation[1]/gyroScale * M_PI/180.0; 144 | rotation.z = (float)rawRotation[2]/gyroScale * M_PI/180.0; 145 | 146 | return rotation; 147 | } 148 | 149 | // GYRO_CONFIG register 150 | uint8_t getFullScaleGyroRange() { 151 | readBits(MPU6050_RA_GYRO_CONFIG, MPU6050_GYRO_FS_SEL_BIT, MPU6050_GYRO_FS_SEL_LEN, buffer); 152 | 153 | return buffer[0]; 154 | } 155 | 156 | void setFullScaleGyroRange(uint8_t range) { 157 | writeBits(MPU6050_RA_GYRO_CONFIG, MPU6050_GYRO_FS_SEL_BIT, MPU6050_GYRO_FS_SEL_LEN, range); 158 | gyroScale = getGyroScale(range); 159 | } 160 | 161 | // ACCEL_CONFIG register 162 | uint8_t getFullScaleAccelRange() { 163 | readBits(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACCEL_FS_SEL_BIT, MPU6050_ACCEL_FS_SEL_LEN, buffer); 164 | 165 | return buffer[0]; 166 | } 167 | 168 | void setFullScaleAccelRange(uint8_t range) { 169 | writeBits(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACCEL_FS_SEL_BIT, MPU6050_ACCEL_FS_SEL_LEN, range); 170 | accelScale = getAccelScale(range); 171 | } 172 | 173 | // INT_PIN_CFG register 174 | bool getI2cBypassEnabled() { 175 | readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_BYPASS_EN_BIT, buffer); 176 | return buffer[0]; 177 | } 178 | 179 | void setI2cBypassEnabled(bool enabled) { 180 | writeBit(MPU6050_RA_INT_PIN_CFG, MPU6050_BYPASS_EN_BIT, enabled); 181 | } 182 | 183 | // USER_CTRL register 184 | bool getDMPEnabled() { 185 | readBit(MPU6050_RA_USER_CTRL, MPU6050_DMP_EN_BIT, buffer); 186 | return buffer[0]; 187 | } 188 | 189 | void setDMPEnabled(bool enabled) { 190 | writeBit(MPU6050_RA_USER_CTRL, MPU6050_DMP_EN_BIT, enabled); 191 | } 192 | 193 | // PWR_MGMT_1 register 194 | void reset() { 195 | writeBit(MPU6050_RA_PWR_MGMT_1, MPU6050_DEVICE_RESET_BIT, 1); 196 | } 197 | bool getSleepEnabled() { 198 | readBit(MPU6050_RA_PWR_MGMT_1, MPU6050_SLEEP_BIT, buffer); 199 | return buffer[0]; 200 | } 201 | 202 | void setSleepEnabled(bool enabled) { 203 | writeBit(MPU6050_RA_PWR_MGMT_1, MPU6050_SLEEP_BIT, enabled); 204 | } 205 | 206 | uint8_t getClockSource() { 207 | readBits(MPU6050_RA_PWR_MGMT_1, MPU6050_CLKSEL_BIT, MPU6050_CLKSEL_LEN, buffer); 208 | return buffer[0]; 209 | } 210 | 211 | void setClockSource(uint8_t source) { 212 | writeBits(MPU6050_RA_PWR_MGMT_1, MPU6050_CLKSEL_BIT, MPU6050_CLKSEL_LEN, source); 213 | } 214 | 215 | // WHO_AM_I register 216 | uint8_t getDeviceID() { 217 | readByte(MPU6050_RA_WHO_AM_I, buffer); 218 | return buffer[0]; 219 | } 220 | 221 | protected: 222 | float accelScale; 223 | float gyroScale; 224 | 225 | float getGyroScale(uint8_t gyroRange) { 226 | return 16.4 * pow(2, 3 - gyroRange); 227 | } 228 | 229 | float getAccelScale(uint8_t accelRange) { 230 | return 2048.0 * pow(2, 3 - accelRange); 231 | } 232 | }; 233 | -------------------------------------------------------------------------------- /src/Magnetometer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Magnetometer.h" 4 | 5 | const float Magnetometer::GAUSS_TO_MICROTESLA = 100.0f; 6 | 7 | float Magnetometer::getAzimuth(float declination) { 8 | // Get the magnetic field vector from the device, in uT 9 | Vector3 magneticField = getMagneticField(); 10 | 11 | // Calculate the compass heading 12 | float heading = atan2(magneticField.y, magneticField.x); 13 | 14 | // Adjust the compass heading for local declination (in rads) 15 | heading += declination; 16 | 17 | // Adjust for overflow 18 | if(heading < 0) heading += 2*M_PI; 19 | if(heading > 2*M_PI) heading -= 2*M_PI; 20 | 21 | // Return the heading in degrees 22 | return heading * 180/M_PI; 23 | } 24 | -------------------------------------------------------------------------------- /src/Magnetometer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vector3.h" 4 | 5 | /** 6 | * A magnetometer measures the strength and direction of magnetic fields 7 | * in μT, on three axes. 8 | * 9 | * Using magnetic field readings from a magnetometer, you can also compute 10 | * the azimuth (or compass direction) of your device. 11 | */ 12 | 13 | class Magnetometer { 14 | public: 15 | // Gauss to microTesla multiplier 16 | static const float GAUSS_TO_MICROTESLA; 17 | 18 | // Get the current magnetic field vector, in μT 19 | virtual Vector3 getMagneticField() = 0; 20 | 21 | // Get the current azimuth (compass direction), optionally adjusting for declination 22 | float getAzimuth(float declination = 0.0); 23 | }; 24 | -------------------------------------------------------------------------------- /src/Sensors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Accelerometer.h" 4 | #include "Barometer.h" 5 | #include "Gyroscope.h" 6 | #include "Magnetometer.h" 7 | #include "Thermometer.h" 8 | 9 | // MPU9150 Accelerometer, Gyroscope and Magnetometer 10 | // The MPU9150 combines an MPU6050 and AK8975 in one chip 11 | #ifdef SENSORS_MPU9150_ATTACHED 12 | #define SENSORS_MPU6050_ATTACHED 13 | #define SENSORS_AK8975_ATTACHED 14 | #endif 15 | 16 | // MPU9250 Accelerometer, Gyroscope and Magnetometer 17 | // The MPU9250 combines an MPU6500 and AK8963 in one chip 18 | #ifdef SENSORS_MPU9250_ATTACHED 19 | #define SENSORS_MPU6500_ATTACHED 20 | #define SENSORS_AK8963_ATTACHED 21 | #endif 22 | 23 | // MPU6500 Accelerometer and Gyroscope 24 | // The MPU6500 is both supported by the MPU6050 driver 25 | #ifdef SENSORS_MPU6500_ATTACHED 26 | #define SENSORS_MPU6050_ATTACHED 27 | #endif 28 | 29 | // BMP180 Barometer and Thermometer 30 | // The BMP180 is supported by the BMP085 driver 31 | #ifdef SENSORS_BMP180_ATTACHED 32 | #define SENSORS_BMP085_ATTACHED 33 | #endif 34 | 35 | // AK8963 Magnetometer 36 | #ifdef SENSORS_AK8963_ATTACHED 37 | #include "AK8963.h" 38 | #define MAGNETOMETER_ATTACHED 39 | #endif 40 | 41 | // BMP085 Barometer and Thermometer 42 | #ifdef SENSORS_BMP085_ATTACHED 43 | #include "BMP085.h" 44 | #define BAROMETER_ATTACHED 45 | #define THERMOMETER_ATTACHED 46 | #endif 47 | 48 | // HMC5883L Magnetometer 49 | #ifdef SENSORS_HMC5883L_ATTACHED 50 | #include "HMC5883L.h" 51 | #define MAGNETOMETER_ATTACHED 52 | #endif 53 | 54 | // MPU6050 Accelerometer and Gyroscope 55 | #ifdef SENSORS_MPU6050_ATTACHED 56 | #include "MPU6050.h" 57 | #define ACCELEROMETER_ATTACHED 58 | #define GYROSCOPE_ATTACHED 59 | #endif 60 | 61 | class Sensors { 62 | public: 63 | // Initialize attached sensors 64 | // Only call this after enabling and waking up the I2C bus 65 | static void initialize() { 66 | #ifdef SENSORS_AK8963_ATTACHED 67 | AK8963::getInstance().initialize(); 68 | #endif 69 | 70 | #ifdef SENSORS_BMP085_ATTACHED 71 | BMP085::getInstance().initialize(); 72 | #endif 73 | 74 | #ifdef SENSORS_HMC5883L_ATTACHED 75 | HMC5883L::getInstance().initialize(); 76 | #endif 77 | 78 | #ifdef SENSORS_MPU6050_ATTACHED 79 | MPU6050::getInstance().initialize(); 80 | #endif 81 | } 82 | 83 | static Accelerometer *getAccelerometer() { 84 | #ifdef SENSORS_MPU6050_ATTACHED 85 | return &MPU6050::getInstance(); 86 | #else 87 | return NULL; 88 | #endif 89 | } 90 | 91 | static Barometer *getBarometer() { 92 | #ifdef SENSORS_BMP085_ATTACHED 93 | return &BMP085::getInstance(); 94 | #else 95 | return NULL; 96 | #endif 97 | } 98 | 99 | static Gyroscope *getGyroscope() { 100 | #ifdef SENSORS_MPU6050_ATTACHED 101 | return &MPU6050::getInstance(); 102 | #else 103 | return NULL; 104 | #endif 105 | } 106 | 107 | static Magnetometer *getMagnetometer() { 108 | #if defined(SENSORS_AK8963_ATTACHED) 109 | return &AK8963::getInstance(); 110 | #elif defined(SENSORS_HMC5883L_ATTACHED) 111 | return &HMC5883L::getInstance(); 112 | #else 113 | return NULL; 114 | #endif 115 | } 116 | 117 | static Thermometer *getThermometer() { 118 | #ifdef SENSORS_BMP085_ATTACHED 119 | return &BMP085::getInstance(); 120 | #else 121 | return NULL; 122 | #endif 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /src/Thermometer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vector3.h" 4 | 5 | /** 6 | * A thermometer measures temperature in °C. 7 | */ 8 | 9 | class Thermometer { 10 | public: 11 | // Get the current ambient temperature in °C 12 | virtual float getTemperature() = 0; 13 | }; 14 | -------------------------------------------------------------------------------- /src/Vector3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Vector3 { 4 | union { 5 | float v[3]; 6 | struct { 7 | float x; 8 | float y; 9 | float z; 10 | }; 11 | }; 12 | 13 | Vector3() : x(0), y(0), z(0) { } 14 | }; 15 | --------------------------------------------------------------------------------