Browse Source

Merge remote-tracking branch 'wokoeck/master'

subDesTagesMitExtraKaese 1 year ago
parent
commit
a7da29b8d7
61 changed files with 4080 additions and 51 deletions
  1. 6 2
      .gitignore
  2. 1 0
      .pio/build/project.checksum
  3. 0 0
      .pio/build/uno/idedata.json
  4. 5 0
      .pio/libdeps/uno/BMP280_DEV/.editorconfig
  5. 32 0
      .pio/libdeps/uno/BMP280_DEV/.gitignore
  6. 1 0
      .pio/libdeps/uno/BMP280_DEV/.piopm
  7. 306 0
      .pio/libdeps/uno/BMP280_DEV/BMP280_DEV.cpp
  8. 218 0
      .pio/libdeps/uno/BMP280_DEV/BMP280_DEV.h
  9. 176 0
      .pio/libdeps/uno/BMP280_DEV/Device.cpp
  10. 86 0
      .pio/libdeps/uno/BMP280_DEV/Device.h
  11. 21 0
      .pio/libdeps/uno/BMP280_DEV/LICENSE
  12. 343 0
      .pio/libdeps/uno/BMP280_DEV/README.md
  13. 45 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_HSPI_Normal/BMP280_ESP32_HSPI_Normal.ino
  14. 29 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_I2C_Normal_DefinedPins/BMP280_ESP32_I2C_Normal_DefinedPins.ino
  15. 29 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP8266_I2C_Normal_DefinedPins/BMP280_ESP8266_I2C_Normal_DefinedPins.ino
  16. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Alt_Normal/BMP280_I2C_Alt_Normal.ino
  17. 31 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Forced/BMP280_I2C_Forced.ino
  18. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Normal/BMP280_I2C_Normal.ino
  19. 31 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Forced/BMP280_SPI_Forced.ino
  20. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal/BMP280_SPI_Normal.ino
  21. 44 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal_Multiple/BMP280_SPI_Normal_Multiple.ino
  22. 64 0
      .pio/libdeps/uno/BMP280_DEV/keywords.txt
  23. 9 0
      .pio/libdeps/uno/BMP280_DEV/library.properties
  24. 1 0
      .pio/libdeps/uno/MPU9250/.piopm
  25. 21 0
      .pio/libdeps/uno/MPU9250/LICENSE
  26. 954 0
      .pio/libdeps/uno/MPU9250/MPU9250.h
  27. 151 0
      .pio/libdeps/uno/MPU9250/MPU9250/MPU9250RegisterMap.h
  28. 223 0
      .pio/libdeps/uno/MPU9250/MPU9250/QuaternionFilter.h
  29. 230 0
      .pio/libdeps/uno/MPU9250/README.md
  30. 22 0
      .pio/libdeps/uno/MPU9250/examples/calibration/calibration.ino
  31. 29 0
      .pio/libdeps/uno/MPU9250/examples/calibration_eeprom/calibration_eeprom.ino
  32. 150 0
      .pio/libdeps/uno/MPU9250/examples/calibration_eeprom/eeprom_utils.h
  33. 71 0
      .pio/libdeps/uno/MPU9250/examples/connection_check/connection_check.ino
  34. 17 0
      .pio/libdeps/uno/MPU9250/examples/simple/simple.ino
  35. 20 0
      .pio/libdeps/uno/MPU9250/library.json
  36. 9 0
      .pio/libdeps/uno/MPU9250/library.properties
  37. 60 0
      .vscode/c_cpp_properties.json
  38. 10 0
      .vscode/extensions.json
  39. 44 0
      .vscode/launch.json
  40. BIN
      LICENSE
  41. 12 3
      README.md
  42. BIN
      arduino/src/ultrasonic.cpp
  43. BIN
      images/Aufgabenstellung.jpg
  44. 19 0
      platformio.ini
  45. BIN
      raspberry-pi/.vscode/settings.json
  46. 11 3
      raspberry-pi/config.ini
  47. 131 21
      raspberry-pi/gui/SourceSansPro-Semibold.otf
  48. 7 5
      raspberry-pi/gui/popup.py
  49. 1 0
      raspberry-pi/markers.png
  50. 21 0
      raspberry-pi/sensors/calibration.py
  51. 18 0
      raspberry-pi/sensors/camera_calibration-master/README.md
  52. BIN
      raspberry-pi/sensors/camera_calibration-master/aruco_marker_board.pdf
  53. 115 0
      raspberry-pi/sensors/camera_calibration-master/camera_calibration.py
  54. 28 0
      raspberry-pi/sensors/camera_calibration-master/data_generation.py
  55. 11 0
      raspberry-pi/sensors/camera_calibration-master/importante.txt
  56. BIN
      raspberry-pi/sensors/camera_calibration-master/markers.png
  57. 9 9
      raspberry-pi/sensors/connection.py
  58. 97 8
      raspberry-pi/sensors/magneticSensor.py
  59. 12 0
      raspberry-pi/sensors/opticalSensor.py
  60. 3 0
      start.sh
  61. 0 0
      ultrasound-tests.ods

+ 6 - 2
.gitignore

@@ -1,5 +1,9 @@
-.vscode/c_cpp_properties.json
 .venv
 .vscode/settings.json
 
-__pycache__
+__pycache__
+.vscode/c_cpp_properties.json
+.pio/build/project.checksum
+.pio/build/uno/idedata.json
+.vscode/c_cpp_properties.json
+.vscode/launch.json

+ 1 - 0
.pio/build/project.checksum

@@ -0,0 +1 @@
+fb7487a38c33ab8f5ddac2d3de9ed44f3bd3ef87

File diff suppressed because it is too large
+ 0 - 0
.pio/build/uno/idedata.json


+ 5 - 0
.pio/libdeps/uno/BMP280_DEV/.editorconfig

@@ -0,0 +1,5 @@
+root = true
+
+[*]
+indent_style = tab
+indent_size = 2

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/.gitignore

@@ -0,0 +1,32 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app

+ 1 - 0
.pio/libdeps/uno/BMP280_DEV/.piopm

@@ -0,0 +1 @@
+{"type": "library", "name": "BMP280_DEV", "version": "1.0.18", "spec": {"owner": "martinl1", "id": 6223, "name": "BMP280_DEV", "requirements": null, "url": null}}

+ 306 - 0
.pio/libdeps/uno/BMP280_DEV/BMP280_DEV.cpp

@@ -0,0 +1,306 @@
+/*
+  BMP280_DEV is an I2C/SPI compatible library for the Bosch BMP280 barometer.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 	
+	V1.0.1 -- Added ESP32 HSPI support and change library to unique name
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Changed library name in the library.properties file
+	V1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
+	V1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
+	V1.0.8 -- Used default arguments for begin() member function and 
+						added example using multiple BMP280 devices with SPI comms in NORMAL mode
+	V1.0.9 -- Moved writeMask to Device class and improved measurement detection code
+	V1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
+	V1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and 
+						 flagging up this issue
+	V1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
+						 getCurrentAltitude() and getCurrentMeasurements() functions,
+						 to allow the BMP280 to be read directly without checking the measuring bit
+	V1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#include <BMP280_DEV.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Class Constructors
+////////////////////////////////////////////////////////////////////////////////
+
+BMP280_DEV::BMP280_DEV() { setI2CAddress(BMP280_I2C_ADDR); }		// Constructor for I2C communications		
+#ifdef ARDUINO_ARCH_ESP8266
+BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl) : Device(sda, scl) { setI2CAddress(BMP280_I2C_ADDR); } 	// Constructor for I2C comms on ESP8266
+#endif
+BMP280_DEV::BMP280_DEV(uint8_t cs) : Device(cs) {}			   			// Constructor for SPI communications
+#ifdef ARDUINO_ARCH_ESP32 																			
+BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl) : Device(sda, scl) { setI2CAddress(BMP280_I2C_ADDR); } 	// Constructor for I2C comms on ESP32
+BMP280_DEV::BMP280_DEV(uint8_t cs, uint8_t spiPort, SPIClass& spiClass) : Device(cs, spiPort, spiClass) {} // Constructor for SPI communications on the ESP32
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Public Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+uint8_t BMP280_DEV::begin(Mode mode, 															// Initialise BMP280 device settings
+											Oversampling presOversampling, 
+											Oversampling tempOversampling,
+											IIRFilter iirFilter,
+											TimeStandby timeStandby)
+{
+	initialise();																											// Call the Device base class "initialise" function
+	if (readByte(BMP280_DEVICE_ID) != DEVICE_ID)              				// Check the device ID
+  {
+    return 0;                                                     	// If the ID is incorrect return 0
+  }	
+  reset();                                                          // Reset the BMP280 barometer
+  readBytes(BMP280_TRIM_PARAMS, (uint8_t*)&params, sizeof(params)); // Read the trim parameters into the params structure
+	setConfigRegister(iirFilter, timeStandby); 												// Initialise the BMP280 configuration register
+	setCtrlMeasRegister(mode, presOversampling, tempOversampling);		// Initialise the BMP280 control and measurement register	
+	return 1;																													// Report successful initialisation
+}
+
+uint8_t BMP280_DEV::begin(Mode mode, uint8_t addr)									// Initialise BMP280 with default settings, but selected mode and
+{																																		// I2C address
+	setI2CAddress(addr);
+	return begin(mode);
+}
+
+uint8_t BMP280_DEV::begin(uint8_t addr)															// Initialise BMP280 with default settings and selected I2C address
+{
+	setI2CAddress(addr);
+	return begin();
+}
+
+void BMP280_DEV::reset()																						// Reset the BMP280 barometer
+{
+	writeByte(BMP280_RESET, RESET_CODE);                     									
+  delay(10);                                                                
+}
+
+void BMP280_DEV::startNormalConversion() { setMode(NORMAL_MODE); }	// Start continuous measurement in NORMAL_MODE
+
+void BMP280_DEV::startForcedConversion() 														// Start a one shot measurement in FORCED_MODE
+{ 
+	ctrl_meas.reg = readByte(BMP280_CTRL_MEAS);											 	// Read the control and measurement register
+	if (ctrl_meas.bit.mode == SLEEP_MODE)															// Only set FORCED_MODE if we're already in SLEEP_MODE
+	{
+		setMode(FORCED_MODE);
+	}	
+}			
+
+void BMP280_DEV::stopConversion() { setMode(SLEEP_MODE); }					// Stop the conversion and return to SLEEP_MODE
+
+void BMP280_DEV::setPresOversampling(Oversampling presOversampling)	// Set the pressure oversampling rate
+{
+	ctrl_meas.bit.osrs_p = presOversampling;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+void BMP280_DEV::setTempOversampling(Oversampling tempOversampling)	// Set the temperature oversampling rate
+{
+	ctrl_meas.bit.osrs_t = tempOversampling;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+void BMP280_DEV::setIIRFilter(IIRFilter iirFilter)									// Set the IIR filter setting
+{
+	config.bit.filter = iirFilter;
+	writeByte(BMP280_CONFIG, config.reg);
+}
+
+void BMP280_DEV::setTimeStandby(TimeStandby timeStandby)						// Set the time standby measurement interval
+{
+	config.bit.t_sb = timeStandby;
+	writeByte(BMP280_CONFIG, config.reg);
+}
+
+void BMP280_DEV::setSeaLevelPressure(float pressure)								// Set the sea level pressure value
+{
+	sea_level_pressure = pressure;
+}
+
+void BMP280_DEV::getCurrentTemperature(float &temperature)					// Get the current temperature without checking the measuring bit
+{
+	uint8_t data[3];                                                  // Create a data buffer
+	readBytes(BMP280_TEMP_MSB, &data[0], 3);             							// Read the temperature and pressure data
+	int32_t adcTemp = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;  // Copy the temperature and pressure data into the adc variables
+	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
+	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
+}
+
+uint8_t BMP280_DEV::getTemperature(float &temperature)							// Get the temperature with measurement check
+{
+	if (!dataReady())																									// Check if a measurement is ready
+	{
+		return 0;
+	}
+	getCurrentTemperature(temperature);																// Get the current temperature
+	return 1;
+}
+
+void BMP280_DEV::getCurrentPressure(float &pressure)								// Get the current pressure without checking the measuring bit
+{
+	float temperature;
+	getCurrentTempPres(temperature, pressure);
+}
+
+uint8_t BMP280_DEV::getPressure(float &pressure)										// Get the pressure
+{
+	float temperature;
+	return getTempPres(temperature, pressure);
+}
+
+void BMP280_DEV::getCurrentTempPres(float &temperature, float &pressure)	// Get the current temperature and pressure without checking the measuring bit
+{
+	uint8_t data[6];                                                  // Create a data buffer
+	readBytes(BMP280_PRES_MSB, &data[0], 6);             							// Read the temperature and pressure data
+	int32_t adcTemp = (int32_t)data[3] << 12 | (int32_t)data[4] << 4 | (int32_t)data[5] >> 4;  // Copy the temperature and pressure data into the adc variables
+	int32_t adcPres = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;
+	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
+	uint32_t pres = bmp280_compensate_P_int64(adcPres);               // Pressure compensation (function from BMP280 datasheet)
+	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
+	pressure = (float)pres / 256.0f / 100.0f;                         // Calculate the pressure in millibar
+}
+
+uint8_t BMP280_DEV::getTempPres(float &temperature, float &pressure)	// Get the temperature and pressure
+{
+	if (!dataReady())																									// Check if a measurement is ready
+	{
+		return 0;
+	}
+	getCurrentTempPres(temperature, pressure);												// Get the current temperature and pressure
+	return 1;
+}
+
+void BMP280_DEV::getCurrentAltitude(float &altitude)								// Get the current altitude without checking the measuring bit
+{
+	float temperature, pressure;
+	getCurrentMeasurements(temperature, pressure, altitude);
+}
+
+uint8_t BMP280_DEV::getAltitude(float &altitude)										// Get the altitude
+{
+	float temperature, pressure;
+	return getMeasurements(temperature, pressure, altitude);
+}
+
+void BMP280_DEV::getCurrentMeasurements(float &temperature, float &pressure, float &altitude) // Get all the measurements without checking the measuring bit
+{
+	getCurrentTempPres(temperature, pressure);
+	altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
+}
+
+uint8_t BMP280_DEV::getMeasurements(float &temperature, float &pressure, float &altitude)		// Get all measurements temperature, pressue and altitude
+{  
+	if (getTempPres(temperature, pressure))
+	{
+		altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
+		return 1;
+	}
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Private Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+void BMP280_DEV::setMode(Mode mode)																	// Set the BMP280's mode
+{
+	ctrl_meas.bit.mode = mode;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+// Set the BMP280 control and measurement register 
+void BMP280_DEV::setCtrlMeasRegister(Mode mode, Oversampling presOversampling, Oversampling tempOversampling)
+{
+	ctrl_meas.reg = tempOversampling << 5 | presOversampling << 2 | mode;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);                              
+}
+
+// Set the BMP280 configuration register
+void BMP280_DEV::setConfigRegister(IIRFilter iirFilter, TimeStandby timeStandby)
+{
+	config.reg = timeStandby << 5 | iirFilter << 2;
+	writeByte(BMP280_CONFIG, config.reg);                              
+}
+
+uint8_t BMP280_DEV::dataReady()																			// Check if a measurement is ready
+{			
+	if (ctrl_meas.bit.mode == SLEEP_MODE)														 	// If we're in SLEEP_MODE return immediately
+	{
+		return 0;
+	}
+	status.reg = readByte(BMP280_STATUS);															// Read the status register				
+	if (status.bit.measuring ^ previous_measuring)						 				// Edge detection: check if the measurement bit has been changed
+	{
+		previous_measuring = status.bit.measuring;											// Update the previous measuring flag
+		if (!status.bit.measuring)																			// Check if the measuring bit has been cleared
+		{
+			if (ctrl_meas.bit.mode == FORCED_MODE)												// If we're in FORCED_MODE switch back to SLEEP_MODE
+			{
+				ctrl_meas.bit.mode = SLEEP_MODE;												
+			}
+			return 1;																											// A measurement is ready
+		}
+	}
+	return 0;																													// A measurement is still pending
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Bosch BMP280_DEV (Private) Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
+// t_fine carries fine temperature as global value
+int32_t BMP280_DEV::bmp280_compensate_T_int32(int32_t adc_T)
+{
+  int32_t var1, var2, T;
+  var1 = ((((adc_T >> 3) - ((int32_t)params.dig_T1 << 1))) * ((int32_t)params.dig_T2)) >> 11;
+  var2 = (((((adc_T >> 4) - ((int32_t)params.dig_T1)) * ((adc_T >> 4) - ((int32_t)params.dig_T1))) >> 12) *
+  ((int32_t)params.dig_T3)) >> 14;
+  t_fine = var1 + var2;
+  T = (t_fine * 5 + 128) >> 8;
+  return T;
+}
+
+// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
+// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
+uint32_t BMP280_DEV::bmp280_compensate_P_int64(int32_t adc_P)
+{
+  int64_t var1, var2, p;
+  var1 = ((int64_t)t_fine) - 128000;
+  var2 = var1 * var1 * (int64_t)params.dig_P6;
+  var2 = var2 + ((var1 * (int64_t)params.dig_P5) << 17);
+  var2 = var2 + (((int64_t)params.dig_P4) << 35);
+  var1 = ((var1 * var1 * (int64_t)params.dig_P3) >> 8) + ((var1 * (int64_t)params.dig_P2) << 12);
+  var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)params.dig_P1) >> 33;
+  if (var1 == 0)
+  {
+    return 0; // avoid exception caused by division by zero
+  }
+  p = 1048576 - adc_P;
+  p = (((p << 31) - var2) * 3125) / var1;
+  var1 = (((int64_t)params.dig_P9) * (p >> 13) * (p>>13)) >> 25;
+  var2 = (((int64_t)params.dig_P8) * p) >> 19;
+  p = ((p + var1 + var2) >> 8) + (((int64_t)params.dig_P7) << 4);
+  return (uint32_t)p;
+}

+ 218 - 0
.pio/libdeps/uno/BMP280_DEV/BMP280_DEV.h

@@ -0,0 +1,218 @@
+/*
+  BMP280_DEV is an I2C/SPI compatible library for the Bosch BMP280 barometer.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 		
+	V1.0.1 -- Added ESP32 HSPI support and change library to unique name
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Changed library name in the library.properties file
+	V1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
+	V1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
+	V1.0.8 -- Used default arguments for begin() member function and 
+						added example using multiple BMP280 devices with SPI comms in NORMAL mode	
+	V1.0.9 -- Moved writeMask to Device class and improved measurement detection code
+	V1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
+	V1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and 
+						 flagging up this issue
+	V1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
+						 getCurrentAltitude() and getCurrentMeasurements() functions,
+						 to allow the BMP280 to be read directly without checking the measuring bit
+	V1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#ifndef BMP280_DEV_h
+#define BMP280_DEV_h
+
+#include <Device.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Definitions
+////////////////////////////////////////////////////////////////////////////////
+
+#define BMP280_I2C_ADDR		 		0x77				// The BMP280 I2C address
+#define BMP280_I2C_ALT_ADDR 	0x76				// The BMP280 I2C alternate address
+#define DEVICE_ID 						0x58				// The BMP280 device ID
+#define RESET_CODE						0xB6				// The BMP280 reset code
+
+enum SPIPort { BMP280_SPI0, BMP280_SPI1 };
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Registers
+////////////////////////////////////////////////////////////////////////////////
+
+enum {
+	BMP280_TRIM_PARAMS		 = 0x88,          // Trim parameter registers' base sub-address
+	BMP280_DEVICE_ID 			 = 0xD0,          // Device ID register sub-address
+	BMP280_RESET 					 = 0xE0,          // Reset register sub-address
+	BMP280_STATUS      		 = 0xF3,          // Status register sub-address
+	BMP280_CTRL_MEAS   		 = 0xF4,          // Control and measurement register sub-address
+	BMP280_CONFIG       	 = 0xF5,          // Configuration register sub-address
+	BMP280_PRES_MSB    		 = 0xF7,          // Pressure Most Significant Byte (MSB) register sub-address
+	BMP280_PRES_LSB    		 = 0xF8,          // Pressure Least Significant Byte (LSB) register sub-address
+	BMP280_PRES_XLSB   		 = 0xF9,          // Pressure eXtended Least Significant Byte (XLSB) register sub-address
+	BMP280_TEMP_MSB    		 = 0xFA,          // Pressure Most Significant Byte (MSB) register sub-address
+	BMP280_TEMP_LSB    	 	 = 0xFB,          // Pressure Least Significant Byte (LSB) register sub-address
+	BMP280_TEMP_XLSB    	 = 0xFC 					// Pressure eXtended Least Significant Byte (XLSB) register sub-address
+};          
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Modes
+////////////////////////////////////////////////////////////////////////////////
+
+enum Mode {
+	SLEEP_MODE          	 = 0x00,          // Device mode bitfield in the control and measurement register 
+	FORCED_MODE         	 = 0x01,
+	NORMAL_MODE         	 = 0x03
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Register bit field Definitions
+////////////////////////////////////////////////////////////////////////////////
+
+enum Oversampling {
+	OVERSAMPLING_SKIP 		 = 0x00,     			// Oversampling bit fields in the control and measurement register
+	OVERSAMPLING_X1   		 = 0x01,
+	OVERSAMPLING_X2   		 = 0x02,
+	OVERSAMPLING_X4  		   = 0x03,
+	OVERSAMPLING_X8    		 = 0x04,
+	OVERSAMPLING_X16   	 	 = 0x05
+};
+
+enum IIRFilter {
+	IIR_FILTER_OFF  			 = 0x00,     			// Infinite Impulse Response (IIR) filter bit field in the configuration register
+	IIR_FILTER_2    			 = 0x01,
+	IIR_FILTER_4           = 0x02,
+	IIR_FILTER_8           = 0x03,
+	IIR_FILTER_16          = 0x04
+};
+
+enum TimeStandby {
+	TIME_STANDBY_05MS      = 0x00,     		  // Time standby bit field in the configuration register
+	TIME_STANDBY_62MS      = 0x01,
+	TIME_STANDBY_125MS     = 0x02,
+	TIME_STANDBY_250MS     = 0x03,
+	TIME_STANDBY_500MS     = 0x04,
+	TIME_STANDBY_1000MS    = 0x05,
+	TIME_STANDBY_2000MS    = 0x06,
+	TIME_STANDBY_4000MS    = 0x07
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Class definition
+////////////////////////////////////////////////////////////////////////////////
+
+class BMP280_DEV : public Device {															// Derive the BMP280_DEV class from the Device class
+	public:
+		BMP280_DEV();																								// BMP280_DEV object for I2C operation
+#ifdef ARDUINO_ARCH_ESP8266
+		BMP280_DEV(uint8_t sda, uint8_t scl);												// BMP280_DEV object for ESP8266 I2C operation with user-defined pins
+#endif
+		BMP280_DEV(uint8_t cs);																			// BMP280_DEV object for SPI operation
+#ifdef ARDUINO_ARCH_ESP32
+		BMP280_DEV(uint8_t sda, uint8_t scl);												// BMP280_DEV object for ESP32 I2C operation with user-defined pins
+		BMP280_DEV(uint8_t cs, uint8_t spiPort, SPIClass& spiClass);	// BMP280_DEV object for SPI1 with supplied SPIClass object
+#endif
+		uint8_t begin(Mode mode = SLEEP_MODE, 												// Initialise the barometer with arguments
+									Oversampling presOversampling = OVERSAMPLING_X16, 
+									Oversampling tempOversampling = OVERSAMPLING_X2, 
+									IIRFilter iirFilter = IIR_FILTER_OFF, 
+									TimeStandby timeStandby = TIME_STANDBY_05MS);
+		uint8_t begin(Mode mode, uint8_t addr);											// Initialise the barometer specifying start mode and I2C addrss
+		uint8_t begin(uint8_t addr);																// Initialise the barometer specifying I2C address with default initialisation
+		void reset();																								// Soft reset the barometer		
+		void startNormalConversion();																// Start continuous measurement in NORMAL_MODE
+		void startForcedConversion();															  // Start a one shot measurement in FORCED_MODE
+		void stopConversion();																			// Stop the conversion and return to SLEEP_MODE
+		void setPresOversampling(Oversampling presOversampling);		// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16
+		void setTempOversampling(Oversampling tempOversampling);		// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16
+		void setIIRFilter(IIRFilter iirFilter);											// Set the IIR filter setting: OFF, 2, 4, 8, 16
+		void setTimeStandby(TimeStandby timeStandby);	 							// Set the time standby measurement interval: 0.5, 62, 125, 250, 500ms, 1s, 2s, 4s
+		void setSeaLevelPressure(float pressure = 1013.23f);				// Set the sea level pressure value
+		void getCurrentTemperature(float &temperature);							// Get the current temperature measurement without checking the measuring bit
+		uint8_t getTemperature(float &temperature);									// Get a temperature measurement
+		void getCurrentPressure(float &pressure);										// Get the current pressure without checking the measuring bit
+		uint8_t getPressure(float &pressure);												// Get a pressure measurement
+		void getCurrentTempPres(float &temperature, float &pressure); // Get the current temperature and pressure without checking the measuring bit
+		uint8_t getTempPres(float &temperature, float &pressure);		// Get a temperature and pressure measurement
+		void getCurrentAltitude(float &altitude);										// Get the current altitude without checking the measuring bit
+		uint8_t getAltitude(float &altitude);												// Get an altitude measurement
+		void getCurrentMeasurements(float &temperature, float &pressure, float &altitude); // Get all measurements without checking the measuring bit
+		uint8_t getMeasurements(float &temperature, float &pressure, float &altitude);	// Get temperature, pressure and altitude measurements
+	protected:
+	private:
+		void setMode(Mode mode);																		// Set the barometer mode
+		void setCtrlMeasRegister(Mode mode, Oversampling presOversampling, Oversampling tempOversamping);		// Set the BMP280 control and measurement register
+		void setConfigRegister(IIRFilter iirFilter, TimeStandby timeStandby);		// Set the BMP280 configuration register
+		uint8_t dataReady();																				// Checks if a measurement is ready
+	
+		struct {																										// The BMP280 compensation trim parameters (coefficients)
+			uint16_t dig_T1;
+			int16_t  dig_T2;
+			int16_t  dig_T3;
+			uint16_t dig_P1;
+			int16_t  dig_P2;
+			int16_t  dig_P3;
+			int16_t  dig_P4;
+			int16_t  dig_P5;
+			int16_t  dig_P6;
+			int16_t  dig_P7;
+			int16_t  dig_P8;
+			int16_t  dig_P9;
+		} params;
+			
+		union {																											// Copy of the BMP280's configuration register
+			struct {
+				uint8_t spi3w_en : 1;
+				uint8_t 				 : 1;
+				uint8_t filter 	 : 3;
+				uint8_t t_sb		 : 3;
+			} bit;
+			uint8_t reg;
+		} config = { .reg = 0 };
+		
+		union {																											// Copy of the BMP280's control and measurement register
+			struct {
+				uint8_t mode   : 2;
+				uint8_t osrs_p : 3;
+				uint8_t osrs_t : 3;
+			} bit;
+			uint8_t reg;
+		} ctrl_meas = { .reg = 0 };
+			
+		union {																											// Copy of the BMP280's status register
+			struct {
+				uint8_t im_update : 1;
+				uint8_t						: 2;
+				uint8_t measuring : 1;
+			} bit;
+			uint8_t reg;
+		} status = { .reg = 0 };
+		
+		int32_t t_fine;																							// Bosch t_fine variable
+		int32_t bmp280_compensate_T_int32(int32_t adc_T);						// Bosch temperature compensation function
+		uint32_t bmp280_compensate_P_int64(int32_t adc_P);					// Bosch pressure compensation function
+		bool previous_measuring;																		// Previous measuring state
+		float sea_level_pressure = 1013.23f;												// Sea level pressure
+};
+
+#endif

+ 176 - 0
.pio/libdeps/uno/BMP280_DEV/Device.cpp

@@ -0,0 +1,176 @@
+/*
+  Device is an I2C/SPI compatible base class library.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release
+	V1.0.1 -- Added ESP32 HSPI support	
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Addition of SPI write and read byte masks
+	V1.0.4 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.5 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.6 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#include <Device.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Class Constructors
+////////////////////////////////////////////////////////////////////////////////
+
+Device::Device() : comms(I2C_COMMS) {}															// Initialise constructor for I2C communications
+#ifdef ARDUINO_ARCH_ESP8266
+Device::Device(uint8_t sda, uint8_t scl) : comms(I2C_COMMS_DEFINED_PINS), sda(sda), scl(scl) {}	// Constructor for ESP8266 I2C with user-defined pins
+#endif
+Device::Device(uint8_t cs) : comms(SPI_COMMS), cs(cs), spiClockSpeed(1000000) {}		// Constructor for SPI communications
+#ifdef ARDUINO_ARCH_ESP32																														
+Device::Device(uint8_t sda, uint8_t scl) : comms(I2C_COMMS_DEFINED_PINS), sda(sda), scl(scl) {}	// Constructor for ESP32 I2C with user-defined pins
+Device::Device(uint8_t cs, uint8_t spiPort, SPIClass& spiClass) 										// Constructor for ESP32 HSPI communications
+	: comms(SPI_COMMS), cs(cs), spiPort(spiPort), spi(&spiClass), spiClockSpeed(1000000) {}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Public Member Function
+////////////////////////////////////////////////////////////////////////////////
+
+void Device::setClock(uint32_t clockSpeed)													// Set the I2C or SPI clock speed
+{
+	if (comms == I2C_COMMS)
+	{
+		Wire.setClock(clockSpeed);
+	}
+	else
+	{
+		spiClockSpeed = clockSpeed;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Device I2C & SPI Wrapper (Protected) Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+void Device::initialise()																						// Initialise device communications
+{
+  if (comms == I2C_COMMS)																						// Check with communications bus has been selected I2C or SPI
+	{
+		Wire.begin();																										// Initialise I2C communication
+		Wire.setClock(400000);																					// Set the SCL clock to default of 400kHz
+	}
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+	else if (comms == I2C_COMMS_DEFINED_PINS)													// Check if the ESP8266 or ESP32 has specified user-defined I2C pins
+	{
+		Wire.begin(sda, scl);																						// Initialise I2C communication with user-defined pins
+		Wire.setClock(400000);																					// Set the SCL clock to default of 400kHz
+		comms = I2C_COMMS;																							// Set the communications to standard I2C
+	}
+#endif
+	else
+	{
+		digitalWrite(cs, HIGH);																					// Pull the chip select (CS) pin high
+		pinMode(cs, OUTPUT);																						// Set-up the SPI chip select pin
+#ifdef ARDUINO_ARCH_ESP32
+		if (spiPort == HSPI)																						// Set-up spi pointer for VSPI or HSPI communications
+		{
+			spi->begin(14, 27, 13, 2);																		// Start HSPI on SCK 14, MOSI 13, MISO 24, SS CS (GPIO2 acts as dummy pin)
+		}
+		else
+		{
+			spi = &SPI;																										// Start VSPI on SCK 5, MOSI 18, MISO 19, SS CS
+			spi->begin();
+		}														
+#else
+		spi = &SPI;																											// Set-up spi pointer for SPI communications
+		spi->begin();
+#endif
+	}
+}
+
+void Device::setI2CAddress(uint8_t addr)														// Set the Device's I2C address
+{	
+	address = addr;
+}
+
+void Device::writeByte(uint8_t subAddress, uint8_t data)
+{
+  if (comms == I2C_COMMS)
+	{
+		Wire.beginTransmission(address);  															// Write a byte to the sub-address using I2C
+		Wire.write(subAddress);          
+		Wire.write(data);                 
+		Wire.endTransmission();          
+	}
+	else // if (comms == SPI_COMMS)
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));	// Write a byte to the sub-address using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress & WRITE_MASK);
+		spi->transfer(data);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();
+	}
+}
+
+uint8_t Device::readByte(uint8_t subAddress)												// Read a byte from the sub-address using I2C
+{
+  uint8_t data = 0x00;
+	if (comms == I2C_COMMS)																		
+	{
+		Wire.beginTransmission(address);         
+		Wire.write(subAddress);                  
+		Wire.endTransmission(false);             
+		Wire.requestFrom(address, (uint8_t)1);	 
+		data = Wire.read();                      
+	}
+	else // if (comms == SPI_COMMS)
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));		// Read a byte from the sub-address using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress | READ_MASK);
+		data = spi->transfer(data);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();	
+	}
+  return data;                             													// Return data read from sub-address register
+}
+
+void Device::readBytes(uint8_t subAddress, uint8_t* data, uint16_t count)
+{  
+  if (comms == I2C_COMMS)																						// Read "count" bytes into the "data" buffer using I2C
+	{
+		Wire.beginTransmission(address);          
+		Wire.write(subAddress);                   
+		Wire.endTransmission(false);              
+		uint8_t i = 0;
+		Wire.requestFrom(address, (uint8_t)count);  
+		while (Wire.available()) 
+		{
+			data[i++] = Wire.read();          
+		}
+	}
+	else // if (comms == SPI_COMMS)		
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));	// Read "count" bytes into the "data" buffer using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress | READ_MASK);
+		spi->transfer(data, count);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();	
+	}
+}

+ 86 - 0
.pio/libdeps/uno/BMP280_DEV/Device.h

@@ -0,0 +1,86 @@
+/*
+  Device is an I2C/SPI compatible base class library.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 
+	V1.0.1 -- Added ESP32 HSPI support	
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Addition of SPI write and read byte masks
+	V1.0.4 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.5 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.6 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#ifndef Device_h
+#define Device_h
+
+#include <Arduino.h>
+#include <Wire.h>
+#include <SPI.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Communications
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+enum Comms { I2C_COMMS, SPI_COMMS, I2C_COMMS_DEFINED_PINS };
+#else						 
+enum Comms { I2C_COMMS, SPI_COMMS };		 
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Class definition
+////////////////////////////////////////////////////////////////////////////////
+
+class Device{
+	public:
+		Device();																										// Device object for I2C operation
+#ifdef ARDUINO_ARCH_ESP8266
+		Device(uint8_t sda, uint8_t scl);														// Device object for ESP8266 I2C operation with user-defined pins
+#endif
+		Device(uint8_t cs);																					// Device object for SPI operation
+#ifdef ARDUINO_ARCH_ESP32
+		Device(uint8_t sda, uint8_t scl);														// Device object for ESP32 I2C operation with user-defined pins
+		Device(uint8_t cs, uint8_t spiPort, SPIClass& spiClass);		// Device object for ESP32 HSPI operation with supplied SPI object
+#endif		
+		void setClock(uint32_t clockSpeed);													// Set the I2C/SPI clock speed
+	protected:
+		void initialise();																					// Initialise communications	
+		void setI2CAddress(uint8_t addr);											  		// Set the Device I2C address
+		void writeByte(uint8_t subAddress, uint8_t data);						// I2C and SPI write byte wrapper function
+		uint8_t readByte(uint8_t subAddress);												// I2C and SPI read byte wrapper function
+		void readBytes(uint8_t subAddress, uint8_t* dest, uint16_t count);		// I2C and SPI read bytes wrapper function
+	private:
+		Comms comms;																								// Communications bus: I2C or SPI
+		uint8_t address;																						// The device I2C address
+		uint8_t cs;																									// The SPI chip select pin
+#ifdef ARDUINO_ARCH_ESP32
+		uint8_t spiPort;																						// SPI port type VSPI or HSPI
+#endif
+		SPIClass* spi;																							// Pointer to the SPI class
+		uint32_t spiClockSpeed;																			// The SPI clock speed
+		const uint8_t WRITE_MASK = 0x7F;														// Sub-address write mask for SPI communications
+		const uint8_t READ_MASK  = 0x80;														// Sub-address read mask for SPI communications
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+		uint8_t sda, scl;																						// Software I2C SDA and SCL pins 
+#endif
+};
+#endif

+ 21 - 0
.pio/libdeps/uno/BMP280_DEV/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 MartinL1
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 343 - 0
.pio/libdeps/uno/BMP280_DEV/README.md

@@ -0,0 +1,343 @@
+# BMP280_DEV
+An Arduino compatible, non-blocking, I2C/SPI library for the Bosch BMP280 barometer.
+
+![alt text](https://cdn-learn.adafruit.com/assets/assets/000/026/851/small240/sensors_2651_iso_ORIG.jpg?1438369374 "Adafruit BMP280 Breakout Board")
+
+© Copyright, image courtesy of [Adafruit Industries](https://www.adafruit.com/product/2651) lisensed under the terms of the [Create Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/legalcode). 
+
+This BMP280_DEV library offers the following features:
+
+- Returns temperature in degrees celsius (**°C**), pressure in hectoPascals/millibar (**hPa**) and altitude in metres (**m**)
+- NORMAL or FORCED modes of operation
+- I2C or hardware SPI communications with configurable clock rates
+- Non-blocking operation 
+- In NORMAL mode barometer returns results at the specified standby time interval
+- Highly configurable, allows for changes to pressure and temperature oversampling, IIR filter and standby time
+
+---
+## __Contents__
+
+1. [Version](#version)
+2. [Arduino Compatiblility](#arduino_compatibility)
+3. [Installation](#installation)
+3. [Usage](#usage)
+	1. [BMP280_DEV Library](#bmp280_dev_library)
+	2. [Device Initialisation](#device_intialisation)
+	3. [Device Configuration](#device_configuration)
+	4. [Modes Of Operation](#modes_of_operation)
+	5. [Results Acquisition](#results_acquisition)
+	6. [Code Implementation](#code_implementation)
+5. [Example Code](#example_code)
+
+<a name="version"></a>
+## __Version__
+
+- Version 1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
+- Version 1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
+						 				getCurrentAltitude() and getCurrentMeasurements() functions,
+						 				to allow the BMP280 to be read directly without checking the status register
+- Version 1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
+- Version 1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and flagging up this issue
+- Version 1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
+- Version 1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+- Version 1.0.9 -- Moved writeMask to Device class and improved measurement detection code
+- Version 1.0.8 -- Use default arguments for begin() member function and 
+									 add example using multiple BMP280 devices with SPI comms in NORMAL mode
+- Version 1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
+- Version 1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
+- Version 1.0.3 -- Change library name in the library.properties file
+- Version 1.0.2 -- Modification to allow external creation of a HSPI object on the ESP32
+- Version 1.0.1 -- Added ESP32 HSPI support and changed library name for Arduino compatibility
+- Version 1.0.0 -- Intial version
+
+<a name="arduino_compatibility"></a>
+## __Arduino Compatibility__
+
+- All Arduino boards, but for 5V Arduino boards (such as the Uno, Nano, Mega, Leonardo, etc...), please check if the BMP280 breakout board requires a 5V to +3.3V voltage level shifter
+
+<a name="installation"></a>
+## __Installation__
+
+The BMP280_DEV library can be installed using the Arduino IDE's Library Manager. To access the Library Manager, in the Arduino IDE's menu select _Sketch->Include Library->Manage Libraries..._. In the Library Manager's search bar type BMP280 then select the "Install" button in the BMP280_DEV entry.
+
+Alternatively simply download BMP280_DEV from this Github repository, un-zip or extract the files and place the BMP280_DEV directory in your _.../Arduino/libraries/..._ folder. The _.../Arduino/..._ folder is the one where your Arduino IDE sketches are usually located.
+
+<a name="usage"></a>
+## __Usage__
+
+<a name="bmp280_dev_library"></a>
+### __BMP280_DEV Library__
+
+Simply include the BMP280_DEV.h file at the beginning of your sketch:
+
+```
+#include <BMP280_DEV.h>
+```
+
+For I2C communication the BMP280_DEV object is normally created (instantiated) without parameters:
+
+```
+BMP280_DEV bmp280;	// Set up I2C communications
+```
+
+By default the library uses the BMP280's I2C address 0x77. (To use the alternate I2C address: 0x76, see the begin() function below.
+
+The ESP8266 and ESP32 also offer the option of selecting the I2C SDA and SDA pins as parameters:
+
+```
+BMP280_DEV bmp280(A6, A7);	// Set up I2C communications on ESP32 pins A6 (SDA) and A7 (SCL): bmp280(SDA, SCL);
+```
+
+If no parameters are selected, the ESP32 uses its default SDA and SCL pins.
+
+For SPI communication the chip select (CS) Arduino digital output pin is specified as an argument, for example digital pin 10:
+
+```
+BMP280_dev bmp280(10);	// Set up SPI communications on digital pin D10
+```
+
+The library also supports the ESP32 HSPI operation on pins: SCK 14, MOSI 13, MISO 27 and user defined SS (CS):
+
+```
+SPIClass SPI1(HSPI);			// Create the SPI1 HSPI object
+BMP280_DEV bmp(21, HSPI, SPI1);		// Set up HSPI port communications on the ESP32
+```
+
+By default the I2C runs in fast mode at 400kHz and SPI at 1MHz. However it is possible to change either the I2C or SPI clock speed using the set clock function:
+
+```
+bmp280.setClock(4000000);			// Set the SPI clock to 4MHz
+```
+
+---
+### __Device Initialisation__
+
+To initialise the bmp280 it is necessary to call the begin() function with or without parameters. The parameters specify the starting mode, pressure/temperature oversampling, IIR filter and standby time options respectively:
+
+```
+bmp280.begin(SLEEP_MODE, OVERSAMPLING_X16, OVERSAMPLING_X2, IIR_FILTER_4, TIME_STANDBY_05MS);
+```
+
+Alternatively simply call the begin function without any paremeters, this sets up the default configuration: SLEEP_MODE, pressure oversampling X16, temperature oversampling X2, IIR filter OFF and a standby time of 0.5ms:
+
+```
+bmp280.begin();	// Initialise the BMP280 with default configuration
+```
+
+Another alternative is to pass the BMP280's mode as an argument:
+
+```
+bmp280.begin(NORMAL_MODE);	// Initialise the BMP280 in NORMAL_MODE with default configuration
+```
+
+Or, specifying mode and alternate I2C address:
+
+```
+bmp280.begin(FORCED_MODE, BMP280_I2C_ALT_ADDR);	// Initialise the BMP280 in FORCED_MODE with the alternate I2C address (0x76)
+```
+
+Or even just the alternate I2C address, (BMP280 initialised in SLEEP_MODE by default):
+
+```
+bmp280.begin(BMP280_I2C_ALT_ADDR);	// Initialise the BMP280 with the alternate I2C address (0x76)
+```
+
+Note that the begin functions return the value 1 upon successful initialisation, otherwise it returns 0 for failure.
+
+---
+<a name="device_configuration"></a>
+### __Device Configuration__
+
+After initialisation it is possible to change the BMP280 configuration with the following functions:
+
+```
+bmp280.setPresOversamping(OVERSAMPING_X4);	// Options are OVERSAMPLING_SKIP, _X1, _X2, _X4, _X8, _X16
+```
+
+```
+bmp280.setTempOversamping(OVERSAMPING_X4);	// Options are OVERSAMPLING_SKIP, _X1, _X2, _X4, _X8, _X16
+```
+
+```
+bmp280.setIIRFilter(IIR_FILTER_16);	// Options are IIR_FILTER_OFF, _2, _4, _8, _16
+```
+
+```
+bmp280.setTimeStandby(TIME_STANDBY_2000MS);	// Options are TIME_STANDBY_05MS, _62MS, _125MS, _250MS, _500MS, _1000MS, 2000MS, 4000MS
+```
+---
+<a name="modes_of_operation"></a>
+### __Modes Of Operation__
+
+The BMP280 has 3 modes of operation: **SLEEP_MODE**, **NORMAL_MODE** and **FORCED_MODE**: 
+
+- **SLEEP_MODE**: puts the device into an inactive standby state 
+
+- **NORMAL_MODE**: performs continuous conversions, separated by the standby time
+
+- **FORCED_MODE**: performs a single conversion, returning to **SLEEP_MODE** upon completion
+
+To kick-off conversions in **NORMAL_MODE**:
+
+```
+bmp280.startNormalConversion();	// Start continuous conversions, separated by the standby time
+```
+
+To perform a single oneshot conversion in **FORCED_MODE**:
+
+```
+bmp280.startForcedConversion();	// Start a single oneshot conversion
+```
+
+To stop the conversion at anytime and return to **SLEEP_MODE**:
+
+```
+bmp280.stopConversion();	// Stop conversion and return to SLEEP_MODE
+```
+---
+<a name="results_acquisition"></a>
+### __Results Acquisition__
+
+The BMP280 barometer library acquires temperature in degrees celsius (**°C**), pressure in hectoPascals/millibar (**hPa**) and altitude in metres (**m**). The acquisition functions scan the BMP280's status register and return 1 if the barometer results are ready and have been successfully read, 0 if they are not; this allows for non-blocking code implementation. The temperature, pressure and altitude results themselves are _float_ variables by passed reference to the function and are updated upon a successful read. 
+
+Here are the results acquisition functions:
+
+```
+bmp280.getMeasurements(temperature, pressure, altitude);	// Acquire temperature, pressue and altitude measurements
+```
+
+```
+bmp280.getTempPres(temperature, pressure);	// Acquire both the temperature and pressure
+```
+
+```
+bmp280.getTemperature(temperature);	// Acquire the temperature only
+```
+
+```
+bmp280.getPressure(pressure);	// Acquire the pressure only, (also calculates temperature, but doesn't return it)
+```
+
+```
+bmp280.getAltitude(altitude);	// Acquire the altitude only
+```
+
+However, these function only operate correctly and efficiently if your Arduino sketch's loop() time is fast enough (<35ms). If your loop() time is slow then these functions are unable to poll the BMP280's status register quickly enough. In this case, it is possible to simply read the barometer's latest results without checking the status register with the following functions:
+
+```
+bmp280.getCurrentMeasurements(temperature, pressure, altitude);	// Acquire the current temperature, pressue and altitude measurements
+```
+
+```
+bmp280.getCurrentTempPres(temperature, pressure);	// Acquire both the current temperature and pressure
+```
+
+```
+bmp280.getCurrentTemperature(temperature);	// Acquire the current temperature only
+```
+
+```
+bmp280.getCurrentPressure(pressure);	// Acquire the currentpressure only, (also calculates temperature, but doesn't return it)
+```
+
+```
+bmp280.getCurrentAltitude(altitude);	// Acquire the current altitude only
+```
+---
+<a name="code_implementation"></a>
+### __Code Implementation__
+
+Here is an example sketch of how to use the BMP280 library for non-blocking I2C operation, default configuration with continuous conversion in NORMAL_MODE, but with a standby sampling time of 1 second:
+
+```
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_1000MS);     // Set the standby time to 1s
+  bmp280.startNormalConversion();                 // Start NORMAL conversion mode
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F(m"));
+  }
+}
+```
+
+A second sketch example for I2C operation, default configuration in FORCED conversion mode:
+
+```
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+}
+
+void loop() 
+{
+  bmp280.startForcedConversion();                 // Start a forced conversion (if in SLEEP_MODE)
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));
+  }
+}
+```
+
+The sketches for SPI operation are identical except that the line:
+
+```
+BMP280_DEV bmp280;	// Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+```
+
+...should be replaced with the line:
+
+```
+BMP280_DEV bmp280(10);	// Instantiate (create) a BMP280_DEV object and set-up for SPI operation with chip select on D10
+```
+
+For more details see code examples provided in the _.../examples/..._ directory.
+
+---
+<a name="example_code"></a>
+## __Example Code__
+
+- __BMP280_I2C_Normal.ino__ : I2C Interface, Normal Mode, Standard I2C Address (0x77)
+
+- __BMP280_I2C_Alt_Normal.ino__ : 2C Interface, Normal Mode, Alternative I2C Address (0x76)
+
+- __BMP280_I2C_Forced.ino__ : I2C Interface, Forced Mode, Standard I2C Address (0x77)
+
+- __BMP280_SPI_Normal.ino__ : SPI Interface, Normal Mode
+
+- __BMP280_SPI_Forced.ino__ : SPI Interface, Forced Mode
+
+- __BMP280_ESP32_HSPI_Normal.ino__ : ESP32 HSPI Interface, Normal Mode
+
+- __BMP280_SPI_Normal_Multiple.ino__ : SPI Interface, Normal Mode, Multiple BMP280 Devices
+
+- __BMP280_ESP8266_I2C_Normal_DefinedPins.ino__ : ESP8266 I2C Interface, Normal Mode, User-Defined Pins
+
+- __BMP280_ESP32_I2C_Normal_DefinedPins.ino__ : ESP32 I2C Interface, Normal Mode, User-Defined Pins

+ 45 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_HSPI_Normal/BMP280_ESP32_HSPI_Normal.ino

@@ -0,0 +1,45 @@
+//////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP32 HSPI Communications, Default Configuration, Normal Conversion
+//////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                            // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+//BMP280_DEV bmp280(21);                            // Create BMP280_DEV object and set-up for VSPI operation, SCK 5, MOSI 18, MISO 19, SS 21
+SPIClass SPI1(HSPI);                              // Create (instantiate) the SPI1 object for HSPI operation
+BMP280_DEV bmp280(21, HSPI, SPI1);                // Create BMP280_DEV object and set-up for HSPI operation, SCK 14, MOSI 13, MISO 27, SS 21
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_1000MS);     // Set the standby time to 1 second (1000ms)
+  bmp280.startNormalConversion();                 // Start NORMAL continuous conversion
+  
+  xTaskCreatePinnedToCore(                        // Kick-off "TaskOne" pinned to core 1
+    taskOne,
+    "TaskOne",
+    10000,
+    NULL,
+    1,
+    NULL,
+    1);
+}
+
+void taskOne(void* parameter)
+{
+  while(true)
+  {
+    if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+    {
+      Serial.print(temperature);                    // Display the results    
+      Serial.print(F("*C   "));
+      Serial.print(pressure);    
+      Serial.print(F("hPa   "));
+      Serial.print(altitude);
+      Serial.println(F("m"));  
+    }
+  }
+}
+
+void loop() { delay(1000); }                        // Add 1 second delay

+ 29 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_I2C_Normal_DefinedPins/BMP280_ESP32_I2C_Normal_DefinedPins.ino

@@ -0,0 +1,29 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP32, I2C Communications, Default Configuration, Normal Conversion, User-Defined Pins
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(A6, A7);                        // Instantiate (create) a BMP280 object and set-up for I2C operation on pins SDA: A6, SCL: A7
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 29 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP8266_I2C_Normal_DefinedPins/BMP280_ESP8266_I2C_Normal_DefinedPins.ino

@@ -0,0 +1,29 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP8266, I2C Communications, Default Configuration, Normal Conversion, User-Defined Pins
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(6, 7);                          // Instantiate (create) a BMP280 object and set-up for I2C operation on pins SDA: 6, SCL: 7
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Alt_Normal/BMP280_I2C_Alt_Normal.ino

@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications (Alternative Address), Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin(BMP280_I2C_ALT_ADDR);              // Default initialisation with alternative I2C address (0x76), place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 31 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Forced/BMP280_I2C_Forced.ino

@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4 
+}
+
+void loop() 
+{
+  bmp280.startForcedConversion();                 // Start BMP280 forced conversion (if we're in SLEEP_MODE)
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Normal/BMP280_I2C_Normal.ino

@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 31 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Forced/BMP280_SPI_Forced.ino

@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Forced Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4 
+}
+
+void loop() 
+{
+  bmp280.startForcedConversion();                 // Start BMP280 forced conversion (if we're in SLEEP_MODE)
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal/BMP280_SPI_Normal.ino

@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Normal Conversion
+///////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 44 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal_Multiple/BMP280_SPI_Normal_Multiple.ino

@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Normal Conversion, Mulitple Devices
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                             // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;              // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280_1(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+BMP280_DEV bmp280_2(9);                             // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D9
+
+void setup() 
+{
+  Serial.begin(115200);                             // Initialise the serial port
+  bmp280_1.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280_1.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280_1.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE 
+  bmp280_2.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280_2.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280_2.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280_1.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(F("BMP280_1 "));                                   // Display the results   
+    Serial.print(temperature);                       
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+  if (bmp280_2.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(F("BMP280_2 "));                                   // Display the results
+    Serial.print(temperature);                          
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 64 - 0
.pio/libdeps/uno/BMP280_DEV/keywords.txt

@@ -0,0 +1,64 @@
+#######################################
+# Syntax Coloring Map For The BMP280
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+BMP280	KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+begin	KEYWORD2
+reset	KEYWORD2
+startNormalConversion	KEYWORD2
+startForcedConversion	KEYWORD2
+stopConversion	KEYWORD2
+setPresOversampling	KEYWORD2
+setTempOversampling	KEYWORD2
+setIIRFilter	KEYWORD2
+setTimeStandby	KEYWORD2
+setSeaLevelPressure	KEYWORD2
+getCurrentTemperature	KEYWORD2
+getTemperature	KEYWORD2
+getCurrentPressure	KEYWORD2
+getPressure	KEYWORD2
+getCurrentTempPres	KEYWORD2
+getTempPres	KEYWORD2
+getCurrentAltitude	KEYWORD2
+getAltitude	KEYWORD2
+getCurrentMeasurements	KEYWORD2
+getMeasurements	KEYWORD2
+setClock	KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################
+
+BMP280_I2C_ADDR	LITERAL1
+BMP280_I2C_ALT_ADDR	LITERAL1
+
+OVERSAMPLING_SKIP	LITERAL1
+OVERSAMPLING_X1	LITERAL1
+OVERSAMPLING_X2	LITERAL1
+OVERSAMPLING_X4	LITERAL1
+OVERSAMPLING_X8	LITERAL1
+OVERSAMPLING_X16	LITERAL1
+
+IIR_FILTER_OFF	LITERAL1
+IIR_FILTER_2	LITERAL1
+IIR_FILTER_4	LITERAL1
+IIR_FILTER_8	LITERAL1
+IIR_FILTER_16	LITERAL1
+
+TIME_STANDBY_05MS	LITERAL1
+TIME_STANDBY_62MS	LITERAL1
+TIME_STANDBY_125MS	LITERAL1
+TIME_STANDBY_250MS	LITERAL1
+TIME_STANDBY_500MS	LITERAL1
+TIME_STANDBY_1000MS	LITERAL1
+TIME_STANDBY_2000MS	LITERAL1
+TIME_STANDBY_4000MS	LITERAL1

+ 9 - 0
.pio/libdeps/uno/BMP280_DEV/library.properties

@@ -0,0 +1,9 @@
+name=BMP280_DEV
+version=1.0.18
+author=Martin Lindupp
+maintainer=Martin Lindupp
+sentence=An Arduino compatible, non-blocking, I2C/SPI library for the Bosch BMP280 barometer. 
+paragraph=This library can operate the BMP280 in either NORMAL or FORCED modes. NORMAL mode automatically samples at the device sample rate. 
+category=Sensors
+url=https://github.com/MartinL1/BMP280_DEV
+architectures=*

+ 1 - 0
.pio/libdeps/uno/MPU9250/.piopm

@@ -0,0 +1 @@
+{"type": "library", "name": "MPU9250", "version": "0.2.3", "spec": {"owner": "hideakitai", "id": 5639, "name": "MPU9250", "requirements": null, "url": null}}

+ 21 - 0
.pio/libdeps/uno/MPU9250/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Hideaki Tai
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 954 - 0
.pio/libdeps/uno/MPU9250/MPU9250.h

@@ -0,0 +1,954 @@
+#pragma once
+#ifndef MPU9250_H
+#define MPU9250_H
+
+#include <Wire.h>
+
+#include "MPU9250/MPU9250RegisterMap.h"
+#include "MPU9250/QuaternionFilter.h"
+
+enum class AFS { A2G,
+                 A4G,
+                 A8G,
+                 A16G };
+enum class GFS { G250DPS,
+                 G500DPS,
+                 G1000DPS,
+                 G2000DPS };
+enum class MFS { M14BITS,
+                 M16BITS };
+static constexpr uint8_t MPU9250_WHOAMI_DEFAULT_VALUE{0x71};
+static constexpr uint8_t MPU9255_WHOAMI_DEFAULT_VALUE{0x73};
+
+template <typename WireType, uint8_t WHO_AM_I, AFS AFSSEL = AFS::A16G, GFS GFSSEL = GFS::G2000DPS, MFS MFSSEL = MFS::M16BITS>
+class MPU9250_ {
+    static constexpr uint8_t MPU9250_DEFAULT_ADDRESS{0x68};  // Device address when ADO = 0
+    static constexpr uint8_t AK8963_ADDRESS{0x0C};           //  Address of magnetometer
+    uint8_t MPU9250_ADDRESS{MPU9250_DEFAULT_ADDRESS};
+
+    const uint8_t AK8963_WHOAMI_DEFAULT_VALUE{0x48};
+
+    // Set initial input parameters
+    // const uint8_t Mmode {0x02};        // 2 for 8 Hz, 6 for 100 Hz continuous magnetometer data read
+    const uint8_t Mmode{0x06};    // 2 for 8 Hz, 6 for 100 Hz continuous magnetometer data read
+    const float aRes{getAres()};  // scale resolutions per LSB for the sensors
+    const float gRes{getGres()};  // scale resolutions per LSB for the sensors
+    const float mRes{getMres()};  // scale resolutions per LSB for the sensors
+
+    float magCalibration[3] = {0, 0, 0};  // factory mag calibration
+    float magBias[3] = {0, 0, 0};
+    float magScale[3] = {1.0, 1.0, 1.0};  // Bias corrections for gyro and accelerometer
+
+    float gyroBias[3] = {0, 0, 0};   // bias corrections
+    float accelBias[3] = {0, 0, 0};  // bias corrections
+
+    int16_t tempCount;        // temperature raw count output
+    float temperature;        // Stores the real internal chip temperature in degrees Celsius
+    float SelfTestResult[6];  // holds results of gyro and accelerometer self test
+
+    float a[3], g[3], m[3];
+    float q[4] = {1.0f, 0.0f, 0.0f, 0.0f};  // vector to hold quaternion
+    float pitch, yaw, roll;
+    float a12, a22, a31, a32, a33;  // rotation matrix coefficients for Euler angles and gravity components
+    float lin_ax, lin_ay, lin_az;   // linear acceleration (acceleration with gravity component subtracted)
+
+    QuaternionFilter qFilter;
+
+    float magnetic_declination = 4.6;  // Köthen, 26th November
+
+    bool b_verbose{false};
+
+public:
+    MPU9250_() : aRes(getAres()), gRes(getGres()), mRes(getMres()) {}
+
+    bool setup(const uint8_t addr, WireType& w = Wire) {
+        // addr should be valid for MPU
+        if ((addr < MPU9250_DEFAULT_ADDRESS) || (addr > MPU9250_DEFAULT_ADDRESS + 7)) {
+            Serial.print("I2C address 0x");
+            Serial.print(addr, HEX);
+            Serial.println(" is not valid for MPU. Please check your I2C address.");
+            return false;
+        }
+
+        wire = &w;
+        MPU9250_ADDRESS = addr;
+
+        if (isConnectedMPU9250()) {
+            if (b_verbose)
+                Serial.println("MPU9250 is online...");
+
+            initMPU9250();
+
+            if (isConnectedAK8963())
+                initAK8963(magCalibration);
+            else {
+                if (b_verbose)
+                    Serial.println("Could not connect to AK8963");
+                return false;
+            }
+        } else {
+            if (b_verbose)
+                Serial.println("Could not connect to MPU9250");
+            return false;
+        }
+        return true;
+    }
+
+    void verbose(const bool b) { b_verbose = b; }
+
+    void calibrateAccelGyro() {
+        calibrateMPU9250(gyroBias, accelBias);
+    }
+
+    void calibrateMag() {
+        magcalMPU9250(magBias, magScale);
+    }
+
+    bool isConnectedMPU9250() {
+        byte c = readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
+        if (b_verbose) {
+            Serial.print("MPU9250 WHO AM I = ");
+            Serial.println(c, HEX);
+        }
+        return (c == WHO_AM_I);
+    }
+
+    bool isConnectedAK8963() {
+        byte c = readByte(AK8963_ADDRESS, AK8963_WHO_AM_I);
+        if (b_verbose) {
+            Serial.print("AK8963  WHO AM I = ");
+            Serial.println(c, HEX);
+        }
+        return (c == AK8963_WHOAMI_DEFAULT_VALUE);
+    }
+
+    bool available() {
+        return (readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01);
+    }
+
+    bool update() {
+        if (!available()) return false;
+
+        updateAccelGyro();
+        updateMag();
+
+        // Madgwick function needs to be fed North, East, and Down direction like
+        // (AN, AE, AD, GN, GE, GD, MN, ME, MD)
+        // Accel and Gyro direction is Right-Hand, X-Forward, Z-Up
+        // Magneto direction is Right-Hand, Y-Forward, Z-Down
+        // So to adopt to the general Aircraft coordinate system (Right-Hand, X-Forward, Z-Down),
+        // we need to feed (ax, -ay, -az, gx, -gy, -gz, my, -mx, mz)
+        // but we pass (-ax, ay, az, gx, -gy, -gz, my, -mx, mz)
+        // because gravity is by convention positive down, we need to ivnert the accel data
+
+        // get quaternion based on aircraft coordinate (Right-Hand, X-Forward, Z-Down)
+        // acc[mg], gyro[deg/s], mag [mG]
+        // gyro will be convert from [deg/s] to [rad/s] inside of this function
+        qFilter.update(-a[0], a[1], a[2], g[0], -g[1], -g[2], m[1], -m[0], m[2], q);
+
+        if (!b_ahrs) {
+            tempCount = readTempData();                        // Read the adc values
+            temperature = ((float)tempCount) / 333.87 + 21.0;  // Temperature in degrees Centigrade
+        } else {
+            // Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation.
+            // In this coordinate system, the positive z-axis is down toward Earth.
+            // Yaw is the angle between Sensor x-axis and Earth magnetic North (or true North if corrected for local declination, looking down on the sensor positive yaw is counterclockwise.
+            // Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative.
+            // Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll.
+            // These arise from the definition of the homogeneous rotation matrix constructed from quaternions.
+            // Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be
+            // applied in the correct order which for this configuration is yaw, pitch, and then roll.
+            // For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links.
+            updateRPY();
+        }
+
+        return true;
+    }
+
+    // TODO: more efficient getter, const refrerence of struct??
+    float getRoll() const { return roll; }
+    float getPitch() const { return pitch; }
+    float getYaw() const { return yaw; }
+
+    float getQuaternion(const uint8_t i) const { return (i < 4) ? q[i] : 0.f; }
+
+    float getQuaternionX() const { return q[0]; }
+    float getQuaternionY() const { return q[1]; }
+    float getQuaternionZ() const { return q[2]; }
+    float getQuaternionW() const { return q[3]; }
+
+    float getAcc(const uint8_t i) const { return (i < 3) ? a[i] : 0.f; }
+    float getGyro(const uint8_t i) const { return (i < 3) ? g[i] : 0.f; }
+    float getMag(const uint8_t i) const { return (i < 3) ? m[i] : 0.f; }
+
+    float getAccX() const { return a[0]; }
+    float getAccY() const { return a[1]; }
+    float getAccZ() const { return a[2]; }
+    float getGyroX() const { return g[0]; }
+    float getGyroY() const { return g[1]; }
+    float getGyroZ() const { return g[2]; }
+    float getMagX() const { return m[0]; }
+    float getMagY() const { return m[1]; }
+    float getMagZ() const { return m[2]; }
+
+    float getAccBias(const uint8_t i) const { return (i < 3) ? accelBias[i] : 0.f; }
+    float getGyroBias(const uint8_t i) const { return (i < 3) ? gyroBias[i] : 0.f; }
+    float getMagBias(const uint8_t i) const { return (i < 3) ? magBias[i] : 0.f; }
+    float getMagScale(const uint8_t i) const { return (i < 3) ? magScale[i] : 0.f; }
+
+    float getAccBiasX() const { return accelBias[0]; }
+    float getAccBiasY() const { return accelBias[1]; }
+    float getAccBiasZ() const { return accelBias[2]; }
+    float getGyroBiasX() const { return gyroBias[0]; }
+    float getGyroBiasY() const { return gyroBias[1]; }
+    float getGyroBiasZ() const { return gyroBias[2]; }
+    float getMagBiasX() const { return magBias[0]; }
+    float getMagBiasY() const { return magBias[1]; }
+    float getMagBiasZ() const { return magBias[2]; }
+    float getMagScaleX() const { return magScale[0]; }
+    float getMagScaleY() const { return magScale[1]; }
+    float getMagScaleZ() const { return magScale[2]; }
+
+    float getTemperature() const { return temperature; }
+
+    void setAccBias(const uint8_t i, const float v) {
+        if (i < 3) accelBias[i] = v;
+    }
+    void setGyroBias(const uint8_t i, const float v) {
+        if (i < 3) gyroBias[i] = v;
+    }
+    void setMagBias(const uint8_t i, const float v) {
+        if (i < 3) magBias[i] = v;
+    }
+    void setMagScale(const uint8_t i, const float v) {
+        if (i < 3) magScale[i] = v;
+    }
+
+    void setAccBiasX(const float v) { accelBias[0] = v; }
+    void setAccBiasY(const float v) { accelBias[1] = v; }
+    void setAccBiasZ(const float v) { accelBias[2] = v; }
+    void setGyroBiasX(const float v) { gyroBias[0] = v; }
+    void setGyroBiasY(const float v) { gyroBias[1] = v; }
+    void setGyroBiasZ(const float v) { gyroBias[2] = v; }
+    void setMagBiasX(const float v) { magBias[0] = v; }
+    void setMagBiasY(const float v) { magBias[1] = v; }
+    void setMagBiasZ(const float v) { magBias[2] = v; }
+    void setMagScaleX(const float v) { magScale[0] = v; }
+    void setMagScaleY(const float v) { magScale[1] = v; }
+    void setMagScaleZ(const float v) { magScale[2] = v; }
+
+    void setMagneticDeclination(const float d) { magnetic_declination = d; }
+
+    void print() const {
+        printRawData();
+        printRollPitchYaw();
+        printCalibration();
+    }
+
+    void printRawData() const {
+        // Print acceleration values in milligs!
+        Serial.print("ax = ");
+        Serial.print((int)1000 * a[0]);
+        Serial.print(" ay = ");
+        Serial.print((int)1000 * a[1]);
+        Serial.print(" az = ");
+        Serial.print((int)1000 * a[2]);
+        Serial.println(" mg");
+        // Print gyro values in degree/sec
+        Serial.print("gx = ");
+        Serial.print(g[0], 2);
+        Serial.print(" gy = ");
+        Serial.print(g[1], 2);
+        Serial.print(" gz = ");
+        Serial.print(g[2], 2);
+        Serial.println(" deg/s");
+        // Print mag values in degree/sec
+        Serial.print("mx = ");
+        Serial.print((int)m[0]);
+        Serial.print(" my = ");
+        Serial.print((int)m[1]);
+        Serial.print(" mz = ");
+        Serial.print((int)m[2]);
+        Serial.println(" mG");
+
+        Serial.print("q0 = ");
+        Serial.print(q[0]);
+        Serial.print(" qx = ");
+        Serial.print(q[1]);
+        Serial.print(" qy = ");
+        Serial.print(q[2]);
+        Serial.print(" qz = ");
+        Serial.println(q[3]);
+    }
+
+    void printRollPitchYaw() const {
+        Serial.print("Yaw, Pitch, Roll: ");
+        Serial.print(yaw, 2);
+        Serial.print(", ");
+        Serial.print(pitch, 2);
+        Serial.print(", ");
+        Serial.println(roll, 2);
+    }
+
+    void printCalibration() const {
+        Serial.println("< calibration parameters >");
+        Serial.println("accel bias [g]: ");
+        Serial.print(accelBias[0] * 1000.f);
+        Serial.print(", ");
+        Serial.print(accelBias[1] * 1000.f);
+        Serial.print(", ");
+        Serial.print(accelBias[2] * 1000.f);
+        Serial.println();
+        Serial.println("gyro bias [deg/s]: ");
+        Serial.print(gyroBias[0]);
+        Serial.print(", ");
+        Serial.print(gyroBias[1]);
+        Serial.print(", ");
+        Serial.print(gyroBias[2]);
+        Serial.println();
+        Serial.println("mag bias [mG]: ");
+        Serial.print(magBias[0]);
+        Serial.print(", ");
+        Serial.print(magBias[1]);
+        Serial.print(", ");
+        Serial.print(magBias[2]);
+        Serial.println();
+        Serial.println("mag scale []: ");
+        Serial.print(magScale[0]);
+        Serial.print(", ");
+        Serial.print(magScale[1]);
+        Serial.print(", ");
+        Serial.print(magScale[2]);
+        Serial.println();
+    }
+
+private:
+    float getAres() const {
+        switch (AFSSEL) {
+            // Possible accelerometer scales (and their register bit settings) are:
+            // 2 Gs (00), 4 Gs (01), 8 Gs (10), and 16 Gs  (11).
+            // Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value:
+            case AFS::A2G:
+                return 2.0 / 32768.0;
+            case AFS::A4G:
+                return 4.0 / 32768.0;
+            case AFS::A8G:
+                return 8.0 / 32768.0;
+            case AFS::A16G:
+                return 16.0 / 32768.0;
+        }
+    }
+
+    float getGres() const {
+        switch (GFSSEL) {
+            // Possible gyro scales (and their register bit settings) are:
+            // 250 DPS (00), 500 DPS (01), 1000 DPS (10), and 2000 DPS  (11).
+            // Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value:
+            case GFS::G250DPS:
+                return 250.0 / 32768.0;
+            case GFS::G500DPS:
+                return 500.0 / 32768.0;
+            case GFS::G1000DPS:
+                return 1000.0 / 32768.0;
+            case GFS::G2000DPS:
+                return 2000.0 / 32768.0;
+        }
+    }
+
+    float getMres() const {
+        switch (MFSSEL) {
+            // Possible magnetometer scales (and their register bit settings) are:
+            // 14 bit resolution (0) and 16 bit resolution (1)
+            // Proper scale to return milliGauss
+            case MFS::M14BITS:
+                return 10. * 4912. / 8190.0;
+            case MFS::M16BITS:
+                return 10. * 4912. / 32760.0;
+        }
+    }
+
+    void updateAccelGyro() {
+        int16_t MPU9250Data[7];        // used to read all 14 bytes at once from the MPU9250 accel/gyro
+        readMPU9250Data(MPU9250Data);  // INT cleared on any read
+
+        // Now we'll calculate the accleration value into actual g's
+        a[0] = (float)MPU9250Data[0] * aRes - accelBias[0];  // get actual g value, this depends on scale being set
+        a[1] = (float)MPU9250Data[1] * aRes - accelBias[1];
+        a[2] = (float)MPU9250Data[2] * aRes - accelBias[2];
+
+        tempCount = MPU9250Data[3];                        // Read the adc values
+        temperature = ((float)tempCount) / 333.87 + 21.0;  // Temperature in degrees Centigrade
+
+        // Calculate the gyro value into actual degrees per second
+        g[0] = (float)MPU9250Data[4] * gRes - gyroBias[0];  // get actual gyro value, this depends on scale being set
+        g[1] = (float)MPU9250Data[5] * gRes - gyroBias[1];
+        g[2] = (float)MPU9250Data[6] * gRes - gyroBias[2];
+    }
+
+    void readMPU9250Data(int16_t* destination) {
+        uint8_t rawData[14];                                        // x/y/z accel register data stored here
+        readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 14, &rawData[0]);  // Read the 14 raw data registers into data array
+        destination[0] = ((int16_t)rawData[0] << 8) | rawData[1];   // Turn the MSB and LSB into a signed 16-bit value
+        destination[1] = ((int16_t)rawData[2] << 8) | rawData[3];
+        destination[2] = ((int16_t)rawData[4] << 8) | rawData[5];
+        destination[3] = ((int16_t)rawData[6] << 8) | rawData[7];
+        destination[4] = ((int16_t)rawData[8] << 8) | rawData[9];
+        destination[5] = ((int16_t)rawData[10] << 8) | rawData[11];
+        destination[6] = ((int16_t)rawData[12] << 8) | rawData[13];
+    }
+
+    void updateMag() {
+        int16_t magCount[3] = {0, 0, 0};  // Stores the 16-bit signed magnetometer sensor output
+        readMagData(magCount);            // Read the x/y/z adc values
+        // getMres();
+
+        // Calculate the magnetometer values in milliGauss
+        // Include factory calibration per data sheet and user environmental corrections
+        m[0] = (float)(magCount[0] * mRes * magCalibration[0] - magBias[0]) * magScale[0];  // get actual magnetometer value, this depends on scale being set
+        m[1] = (float)(magCount[1] * mRes * magCalibration[1] - magBias[1]) * magScale[1];
+        m[2] = (float)(magCount[2] * mRes * magCalibration[2] - magBias[2]) * magScale[2];
+    }
+
+    void readMagData(int16_t* destination) {
+        uint8_t rawData[7];                                                // x/y/z gyro register data, ST2 register stored here, must read ST2 at end of data acquisition
+        if (readByte(AK8963_ADDRESS, AK8963_ST1) & 0x01) {                 // wait for magnetometer data ready bit to be set
+            readBytes(AK8963_ADDRESS, AK8963_XOUT_L, 7, &rawData[0]);      // Read the six raw data and ST2 registers sequentially into data array
+            uint8_t c = rawData[6];                                        // End data read by reading ST2 register
+            if (!(c & 0x08)) {                                             // Check if magnetic sensor overflow set, if not then report data
+                destination[0] = ((int16_t)rawData[1] << 8) | rawData[0];  // Turn the MSB and LSB into a signed 16-bit value
+                destination[1] = ((int16_t)rawData[3] << 8) | rawData[2];  // Data stored as little Endian
+                destination[2] = ((int16_t)rawData[5] << 8) | rawData[4];
+            }
+        }
+    }
+
+    void updateRPY() {
+        a12 = 2.0f * (q[1] * q[2] + q[0] * q[3]);
+        a22 = q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3];
+        a31 = 2.0f * (q[0] * q[1] + q[2] * q[3]);
+        a32 = 2.0f * (q[1] * q[3] - q[0] * q[2]);
+        a33 = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
+        pitch = -asinf(a32);
+        roll = atan2f(a31, a33);
+        yaw = atan2f(a12, a22);
+        pitch *= 180.0f / PI;
+        roll *= 180.0f / PI;
+        yaw *= 180.0f / PI;
+        yaw += magnetic_declination;
+        if (yaw >= +180.f)
+            yaw -= 360.f;
+        else if (yaw < -180.f)
+            yaw += 360.f;
+
+        lin_ax = a[0] + a31;
+        lin_ay = a[1] + a32;
+        lin_az = a[2] - a33;
+    }
+
+    int16_t readTempData() {
+        uint8_t rawData[2];                                      // x/y/z gyro register data stored here
+        readBytes(MPU9250_ADDRESS, TEMP_OUT_H, 2, &rawData[0]);  // Read the two raw data registers sequentially into data array
+        return ((int16_t)rawData[0] << 8) | rawData[1];          // Turn the MSB and LSB into a 16-bit value
+    }
+
+    void initAK8963(float* destination) {
+        // First extract the factory calibration for each magnetometer axis
+        uint8_t rawData[3];                            // x/y/z gyro calibration data stored here
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x00);  // Power down magnetometer
+        delay(10);
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x0F);  // Enter Fuse ROM access mode
+        delay(10);
+        readBytes(AK8963_ADDRESS, AK8963_ASAX, 3, &rawData[0]);  // Read the x-, y-, and z-axis calibration values
+        destination[0] = (float)(rawData[0] - 128) / 256. + 1.;  // Return x-axis sensitivity adjustment values, etc.
+        destination[1] = (float)(rawData[1] - 128) / 256. + 1.;
+        destination[2] = (float)(rawData[2] - 128) / 256. + 1.;
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x00);  // Power down magnetometer
+        delay(10);
+        // Configure the magnetometer for continuous read and highest resolution
+        // set Mscale bit 4 to 1 (0) to enable 16 (14) bit resolution in CNTL register,
+        // and enable continuous mode data acquisition Mmode (bits [3:0]), 0010 for 8 Hz and 0110 for 100 Hz sample rates
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, (uint8_t)MFSSEL << 4 | Mmode);  // Set magnetometer data resolution and sample ODR
+        delay(10);
+
+        if (b_verbose) {
+            Serial.println("Calibration values: ");
+            Serial.print("X-Axis sensitivity adjustment value ");
+            Serial.println(destination[0], 2);
+            Serial.print("Y-Axis sensitivity adjustment value ");
+            Serial.println(destination[1], 2);
+            Serial.print("Z-Axis sensitivity adjustment value ");
+            Serial.println(destination[2], 2);
+            Serial.print("X-Axis sensitivity offset value ");
+            Serial.println(magBias[0], 2);
+            Serial.print("Y-Axis sensitivity offset value ");
+            Serial.println(magBias[1], 2);
+            Serial.print("Z-Axis sensitivity offset value ");
+            Serial.println(magBias[2], 2);
+        }
+    }
+
+    void magcalMPU9250(float* dest1, float* dest2) {
+        uint16_t ii = 0, sample_count = 0;
+        int32_t mag_bias[3] = {0, 0, 0}, mag_scale[3] = {0, 0, 0};
+        int16_t mag_max[3] = {-32767, -32767, -32767}, mag_min[3] = {32767, 32767, 32767}, mag_temp[3] = {0, 0, 0};
+
+        if (b_verbose)
+            Serial.println("Mag Calibration: Wave device in a figure eight until done!");
+        delay(4000);
+
+        // shoot for ~fifteen seconds of mag data
+        if (Mmode == 0x02)
+            sample_count = 128;  // at 8 Hz ODR, new mag data is available every 125 ms
+        else if (Mmode == 0x06)
+            sample_count = 1500;  // at 100 Hz ODR, new mag data is available every 10 ms
+
+        for (ii = 0; ii < sample_count; ii++) {
+            readMagData(mag_temp);  // Read the mag data
+            for (int jj = 0; jj < 3; jj++) {
+                if (mag_temp[jj] > mag_max[jj]) mag_max[jj] = mag_temp[jj];
+                if (mag_temp[jj] < mag_min[jj]) mag_min[jj] = mag_temp[jj];
+            }
+            if (Mmode == 0x02) delay(135);  // at 8 Hz ODR, new mag data is available every 125 ms
+            if (Mmode == 0x06) delay(12);   // at 100 Hz ODR, new mag data is available every 10 ms
+        }
+
+        if (b_verbose) {
+            Serial.println("mag x min/max:");
+            Serial.println(mag_max[0]);
+            Serial.println(mag_min[0]);
+            Serial.println("mag y min/max:");
+            Serial.println(mag_max[1]);
+            Serial.println(mag_min[1]);
+            Serial.println("mag z min/max:");
+            Serial.println(mag_max[2]);
+            Serial.println(mag_min[2]);
+        }
+
+        // Get hard iron correction
+        mag_bias[0] = (mag_max[0] + mag_min[0]) / 2;  // get average x mag bias in counts
+        mag_bias[1] = (mag_max[1] + mag_min[1]) / 2;  // get average y mag bias in counts
+        mag_bias[2] = (mag_max[2] + mag_min[2]) / 2;  // get average z mag bias in counts
+
+        dest1[0] = (float)mag_bias[0] * mRes * magCalibration[0];  // save mag biases in G for main program
+        dest1[1] = (float)mag_bias[1] * mRes * magCalibration[1];
+        dest1[2] = (float)mag_bias[2] * mRes * magCalibration[2];
+
+        // Get soft iron correction estimate
+        mag_scale[0] = (mag_max[0] - mag_min[0]) / 2;  // get average x axis max chord length in counts
+        mag_scale[1] = (mag_max[1] - mag_min[1]) / 2;  // get average y axis max chord length in counts
+        mag_scale[2] = (mag_max[2] - mag_min[2]) / 2;  // get average z axis max chord length in counts
+
+        float avg_rad = mag_scale[0] + mag_scale[1] + mag_scale[2];
+        avg_rad /= 3.0;
+
+        dest2[0] = avg_rad / ((float)mag_scale[0]);
+        dest2[1] = avg_rad / ((float)mag_scale[1]);
+        dest2[2] = avg_rad / ((float)mag_scale[2]);
+
+        if (b_verbose) {
+            Serial.println("Mag Calibration done!");
+
+            Serial.println("AK8963 mag biases (mG)");
+            Serial.print(magBias[0]);
+            Serial.print(", ");
+            Serial.print(magBias[1]);
+            Serial.print(", ");
+            Serial.print(magBias[2]);
+            Serial.println();
+            Serial.println("AK8963 mag scale (mG)");
+            Serial.print(magScale[0]);
+            Serial.print(", ");
+            Serial.print(magScale[1]);
+            Serial.print(", ");
+            Serial.print(magScale[2]);
+            Serial.println();
+        }
+    }
+
+    void initMPU9250() {
+        // wake up device
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x00);  // Clear sleep mode bit (6), enable all sensors
+        delay(100);                                    // Wait for all registers to reset
+
+        // get stable time source
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x01);  // Auto select clock source to be PLL gyroscope reference if ready else
+        delay(200);
+
+        // Configure Gyro and Thermometer
+        // Disable FSYNC and set thermometer and gyro bandwidth to 41 and 42 Hz, respectively;
+        // minimum delay time for this setting is 5.9 ms, which means sensor fusion update rates cannot
+        // be higher than 1 / 0.0059 = 170 Hz
+        // DLPF_CFG = bits 2:0 = 011; this limits the sample rate to 1000 Hz for both
+        // With the MPU9250, it is possible to get gyro sample rates of 32 kHz (!), 8 kHz, or 1 kHz
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x03);
+
+        // Set sample rate = gyroscope output rate/(1 + SMPLRT_DIV)
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x04);  // Use a 200 Hz rate; a rate consistent with the filter update rate
+                                                       // determined inset in CONFIG above
+
+        // Set gyroscope full scale range
+        // Range selects FS_SEL and GFS_SEL are 0 - 3, so 2-bit values are left-shifted into positions 4:3
+        uint8_t c = readByte(MPU9250_ADDRESS, GYRO_CONFIG);  // get current GYRO_CONFIG register value
+        // c = c & ~0xE0; // Clear self-test bits [7:5]
+        c = c & ~0x03;                 // Clear Fchoice bits [1:0]
+        c = c & ~0x18;                 // Clear GFS bits [4:3]
+        c = c | (uint8_t)GFSSEL << 3;  // Set full scale range for the gyro
+        // c =| 0x00; // Set Fchoice for the gyro to 11 by writing its inverse to bits 1:0 of GYRO_CONFIG
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, c);  // Write new GYRO_CONFIG value to register
+
+        // Set accelerometer full-scale range configuration
+        c = readByte(MPU9250_ADDRESS, ACCEL_CONFIG);  // get current ACCEL_CONFIG register value
+        // c = c & ~0xE0; // Clear self-test bits [7:5]
+        c = c & ~0x18;                                // Clear AFS bits [4:3]
+        c = c | (uint8_t)AFSSEL << 3;                 // Set full scale range for the accelerometer
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, c);  // Write new ACCEL_CONFIG register value
+
+        // Set accelerometer sample rate configuration
+        // It is possible to get a 4 kHz sample rate from the accelerometer by choosing 1 for
+        // accel_fchoice_b bit [3]; in this case the bandwidth is 1.13 kHz
+        c = readByte(MPU9250_ADDRESS, ACCEL_CONFIG2);  // get current ACCEL_CONFIG2 register value
+        c = c & ~0x0F;                                 // Clear accel_fchoice_b (bit 3) and A_DLPFG (bits [2:0])
+        c = c | 0x03;                                  // Set accelerometer rate to 1 kHz and bandwidth to 41 Hz
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG2, c);  // Write new ACCEL_CONFIG2 register value
+
+        // The accelerometer, gyro, and thermometer are set to 1 kHz sample rates,
+        // but all these rates are further reduced by a factor of 5 to 200 Hz because of the SMPLRT_DIV setting
+
+        // Configure Interrupts and Bypass Enable
+        // Set interrupt pin active high, push-pull, hold interrupt pin level HIGH until interrupt cleared,
+        // clear on read of INT_STATUS, and enable I2C_BYPASS_EN so additional chips
+        // can join the I2C bus and all can be controlled by the Arduino as master
+        writeByte(MPU9250_ADDRESS, INT_PIN_CFG, 0x22);
+        writeByte(MPU9250_ADDRESS, INT_ENABLE, 0x01);  // Enable data ready (bit 0) interrupt
+        delay(100);
+    }
+
+    // Function which accumulates gyro and accelerometer data after device initialization. It calculates the average
+    // of the at-rest readings and then loads the resulting offsets into accelerometer and gyro bias registers.
+    void calibrateMPU9250(float* dest1, float* dest2) {
+        uint8_t data[12];  // data array to hold accelerometer and gyro x, y, z, data
+        uint16_t ii, packet_count, fifo_count;
+        int32_t gyro_bias[3] = {0, 0, 0}, accel_bias[3] = {0, 0, 0};
+
+        // reset device
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x80);  // Write a one to bit 7 reset bit; toggle reset device
+        delay(100);
+
+        // get stable time source; Auto select clock source to be PLL gyroscope reference if ready
+        // else use the internal oscillator, bits 2:0 = 001
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x01);
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_2, 0x00);
+        delay(200);
+
+        // Configure device for bias calculation
+        writeByte(MPU9250_ADDRESS, INT_ENABLE, 0x00);    // Disable all interrupts
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x00);       // Disable FIFO
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x00);    // Turn on internal clock source
+        writeByte(MPU9250_ADDRESS, I2C_MST_CTRL, 0x00);  // Disable I2C master
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x00);     // Disable FIFO and I2C master modes
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x0C);     // Reset FIFO and DMP
+        delay(15);
+
+        // Configure MPU6050 gyro and accelerometer for bias calculation
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x01);    // Set low-pass filter to 188 Hz
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x00);    // Set sample rate to 1 kHz
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0x00);   // Set gyro full-scale to 250 degrees per second, maximum sensitivity
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0x00);  // Set accelerometer full-scale to 2 g, maximum sensitivity
+
+        uint16_t gyrosensitivity = 131;     // = 131 LSB/degrees/sec
+        uint16_t accelsensitivity = 16384;  // = 16384 LSB/g
+
+        // Configure FIFO to capture accelerometer and gyro data for bias calculation
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x40);  // Enable FIFO
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x78);    // Enable gyro and accelerometer sensors for FIFO  (max size 512 bytes in MPU-9150)
+        delay(40);                                    // accumulate 40 samples in 40 milliseconds = 480 bytes
+
+        // At end of sample accumulation, turn off FIFO sensor read
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x00);             // Disable gyro and accelerometer sensors for FIFO
+        readBytes(MPU9250_ADDRESS, FIFO_COUNTH, 2, &data[0]);  // read FIFO sample count
+        fifo_count = ((uint16_t)data[0] << 8) | data[1];
+        packet_count = fifo_count / 12;  // How many sets of full gyro and accelerometer data for averaging
+
+        for (ii = 0; ii < packet_count; ii++) {
+            int16_t accel_temp[3] = {0, 0, 0}, gyro_temp[3] = {0, 0, 0};
+            readBytes(MPU9250_ADDRESS, FIFO_R_W, 12, &data[0]);            // read data for averaging
+            accel_temp[0] = (int16_t)(((int16_t)data[0] << 8) | data[1]);  // Form signed 16-bit integer for each sample in FIFO
+            accel_temp[1] = (int16_t)(((int16_t)data[2] << 8) | data[3]);
+            accel_temp[2] = (int16_t)(((int16_t)data[4] << 8) | data[5]);
+            gyro_temp[0] = (int16_t)(((int16_t)data[6] << 8) | data[7]);
+            gyro_temp[1] = (int16_t)(((int16_t)data[8] << 8) | data[9]);
+            gyro_temp[2] = (int16_t)(((int16_t)data[10] << 8) | data[11]);
+
+            accel_bias[0] += (int32_t)accel_temp[0];  // Sum individual signed 16-bit biases to get accumulated signed 32-bit biases
+            accel_bias[1] += (int32_t)accel_temp[1];
+            accel_bias[2] += (int32_t)accel_temp[2];
+            gyro_bias[0] += (int32_t)gyro_temp[0];
+            gyro_bias[1] += (int32_t)gyro_temp[1];
+            gyro_bias[2] += (int32_t)gyro_temp[2];
+        }
+        accel_bias[0] /= (int32_t)packet_count;  // Normalize sums to get average count biases
+        accel_bias[1] /= (int32_t)packet_count;
+        accel_bias[2] /= (int32_t)packet_count;
+        gyro_bias[0] /= (int32_t)packet_count;
+        gyro_bias[1] /= (int32_t)packet_count;
+        gyro_bias[2] /= (int32_t)packet_count;
+
+        if (accel_bias[2] > 0L) {
+            accel_bias[2] -= (int32_t)accelsensitivity;
+        }  // Remove gravity from the z-axis accelerometer bias calculation
+        else {
+            accel_bias[2] += (int32_t)accelsensitivity;
+        }
+
+        // Construct the gyro biases for push to the hardware gyro bias registers, which are reset to zero upon device startup
+        data[0] = (-gyro_bias[0] / 4 >> 8) & 0xFF;  // Divide by 4 to get 32.9 LSB per deg/s to conform to expected bias input format
+        data[1] = (-gyro_bias[0] / 4) & 0xFF;       // Biases are additive, so change sign on calculated average gyro biases
+        data[2] = (-gyro_bias[1] / 4 >> 8) & 0xFF;
+        data[3] = (-gyro_bias[1] / 4) & 0xFF;
+        data[4] = (-gyro_bias[2] / 4 >> 8) & 0xFF;
+        data[5] = (-gyro_bias[2] / 4) & 0xFF;
+
+        // Push gyro biases to hardware registers
+        writeByte(MPU9250_ADDRESS, XG_OFFSET_H, data[0]);
+        writeByte(MPU9250_ADDRESS, XG_OFFSET_L, data[1]);
+        writeByte(MPU9250_ADDRESS, YG_OFFSET_H, data[2]);
+        writeByte(MPU9250_ADDRESS, YG_OFFSET_L, data[3]);
+        writeByte(MPU9250_ADDRESS, ZG_OFFSET_H, data[4]);
+        writeByte(MPU9250_ADDRESS, ZG_OFFSET_L, data[5]);
+
+        // Output scaled gyro biases for display in the main program
+        dest1[0] = (float)gyro_bias[0] / (float)gyrosensitivity;
+        dest1[1] = (float)gyro_bias[1] / (float)gyrosensitivity;
+        dest1[2] = (float)gyro_bias[2] / (float)gyrosensitivity;
+
+        // Construct the accelerometer biases for push to the hardware accelerometer bias registers. These registers contain
+        // factory trim values which must be added to the calculated accelerometer biases; on boot up these registers will hold
+        // non-zero values. In addition, bit 0 of the lower byte must be preserved since it is used for temperature
+        // compensation calculations. Accelerometer bias registers expect bias input as 2048 LSB per g, so that
+        // the accelerometer biases calculated above must be divided by 8.
+
+        // int32_t accel_bias_reg[3] = {0, 0, 0}; // A place to hold the factory accelerometer trim biases
+        // readBytes(MPU9250_ADDRESS, XA_OFFSET_H, 2, &data[0]); // Read factory accelerometer trim values
+        // accel_bias_reg[0] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+        // readBytes(MPU9250_ADDRESS, YA_OFFSET_H, 2, &data[0]);
+        // accel_bias_reg[1] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+        // readBytes(MPU9250_ADDRESS, ZA_OFFSET_H, 2, &data[0]);
+        // accel_bias_reg[2] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+
+        // uint32_t mask = 1uL; // Define mask for temperature compensation bit 0 of lower byte of accelerometer bias registers
+        // uint8_t mask_bit[3] = {0, 0, 0}; // Define array to hold mask bit for each accelerometer bias axis
+
+        // for(ii = 0; ii < 3; ii++) {
+        //     if((accel_bias_reg[ii] & mask)) mask_bit[ii] = 0x01; // If temperature compensation bit is set, record that fact in mask_bit
+        // }
+
+        // // Construct total accelerometer bias, including calculated average accelerometer bias from above
+        // accel_bias_reg[0] -= (accel_bias[0] / 8); // Subtract calculated averaged accelerometer bias scaled to 2048 LSB/g (16 g full scale)
+        // accel_bias_reg[1] -= (accel_bias[1] / 8);
+        // accel_bias_reg[2] -= (accel_bias[2] / 8);
+
+        // data[0] = (accel_bias_reg[0] >> 8) & 0xFF;
+        // data[1] = (accel_bias_reg[0])      & 0xFF;
+        // data[1] = data[1] | mask_bit[0]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+        // data[2] = (accel_bias_reg[1] >> 8) & 0xFF;
+        // data[3] = (accel_bias_reg[1])      & 0xFF;
+        // data[3] = data[3] | mask_bit[1]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+        // data[4] = (accel_bias_reg[2] >> 8) & 0xFF;
+        // data[5] = (accel_bias_reg[2])      & 0xFF;
+        // data[5] = data[5] | mask_bit[2]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+
+        // Apparently this is not working for the acceleration biases in the MPU-9250
+        // Are we handling the temperature correction bit properly?
+        // Push accelerometer biases to hardware registers
+        // writeByte(MPU9250_ADDRESS, XA_OFFSET_H, data[0]);
+        // writeByte(MPU9250_ADDRESS, XA_OFFSET_L, data[1]);
+        // writeByte(MPU9250_ADDRESS, YA_OFFSET_H, data[2]);
+        // writeByte(MPU9250_ADDRESS, YA_OFFSET_L, data[3]);
+        // writeByte(MPU9250_ADDRESS, ZA_OFFSET_H, data[4]);
+        // writeByte(MPU9250_ADDRESS, ZA_OFFSET_L, data[5]);
+
+        // Output scaled accelerometer biases for display in the main program
+        dest2[0] = (float)accel_bias[0] / (float)accelsensitivity;
+        dest2[1] = (float)accel_bias[1] / (float)accelsensitivity;
+        dest2[2] = (float)accel_bias[2] / (float)accelsensitivity;
+
+        if (b_verbose) {
+            Serial.println("MPU9250 bias");
+            Serial.println(" x   y   z  ");
+            Serial.print((int)(1000 * accelBias[0]));
+            Serial.print(" ");
+            Serial.print((int)(1000 * accelBias[1]));
+            Serial.print(" ");
+            Serial.print((int)(1000 * accelBias[2]));
+            Serial.print(" ");
+            Serial.println("mg");
+            Serial.print(gyroBias[0], 1);
+            Serial.print(" ");
+            Serial.print(gyroBias[1], 1);
+            Serial.print(" ");
+            Serial.print(gyroBias[2], 1);
+            Serial.print(" ");
+            Serial.println("o/s");
+        }
+
+        delay(100);
+
+        initMPU9250();
+
+        delay(1000);
+    }
+
+    // Accelerometer and gyroscope self test; check calibration wrt factory settings
+    void SelfTest()  // Should return percent deviation from factory trim values, +/- 14 or less deviation is a pass
+    {
+        uint8_t rawData[6] = {0, 0, 0, 0, 0, 0};
+        uint8_t selfTest[6];
+        int32_t gAvg[3] = {0}, aAvg[3] = {0}, aSTAvg[3] = {0}, gSTAvg[3] = {0};
+        float factoryTrim[6];
+        uint8_t FS = 0;
+
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x00);       // Set gyro sample rate to 1 kHz
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x02);       // Set gyro sample rate to 1 kHz and DLPF to 92 Hz
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, FS << 3);   // Set full scale range for the gyro to 250 dps
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG2, 0x02);    // Set accelerometer rate to 1 kHz and bandwidth to 92 Hz
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, FS << 3);  // Set full scale range for the accelerometer to 2 g
+
+        for (int ii = 0; ii < 200; ii++) {  // get average current values of gyro and acclerometer
+
+            readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 6, &rawData[0]);       // Read the six raw data registers into data array
+            aAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            aAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            aAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+
+            readBytes(MPU9250_ADDRESS, GYRO_XOUT_H, 6, &rawData[0]);        // Read the six raw data registers sequentially into data array
+            gAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            gAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            gAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+        }
+
+        for (int ii = 0; ii < 3; ii++) {  // Get average of 200 values and store as average current readings
+            aAvg[ii] /= 200;
+            gAvg[ii] /= 200;
+        }
+
+        // Configure the accelerometer for self-test
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0xE0);  // Enable self test on all three axes and set accelerometer range to +/- 2 g
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0xE0);   // Enable self test on all three axes and set gyro range to +/- 250 degrees/s
+        delay(25);                                       // Delay a while to let the device stabilize
+
+        for (int ii = 0; ii < 200; ii++) {  // get average self-test values of gyro and acclerometer
+
+            readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 6, &rawData[0]);         // Read the six raw data registers into data array
+            aSTAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            aSTAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            aSTAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+
+            readBytes(MPU9250_ADDRESS, GYRO_XOUT_H, 6, &rawData[0]);          // Read the six raw data registers sequentially into data array
+            gSTAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            gSTAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            gSTAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+        }
+
+        for (int ii = 0; ii < 3; ii++) {  // Get average of 200 values and store as average self-test readings
+            aSTAvg[ii] /= 200;
+            gSTAvg[ii] /= 200;
+        }
+
+        // Configure the gyro and accelerometer for normal operation
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0x00);
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0x00);
+        delay(25);  // Delay a while to let the device stabilize
+
+        // Retrieve accelerometer and gyro factory Self-Test Code from USR_Reg
+        selfTest[0] = readByte(MPU9250_ADDRESS, SELF_TEST_X_ACCEL);  // X-axis accel self-test results
+        selfTest[1] = readByte(MPU9250_ADDRESS, SELF_TEST_Y_ACCEL);  // Y-axis accel self-test results
+        selfTest[2] = readByte(MPU9250_ADDRESS, SELF_TEST_Z_ACCEL);  // Z-axis accel self-test results
+        selfTest[3] = readByte(MPU9250_ADDRESS, SELF_TEST_X_GYRO);   // X-axis gyro self-test results
+        selfTest[4] = readByte(MPU9250_ADDRESS, SELF_TEST_Y_GYRO);   // Y-axis gyro self-test results
+        selfTest[5] = readByte(MPU9250_ADDRESS, SELF_TEST_Z_GYRO);   // Z-axis gyro self-test results
+
+        // Retrieve factory self-test value from self-test code reads
+        factoryTrim[0] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[0] - 1.0)));  // FT[Xa] factory trim calculation
+        factoryTrim[1] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[1] - 1.0)));  // FT[Ya] factory trim calculation
+        factoryTrim[2] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[2] - 1.0)));  // FT[Za] factory trim calculation
+        factoryTrim[3] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[3] - 1.0)));  // FT[Xg] factory trim calculation
+        factoryTrim[4] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[4] - 1.0)));  // FT[Yg] factory trim calculation
+        factoryTrim[5] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[5] - 1.0)));  // FT[Zg] factory trim calculation
+
+        // Report results as a ratio of (STR - FT)/FT; the change from Factory Trim of the Self-Test Response
+        // To get percent, must multiply by 100
+        for (int i = 0; i < 3; i++) {
+            SelfTestResult[i] = 100.0 * ((float)(aSTAvg[i] - aAvg[i])) / factoryTrim[i] - 100.;          // Report percent differences
+            SelfTestResult[i + 3] = 100.0 * ((float)(gSTAvg[i] - gAvg[i])) / factoryTrim[i + 3] - 100.;  // Report percent differences
+        }
+
+        if (b_verbose) {
+            Serial.print("x-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[0], 1);
+            Serial.println("% of factory value");
+            Serial.print("y-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[1], 1);
+            Serial.println("% of factory value");
+            Serial.print("z-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[2], 1);
+            Serial.println("% of factory value");
+            Serial.print("x-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[3], 1);
+            Serial.println("% of factory value");
+            Serial.print("y-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[4], 1);
+            Serial.println("% of factory value");
+            Serial.print("z-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[5], 1);
+            Serial.println("% of factory value");
+        }
+        delay(5000);
+    }
+
+    void writeByte(uint8_t address, uint8_t subAddress, uint8_t data) {
+        wire->beginTransmission(address);    // Initialize the Tx buffer
+        wire->write(subAddress);             // Put slave register address in Tx buffer
+        wire->write(data);                   // Put data in Tx buffer
+        i2c_err_ = wire->endTransmission();  // Send the Tx buffer
+        if (i2c_err_) pirntI2CError();
+    }
+
+    uint8_t readByte(uint8_t address, uint8_t subAddress) {
+        uint8_t data = 0;                         // `data` will store the register data
+        wire->beginTransmission(address);         // Initialize the Tx buffer
+        wire->write(subAddress);                  // Put slave register address in Tx buffer
+        i2c_err_ = wire->endTransmission(false);  // Send the Tx buffer, but send a restart to keep connection alive
+        if (i2c_err_) pirntI2CError();
+        wire->requestFrom(address, (size_t)1);       // Read one byte from slave register address
+        if (wire->available()) data = wire->read();  // Fill Rx buffer with result
+        return data;                                 // Return data read from slave register
+    }
+
+    void readBytes(uint8_t address, uint8_t subAddress, uint8_t count, uint8_t* dest) {
+        wire->beginTransmission(address);         // Initialize the Tx buffer
+        wire->write(subAddress);                  // Put slave register address in Tx buffer
+        i2c_err_ = wire->endTransmission(false);  // Send the Tx buffer, but send a restart to keep connection alive
+        if (i2c_err_) pirntI2CError();
+        uint8_t i = 0;
+        wire->requestFrom(address, count);  // Read bytes from slave register address
+        while (wire->available()) {
+            dest[i++] = wire->read();
+        }  // Put read results in the Rx buffer
+    }
+
+    void pirntI2CError() {
+        if (i2c_err_ == 7) return;  // to avoid stickbreaker-i2c branch's error code
+        Serial.print("I2C ERROR CODE : ");
+        Serial.println(i2c_err_);
+    }
+
+    bool b_ahrs{true};
+
+    WireType* wire;
+    uint8_t i2c_err_;
+};
+
+using MPU9250 = MPU9250_<TwoWire, MPU9250_WHOAMI_DEFAULT_VALUE>;
+using MPU9255 = MPU9250_<TwoWire, MPU9255_WHOAMI_DEFAULT_VALUE>;
+
+#endif  // MPU9250_H

+ 151 - 0
.pio/libdeps/uno/MPU9250/MPU9250/MPU9250RegisterMap.h

@@ -0,0 +1,151 @@
+#pragma once
+#ifndef MPU9250REGISTERMAP_H
+#define MPU9250REGISTERMAP_H
+
+//Magnetometer Registers
+// #define AK8963_ADDRESS   0x0C
+#define AK8963_WHO_AM_I  0x00 // should return 0x48
+#define AK8963_INFO      0x01
+#define AK8963_ST1       0x02  // data ready status bit 0
+#define AK8963_XOUT_L	 0x03  // data
+#define AK8963_XOUT_H	 0x04
+#define AK8963_YOUT_L	 0x05
+#define AK8963_YOUT_H	 0x06
+#define AK8963_ZOUT_L	 0x07
+#define AK8963_ZOUT_H	 0x08
+#define AK8963_ST2       0x09  // Data overflow bit 3 and data read error status bit 2
+#define AK8963_CNTL      0x0A  // Power down (0000), single-measurement (0001), self-test (1000) and Fuse ROM (1111) modes on bits 3:0
+#define AK8963_ASTC      0x0C  // Self test control
+#define AK8963_I2CDIS    0x0F  // I2C disable
+#define AK8963_ASAX      0x10  // Fuse ROM x-axis sensitivity adjustment value
+#define AK8963_ASAY      0x11  // Fuse ROM y-axis sensitivity adjustment value
+#define AK8963_ASAZ      0x12  // Fuse ROM z-axis sensitivity adjustment value
+
+#define SELF_TEST_X_GYRO 0x00
+#define SELF_TEST_Y_GYRO 0x01
+#define SELF_TEST_Z_GYRO 0x02
+
+// #define X_FINE_GAIN      0x03 // [7:0] fine gain
+// #define Y_FINE_GAIN      0x04
+// #define Z_FINE_GAIN      0x05
+// #define XA_OFFSET_H      0x06 // User-defined trim values for accelerometer
+// #define XA_OFFSET_L_TC   0x07
+// #define YA_OFFSET_H      0x08
+// #define YA_OFFSET_L_TC   0x09
+// #define ZA_OFFSET_H      0x0A
+// #define ZA_OFFSET_L_TC   0x0B
+
+#define SELF_TEST_X_ACCEL 0x0D
+#define SELF_TEST_Y_ACCEL 0x0E
+#define SELF_TEST_Z_ACCEL 0x0F
+
+#define SELF_TEST_A      0x10
+
+#define XG_OFFSET_H      0x13  // User-defined trim values for gyroscope
+#define XG_OFFSET_L      0x14
+#define YG_OFFSET_H      0x15
+#define YG_OFFSET_L      0x16
+#define ZG_OFFSET_H      0x17
+#define ZG_OFFSET_L      0x18
+#define SMPLRT_DIV       0x19
+#define MPU_CONFIG           0x1A
+#define GYRO_CONFIG      0x1B
+#define ACCEL_CONFIG     0x1C
+#define ACCEL_CONFIG2    0x1D
+#define LP_ACCEL_ODR     0x1E
+#define WOM_THR          0x1F
+
+#define MOT_DUR          0x20  // Duration counter threshold for motion interrupt generation, 1 kHz rate, LSB = 1 ms
+#define ZMOT_THR         0x21  // Zero-motion detection threshold bits [7:0]
+#define ZRMOT_DUR        0x22  // Duration counter threshold for zero motion interrupt generation, 16 Hz rate, LSB = 64 ms
+
+#define FIFO_EN          0x23
+#define I2C_MST_CTRL     0x24
+#define I2C_SLV0_ADDR    0x25
+#define I2C_SLV0_REG     0x26
+#define I2C_SLV0_CTRL    0x27
+#define I2C_SLV1_ADDR    0x28
+#define I2C_SLV1_REG     0x29
+#define I2C_SLV1_CTRL    0x2A
+#define I2C_SLV2_ADDR    0x2B
+#define I2C_SLV2_REG     0x2C
+#define I2C_SLV2_CTRL    0x2D
+#define I2C_SLV3_ADDR    0x2E
+#define I2C_SLV3_REG     0x2F
+#define I2C_SLV3_CTRL    0x30
+#define I2C_SLV4_ADDR    0x31
+#define I2C_SLV4_REG     0x32
+#define I2C_SLV4_DO      0x33
+#define I2C_SLV4_CTRL    0x34
+#define I2C_SLV4_DI      0x35
+#define I2C_MST_STATUS   0x36
+#define INT_PIN_CFG      0x37
+#define INT_ENABLE       0x38
+#define DMP_INT_STATUS   0x39  // Check DMP interrupt
+#define INT_STATUS       0x3A
+#define ACCEL_XOUT_H     0x3B
+#define ACCEL_XOUT_L     0x3C
+#define ACCEL_YOUT_H     0x3D
+#define ACCEL_YOUT_L     0x3E
+#define ACCEL_ZOUT_H     0x3F
+#define ACCEL_ZOUT_L     0x40
+#define TEMP_OUT_H       0x41
+#define TEMP_OUT_L       0x42
+#define GYRO_XOUT_H      0x43
+#define GYRO_XOUT_L      0x44
+#define GYRO_YOUT_H      0x45
+#define GYRO_YOUT_L      0x46
+#define GYRO_ZOUT_H      0x47
+#define GYRO_ZOUT_L      0x48
+#define EXT_SENS_DATA_00 0x49
+#define EXT_SENS_DATA_01 0x4A
+#define EXT_SENS_DATA_02 0x4B
+#define EXT_SENS_DATA_03 0x4C
+#define EXT_SENS_DATA_04 0x4D
+#define EXT_SENS_DATA_05 0x4E
+#define EXT_SENS_DATA_06 0x4F
+#define EXT_SENS_DATA_07 0x50
+#define EXT_SENS_DATA_08 0x51
+#define EXT_SENS_DATA_09 0x52
+#define EXT_SENS_DATA_10 0x53
+#define EXT_SENS_DATA_11 0x54
+#define EXT_SENS_DATA_12 0x55
+#define EXT_SENS_DATA_13 0x56
+#define EXT_SENS_DATA_14 0x57
+#define EXT_SENS_DATA_15 0x58
+#define EXT_SENS_DATA_16 0x59
+#define EXT_SENS_DATA_17 0x5A
+#define EXT_SENS_DATA_18 0x5B
+#define EXT_SENS_DATA_19 0x5C
+#define EXT_SENS_DATA_20 0x5D
+#define EXT_SENS_DATA_21 0x5E
+#define EXT_SENS_DATA_22 0x5F
+#define EXT_SENS_DATA_23 0x60
+#define MOT_DETECT_STATUS 0x61
+#define I2C_SLV0_DO      0x63
+#define I2C_SLV1_DO      0x64
+#define I2C_SLV2_DO      0x65
+#define I2C_SLV3_DO      0x66
+#define I2C_MST_DELAY_CTRL 0x67
+#define SIGNAL_PATH_RESET  0x68
+#define MOT_DETECT_CTRL  0x69
+#define USER_CTRL        0x6A  // Bit 7 enable DMP, bit 3 reset DMP
+#define PWR_MGMT_1       0x6B // Device defaults to the SLEEP mode
+#define PWR_MGMT_2       0x6C
+#define DMP_BANK         0x6D  // Activates a specific bank in the DMP
+#define DMP_RW_PNT       0x6E  // Set read/write pointer to a specific start address in specified DMP bank
+#define DMP_REG          0x6F  // Register in DMP from which to read or to which to write
+#define DMP_REG_1        0x70
+#define DMP_REG_2        0x71
+#define FIFO_COUNTH      0x72
+#define FIFO_COUNTL      0x73
+#define FIFO_R_W         0x74
+#define WHO_AM_I_MPU9250 0x75 // Should return 0x71
+#define XA_OFFSET_H      0x77
+#define XA_OFFSET_L      0x78
+#define YA_OFFSET_H      0x7A
+#define YA_OFFSET_L      0x7B
+#define ZA_OFFSET_H      0x7D
+#define ZA_OFFSET_L      0x7E
+
+#endif // MPU9250REGISTERMAP_H

+ 223 - 0
.pio/libdeps/uno/MPU9250/MPU9250/QuaternionFilter.h

@@ -0,0 +1,223 @@
+#pragma once
+#ifndef QUATERNIONFILTER_H
+#define QUATERNIONFILTER_H
+
+class QuaternionFilter
+{
+
+    float GyroMeasError = PI * (40.0f / 180.0f);   // gyroscope measurement error in rads/s (start at 40 deg/s)
+    float GyroMeasDrift = PI * (0.0f  / 180.0f);   // gyroscope measurement drift in rad/s/s (start at 0.0 deg/s/s)
+    float beta = sqrt(3.0f / 4.0f) * GyroMeasError;   // compute beta
+    float zeta = sqrt(3.0f / 4.0f) * GyroMeasDrift;   // compute zeta, the other free parameter in the Madgwick scheme usually set to a small or zero value
+    const float Kp = 2.0f * 5.0f; // these are the free parameters in the Mahony filter and fusion scheme, Kp for proportional feedback, Ki for integral
+    const float Ki = 0.0f;
+
+    float deltat = 0.0f, sum = 0.0f;        // integration interval for both filter schemes
+    uint32_t lastUpdate = 0, firstUpdate = 0; // used to calculate integration interval
+    uint32_t Now = 0;        // used to calculate integration interval
+
+    // for mahony only
+    float eInt[3] = {0.0f, 0.0f, 0.0f};       // vector to hold integral error for Mahony method
+
+public:
+
+    void bind() {}
+
+    // MadgwickQuaternionUpdate
+    void update(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float* q)
+    {
+        // updateParams()
+        float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];   // short name local variable for readability
+        float norm;
+        float hx, hy, _2bx, _2bz;
+        float s1, s2, s3, s4;
+        float qDot1, qDot2, qDot3, qDot4;
+
+        // Auxiliary variables to avoid repeated arithmetic
+        float _2q1mx;
+        float _2q1my;
+        float _2q1mz;
+        float _2q2mx;
+        float _4bx;
+        float _4bz;
+        float _2q1 = 2.0f * q1;
+        float _2q2 = 2.0f * q2;
+        float _2q3 = 2.0f * q3;
+        float _2q4 = 2.0f * q4;
+        float _2q1q3 = 2.0f * q1 * q3;
+        float _2q3q4 = 2.0f * q3 * q4;
+        float q1q1 = q1 * q1;
+        float q1q2 = q1 * q2;
+        float q1q3 = q1 * q3;
+        float q1q4 = q1 * q4;
+        float q2q2 = q2 * q2;
+        float q2q3 = q2 * q3;
+        float q2q4 = q2 * q4;
+        float q3q3 = q3 * q3;
+        float q3q4 = q3 * q4;
+        float q4q4 = q4 * q4;
+        gx *= PI / 180.f;
+        gy *= PI / 180.f;
+        gz *= PI / 180.f;
+
+        // updateTime()
+        Now = micros();
+        deltat = ((Now - lastUpdate) / 1000000.0f); // set integration time by time elapsed since last filter update
+        lastUpdate = Now;
+
+        // Normalise accelerometer measurement
+        norm = sqrtf(ax * ax + ay * ay + az * az);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;
+        ax *= norm;
+        ay *= norm;
+        az *= norm;
+
+        // Normalise magnetometer measurement
+        norm = sqrtf(mx * mx + my * my + mz * mz);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;
+        mx *= norm;
+        my *= norm;
+        mz *= norm;
+
+        // Reference direction of Earth's magnetic field
+        _2q1mx = 2.0f * q1 * mx;
+        _2q1my = 2.0f * q1 * my;
+        _2q1mz = 2.0f * q1 * mz;
+        _2q2mx = 2.0f * q2 * mx;
+        hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
+        hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
+        _2bx = sqrtf(hx * hx + hy * hy);
+        _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
+        _4bx = 2.0f * _2bx;
+        _4bz = 2.0f * _2bz;
+
+        // Gradient decent algorithm corrective step
+        s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        norm = sqrtf(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4);    // normalise step magnitude
+        norm = 1.0f/norm;
+        s1 *= norm;
+        s2 *= norm;
+        s3 *= norm;
+        s4 *= norm;
+
+        // Compute rate of change of quaternion
+        qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
+        qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
+        qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
+        qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;
+
+        // Integrate to yield quaternion
+        q1 += qDot1 * deltat;
+        q2 += qDot2 * deltat;
+        q3 += qDot3 * deltat;
+        q4 += qDot4 * deltat;
+        norm = sqrtf(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);    // normalise quaternion
+        norm = 1.0f/norm;
+        q[0] = q1 * norm;
+        q[1] = q2 * norm;
+        q[2] = q3 * norm;
+        q[3] = q4 * norm;
+    }
+
+ // Similar to Madgwick scheme but uses proportional and integral filtering on the error between estimated reference vectors and
+ // measured ones.
+    void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float* q)
+    {
+        float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];   // short name local variable for readability
+        float norm;
+        float hx, hy, bx, bz;
+        float vx, vy, vz, wx, wy, wz;
+        float ex, ey, ez;
+        float pa, pb, pc;
+
+        // Auxiliary variables to avoid repeated arithmetic
+        float q1q1 = q1 * q1;
+        float q1q2 = q1 * q2;
+        float q1q3 = q1 * q3;
+        float q1q4 = q1 * q4;
+        float q2q2 = q2 * q2;
+        float q2q3 = q2 * q3;
+        float q2q4 = q2 * q4;
+        float q3q3 = q3 * q3;
+        float q3q4 = q3 * q4;
+        float q4q4 = q4 * q4;
+
+        // Normalise accelerometer measurement
+        norm = sqrtf(ax * ax + ay * ay + az * az);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;        // use reciprocal for division
+        ax *= norm;
+        ay *= norm;
+        az *= norm;
+
+        // Normalise magnetometer measurement
+        norm = sqrtf(mx * mx + my * my + mz * mz);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;        // use reciprocal for division
+        mx *= norm;
+        my *= norm;
+        mz *= norm;
+
+        // Reference direction of Earth's magnetic field
+        hx = 2.0f * mx * (0.5f - q3q3 - q4q4) + 2.0f * my * (q2q3 - q1q4) + 2.0f * mz * (q2q4 + q1q3);
+        hy = 2.0f * mx * (q2q3 + q1q4) + 2.0f * my * (0.5f - q2q2 - q4q4) + 2.0f * mz * (q3q4 - q1q2);
+        bx = sqrtf((hx * hx) + (hy * hy));
+        bz = 2.0f * mx * (q2q4 - q1q3) + 2.0f * my * (q3q4 + q1q2) + 2.0f * mz * (0.5f - q2q2 - q3q3);
+
+        // Estimated direction of gravity and magnetic field
+        vx = 2.0f * (q2q4 - q1q3);
+        vy = 2.0f * (q1q2 + q3q4);
+        vz = q1q1 - q2q2 - q3q3 + q4q4;
+        wx = 2.0f * bx * (0.5f - q3q3 - q4q4) + 2.0f * bz * (q2q4 - q1q3);
+        wy = 2.0f * bx * (q2q3 - q1q4) + 2.0f * bz * (q1q2 + q3q4);
+        wz = 2.0f * bx * (q1q3 + q2q4) + 2.0f * bz * (0.5f - q2q2 - q3q3);
+
+        // Error is cross product between estimated direction and measured direction of gravity
+        ex = (ay * vz - az * vy) + (my * wz - mz * wy);
+        ey = (az * vx - ax * vz) + (mz * wx - mx * wz);
+        ez = (ax * vy - ay * vx) + (mx * wy - my * wx);
+        if (Ki > 0.0f)
+        {
+            eInt[0] += ex;      // accumulate integral error
+            eInt[1] += ey;
+            eInt[2] += ez;
+        }
+        else
+        {
+            eInt[0] = 0.0f;     // prevent integral wind up
+            eInt[1] = 0.0f;
+            eInt[2] = 0.0f;
+        }
+
+        // Apply feedback terms
+        gx = gx + Kp * ex + Ki * eInt[0];
+        gy = gy + Kp * ey + Ki * eInt[1];
+        gz = gz + Kp * ez + Ki * eInt[2];
+
+        // Integrate rate of change of quaternion
+        pa = q2;
+        pb = q3;
+        pc = q4;
+        q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltat);
+        q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * deltat);
+        q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * deltat);
+        q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * deltat);
+
+        // Normalise quaternion
+        norm = sqrtf(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
+        norm = 1.0f / norm;
+        q[0] = q1 * norm;
+        q[1] = q2 * norm;
+        q[2] = q3 * norm;
+        q[3] = q4 * norm;
+    }
+
+
+};
+
+#endif // QUATERNIONFILTER_H

+ 230 - 0
.pio/libdeps/uno/MPU9250/README.md

@@ -0,0 +1,230 @@
+# MPU9250
+Arduino library for [MPU9250](https://www.invensense.com/products/motion-tracking/9-axis/mpu-9250/) Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+
+This library is based on the [great work](https://github.com/kriswiner/MPU9250) by [kriswiner](https://github.com/kriswiner), and re-writen for the simple usage.
+
+## Usage
+
+### Simple Measurement
+
+``` C++
+#include "MPU9250.h"
+
+MPU9250 mpu;
+// MPU9255 mpu; // You can also use MPU9255
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+}
+
+void loop() {
+    if (mpu.update()) {
+        mpu.printRollPitchYaw();
+    }
+}
+```
+
+### Calibration
+
+- device should be stay still during accel/gyro calibration
+- round device around during mag calibration
+
+``` C++
+#include "MPU9250.h"
+
+MPU9250 mpu;
+// MPU9255 mpu; // You can also use MPU9255
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate anytime you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    mpu.printCalibration();
+}
+
+void loop() { }
+```
+
+
+## Other Settings
+
+### I2C Address
+
+You must set your own address based on A0, A1, A2 setting as:
+
+``` C++
+mpu.setup(0x70);
+```
+
+### Other I2C library
+
+You can use other I2C library e.g. [SoftWire](https://github.com/stevemarple/SoftWire).
+
+``` C++
+MPU9250_<SoftWire, MPU9250_WHOAMI_DEFAULT_VALUE> mpu;
+SoftWire sw(SDA, SCL);
+
+// in setup()
+mpu.setup(0x70, sw);
+```
+
+### Magnetic Declination
+
+Magnetic declination should be set depending on where you are to get accurate data.
+To set it, use this method.
+
+```C++
+mpu.setMagneticDeclination(value);
+```
+
+You can find magnetic declination in your city [here](http://www.magnetic-declination.com/).
+
+For more details, see [wiki](https://en.wikipedia.org/wiki/Magnetic_declination).
+
+
+### AFS, FGS, MFS
+
+#### AFS
+
+`enum class AFS { A2G, A4G, A8G, A16G };`
+
+#### GFS
+
+`enum class GFS { G250DPS, G500DPS, G1000DPS, G2000DPS };`
+
+#### MFS
+
+`enum class MFS { M14BITS, M16BITS };
+
+#### How to change
+
+MPU9250 class is defined as follows.
+
+```C++
+template <
+	typename WireType,
+	AFS AFSSEL = AFS::A16G,
+	GFS GFSSEL = GFS::G2000DPS,
+	MFS MFSSEL = MFS::M16BITS
+>
+class MPU9250_;
+```
+
+So, please use like
+
+```
+class MPU9250_<TwoWire, AFS::A4G, GFS::G500DPS, MFS::M14BITS> mpu; // most of Arduino
+class MPU9250_<i2c_t3, AFS::A4G, GFS::G500DPS, MFS::M14BITS> mpu; // Teensy
+```
+
+### MPU9255
+
+To use MPU9255 instead of MPU9250, just declare MPU9255.
+
+```C++
+MPU9255 mpu;
+```
+
+## APIs
+
+``` C++
+bool setup(const uint8_t addr, WireType& w = Wire);
+void verbose(const bool b);
+void calibrateAccelGyro();
+void calibrateMag();
+bool isConnectedMPU9250();
+bool isConnectedAK8963();
+bool available();
+bool update();
+
+float getRoll() const;
+float getPitch() const;
+float getYaw() const;
+
+float getQuaternion(uint8_t i) const;
+
+float getQuaternionX() const;
+float getQuaternionY() const;
+float getQuaternionZ() const;
+float getQuaternionW() const;
+
+float getAcc(const uint8_t i) const;
+float getGyro(const uint8_t i) const;
+float getMag(const uint8_t i) const;
+
+float getAccX() const;
+float getAccY() const;
+float getAccZ() const;
+float getGyroX() const;
+float getGyroY() const;
+float getGyroZ() const;
+float getMagX() const;
+float getMagY() const;
+float getMagZ() const;
+
+float getAccBias(const uint8_t i) const;
+float getGyroBias(const uint8_t i) const;
+float getMagBias(const uint8_t i) const;
+float getMagScale(const uint8_t i) const;
+
+float getAccBiasX() const;
+float getAccBiasY() const;
+float getAccBiasZ() const;
+float getGyroBiasX() const;
+float getGyroBiasY() const;
+float getGyroBiasZ() const;
+float getMagBiasX() const;
+float getMagBiasY() const;
+float getMagBiasZ() const;
+float getMagScaleX() const;
+float getMagScaleY() const;
+float getMagScaleZ() const;
+
+float getTemperature() const;
+
+void setAccBias(const uint8_t i, const float v);
+void setGyroBias(const uint8_t i, const float v);
+void setMagBias(const uint8_t i, const float v);
+void setMagScale(const uint8_t i, const float v);
+
+void setAccBiasX(const float v);
+void setAccBiasY(const float v);
+void setAccBiasZ(const float v);
+void setGyroBiasX(const float v);
+void setGyroBiasY(const float v);
+void setGyroBiasZ(const float v);
+void setMagBiasX(const float v);
+void setMagBiasY(const float v);
+void setMagBiasZ(const float v);
+void setMagScaleX(const float v);
+void setMagScaleY(const float v);
+void setMagScaleZ(const float v);
+
+void setMagneticDeclination(const float d);
+
+void print() const;
+void printRawData() const;
+void printRollPitchYaw() const;
+void printCalibration() const;
+```
+
+## License
+
+MIT
+
+## Acknowledgments / Contributor
+
+- [Yuta Asai](https://github.com/asaiyuta)

+ 22 - 0
.pio/libdeps/uno/MPU9250/examples/calibration/calibration.ino

@@ -0,0 +1,22 @@
+#include "MPU9250.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate anytime you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    mpu.printCalibration();
+}
+
+void loop() {
+}

+ 29 - 0
.pio/libdeps/uno/MPU9250/examples/calibration_eeprom/calibration_eeprom.ino

@@ -0,0 +1,29 @@
+#include "MPU9250.h"
+#include "eeprom_utils.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate when you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    // save to eeprom
+    saveCalibration();
+
+    // load from eeprom
+    loadCalibration();
+
+    mpu.printCalibration();
+}
+
+void loop() {
+}

+ 150 - 0
.pio/libdeps/uno/MPU9250/examples/calibration_eeprom/eeprom_utils.h

@@ -0,0 +1,150 @@
+#include <EEPROM.h>
+#include "MPU9250.h"
+
+const uint8_t EEPROM_SIZE = 1 + sizeof(float) * 3 * 4;
+extern MPU9250 mpu;
+
+enum EEP_ADDR
+{
+    EEP_CALIB_FLAG = 0x00,
+    EEP_ACC_BIAS = 0x01,
+    EEP_GYRO_BIAS = 0x0D,
+    EEP_MAG_BIAS = 0x19,
+    EEP_MAG_SCALE = 0x25
+};
+
+void writeByte(int address, byte value){
+  EEPROM.put(address, value);
+}
+
+void writeFloat(int address, float value){
+  EEPROM.put(address, value);
+}
+
+byte readByte(int address){
+  byte valueIn;
+  EEPROM.get(address, valueIn);
+  return valueIn;
+}
+
+float readFloat(int address){
+  float valueIn;
+  EEPROM.get(address, valueIn);
+  return valueIn;
+}
+
+void clearCalibration()
+{
+    writeByte(EEP_CALIB_FLAG, 0);
+}
+
+bool isCalibrated()
+{
+    return (readByte(EEP_CALIB_FLAG) == 0x01);
+}
+
+void saveCalibration()
+{
+    writeByte(EEP_CALIB_FLAG, 1);
+    writeFloat(EEP_ACC_BIAS + 0, mpu.getAccBias(0));
+    writeFloat(EEP_ACC_BIAS + 4, mpu.getAccBias(1));
+    writeFloat(EEP_ACC_BIAS + 8, mpu.getAccBias(2));
+    writeFloat(EEP_GYRO_BIAS + 0, mpu.getGyroBias(0));
+    writeFloat(EEP_GYRO_BIAS + 4, mpu.getGyroBias(1));
+    writeFloat(EEP_GYRO_BIAS + 8, mpu.getGyroBias(2));
+    writeFloat(EEP_MAG_BIAS + 0, mpu.getMagBias(0));
+    writeFloat(EEP_MAG_BIAS + 4, mpu.getMagBias(1));
+    writeFloat(EEP_MAG_BIAS + 8, mpu.getMagBias(2));
+    writeFloat(EEP_MAG_SCALE + 0, mpu.getMagScale(0));
+    writeFloat(EEP_MAG_SCALE + 4, mpu.getMagScale(1));
+    writeFloat(EEP_MAG_SCALE + 8, mpu.getMagScale(2));
+}
+
+void loadCalibration()
+{
+    if (isCalibrated())
+    {
+        Serial.println("calibrated? : YES");
+        Serial.println("load calibrated values");
+        mpu.setAccBias(0, readFloat(EEP_ACC_BIAS + 0));
+        mpu.setAccBias(1, readFloat(EEP_ACC_BIAS + 4));
+        mpu.setAccBias(2, readFloat(EEP_ACC_BIAS + 8));
+        mpu.setGyroBias(0, readFloat(EEP_GYRO_BIAS + 0));
+        mpu.setGyroBias(1, readFloat(EEP_GYRO_BIAS + 4));
+        mpu.setGyroBias(2, readFloat(EEP_GYRO_BIAS + 8));
+        mpu.setMagBias(0, readFloat(EEP_MAG_BIAS + 0));
+        mpu.setMagBias(1, readFloat(EEP_MAG_BIAS + 4));
+        mpu.setMagBias(2, readFloat(EEP_MAG_BIAS + 8));
+        mpu.setMagScale(0, readFloat(EEP_MAG_SCALE + 0));
+        mpu.setMagScale(1, readFloat(EEP_MAG_SCALE + 4));
+        mpu.setMagScale(2, readFloat(EEP_MAG_SCALE + 8));
+    }
+    else
+    {
+        Serial.println("calibrated? : NO");
+        Serial.println("load default values");
+        mpu.setAccBias(0, +0.005);
+        mpu.setAccBias(1, -0.008);
+        mpu.setAccBias(2, -0.001);
+        mpu.setGyroBias(0, +1.5);
+        mpu.setGyroBias(1, -0.5);
+        mpu.setGyroBias(2, +0.7);
+        mpu.setMagBias(0, +186.41);
+        mpu.setMagBias(1, -197.91);
+        mpu.setMagBias(2, -425.55);
+        mpu.setMagScale(0, +1.07);
+        mpu.setMagScale(1, +0.95);
+        mpu.setMagScale(2, +0.99);
+    }
+}
+
+void printCalibration()
+{
+    Serial.println("< calibration parameters >");
+    Serial.print("calibrated? : ");
+    Serial.println(readByte(EEP_CALIB_FLAG) ? "YES" : "NO");
+    Serial.print("acc bias x  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 0) * 1000.f);
+    Serial.print("acc bias y  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 4) * 1000.f);
+    Serial.print("acc bias z  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 8) * 1000.f);
+    Serial.print("gyro bias x : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 0));
+    Serial.print("gyro bias y : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 4));
+    Serial.print("gyro bias z : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 8));
+    Serial.print("mag bias x  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 0));
+    Serial.print("mag bias y  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 4));
+    Serial.print("mag bias z  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 8));
+    Serial.print("mag scale x : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 0));
+    Serial.print("mag scale y : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 4));
+    Serial.print("mag scale z : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 8));
+}
+
+void printBytes()
+{
+    for (size_t i = 0; i < EEPROM_SIZE; ++i)
+        Serial.println(readByte(i), HEX);
+}
+
+void setupEEPROM()
+{
+    Serial.println("EEPROM start");
+
+    if (!isCalibrated())
+    {
+        Serial.println("Need Calibration!!");
+    }
+    Serial.println("EEPROM calibration value is : ");
+    printCalibration();
+    Serial.println("Loaded calibration value is : ");
+    loadCalibration();
+}

+ 71 - 0
.pio/libdeps/uno/MPU9250/examples/connection_check/connection_check.ino

@@ -0,0 +1,71 @@
+#include "MPU9250.h"
+
+uint8_t addrs[7] = {0};
+uint8_t device_count = 0;
+
+template <typename WireType = TwoWire>
+void scan_mpu(WireType& wire = Wire) {
+    Serial.println("Searching for i2c devices...");
+    device_count = 0;
+    for (uint8_t i = 0x68; i < 0x70; ++i) {
+        wire.beginTransmission(i);
+        if (wire.endTransmission() == 0) {
+            addrs[device_count++] = i;
+            delay(1);
+        }
+    }
+    Serial.print("Found ");
+    Serial.print(device_count, DEC);
+    Serial.println(" I2C devices");
+
+    Serial.print("I2C addresses are: ");
+    for (uint8_t i = 0; i < device_count; ++i) {
+        Serial.print("0x");
+        Serial.print(addrs[i], HEX);
+        Serial.print(" ");
+    }
+    Serial.println();
+}
+
+template <typename WireType = TwoWire>
+uint8_t readByte(uint8_t address, uint8_t subAddress, WireType& wire = Wire) {
+    uint8_t data = 0;
+    wire.beginTransmission(address);
+    wire.write(subAddress);
+    wire.endTransmission(false);
+    wire.requestFrom(address, (size_t)1);
+    if (wire.available()) data = wire.read();
+    return data;
+}
+
+void setup() {
+    Serial.begin(115200);
+    Serial.flush();
+    Wire.begin();
+    delay(2000);
+
+    scan_mpu();
+
+    if (device_count == 0) {
+        Serial.println("No device found on I2C bus. Please check your hardware connection");
+        while (1)
+            ;
+    }
+
+    // check WHO_AM_I address of MPU
+    for (uint8_t i = 0; i < device_count; ++i) {
+        Serial.print("I2C address 0x");
+        Serial.print(addrs[i], HEX);
+        byte c = readByte(addrs[i], WHO_AM_I_MPU9250);
+        if (c == MPU9250_WHOAMI_DEFAULT_VALUE) {
+            Serial.println(" is MPU9250 and ready to use");
+        } else if (c == MPU9255_WHOAMI_DEFAULT_VALUE) {
+            Serial.println(" is MPU9255 and ready to use");
+        } else {
+            Serial.println(" is not MPU series. Please use correct device");
+        }
+    }
+}
+
+void loop() {
+}

+ 17 - 0
.pio/libdeps/uno/MPU9250/examples/simple/simple.ino

@@ -0,0 +1,17 @@
+#include "MPU9250.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+}
+
+void loop() {
+    if (mpu.update()) {
+        mpu.printRollPitchYaw();
+    }
+}

+ 20 - 0
.pio/libdeps/uno/MPU9250/library.json

@@ -0,0 +1,20 @@
+{
+    "name": "MPU9250",
+    "keywords": "i2c,wire,imu",
+    "description": "Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device",
+    "repository":
+    {
+        "type": "git",
+        "url": "https://github.com/hideakitai/MPU9250.git"
+    },
+    "authors":
+    {
+        "name": "Hideaki Tai",
+        "url": "https://github.com/hideakitai",
+        "maintainer": true
+    },
+    "version": "0.2.3",
+    "license": "MIT",
+    "frameworks": "arduino",
+    "platforms": "*"
+}

+ 9 - 0
.pio/libdeps/uno/MPU9250/library.properties

@@ -0,0 +1,9 @@
+name=MPU9250
+version=0.2.3
+author=hideakitai
+maintainer=hideakitai
+sentence=Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+paragraph=Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+category=Device Control
+url=https://github.com/hideakitai/MPU9250
+architectures=*

+ 60 - 0
.vscode/c_cpp_properties.json

@@ -0,0 +1,60 @@
+//
+// !!! WARNING !!! AUTO-GENERATED FILE!
+// PLEASE DO NOT MODIFY IT AND USE "platformio.ini":
+// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
+//
+{
+    "configurations": [
+        {
+            "name": "PlatformIO",
+            "includePath": [
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/include",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/src",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/BMP280_DEV",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/MPU9250",
+                "/home/user/.platformio/packages/framework-arduino-avr/cores/arduino",
+                "/home/user/.platformio/packages/framework-arduino-avr/variants/standard",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/SPI/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/Wire/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/HID/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src",
+                ""
+            ],
+            "browse": {
+                "limitSymbolsToIncludedHeaders": true,
+                "path": [
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/include",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/src",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/BMP280_DEV",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/MPU9250",
+                    "/home/user/.platformio/packages/framework-arduino-avr/cores/arduino",
+                    "/home/user/.platformio/packages/framework-arduino-avr/variants/standard",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/SPI/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/Wire/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/HID/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src",
+                    ""
+                ]
+            },
+            "defines": [
+                "PLATFORMIO=50205",
+                "ARDUINO_AVR_UNO",
+                "F_CPU=16000000L",
+                "ARDUINO_ARCH_AVR",
+                "ARDUINO=10808",
+                "__AVR_ATmega328P__",
+                ""
+            ],
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+            "compilerPath": "/home/user/.platformio/packages/toolchain-atmelavr/bin/avr-gcc",
+            "compilerArgs": [
+                "-mmcu=atmega328p",
+                ""
+            ]
+        }
+    ],
+    "version": 4
+}

+ 10 - 0
.vscode/extensions.json

@@ -0,0 +1,10 @@
+{
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
+    "recommendations": [
+        "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
+    ]
+}

+ 44 - 0
.vscode/launch.json

@@ -0,0 +1,44 @@
+// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY
+//
+// PIO Unified Debugger
+//
+// Documentation: https://docs.platformio.org/page/plus/debugging.html
+// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html
+
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart",
+            "preLaunchTask": {
+                "type": "PlatformIO",
+                "task": "Pre-Debug"
+            }
+        },
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug (skip Pre-Debug)",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart"
+        },
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug (without uploading)",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart",
+            "loadMode": "manual"
+        }
+    ]
+}

BIN
LICENSE


+ 12 - 3
README.md

@@ -27,9 +27,12 @@ void setup() {
   mpu.setup(0x68); 
   delay(1000);
 
-  Serial.println(F("calibrate accel/gyro"));
+  Serial.println(F("Calibrate accel/gyro"));
   mpu.calibrateAccelGyro();
 
+  Serial.println(F("Calibrate Magnetometer"));
+  //mpu.calibrateMag();
+  
   Serial.println(F("FIELDS:\tUS_0\tUS_1\tMAG_X\tMAG_Y\tMAG_Z\tACCEL_X\tACCEL_Y\tACCEL_Z\tGYRO_X\tGYRO_Y\tGYRO_Z\tTEMP\tEXEC_TIME"));
 
 }
@@ -41,6 +44,12 @@ void loop() {
 
   if(micros() >= loopStart + LOOP_INTERVAL_US) {
     loopStart = micros();
+    /*if (Serial.available() > 0){
+      char c = Serial.read(1);
+      if (c == 'c'){
+        mpu.calibrateMag();
+      }
+    }*/
 
     //get mpu values
     mpu.update();
@@ -56,10 +65,10 @@ void loop() {
       (long)(mpu.getAccX()*1000),
       (long)(mpu.getAccY()*1000),
       (long)(mpu.getAccZ()*1000),
-      //gyroscope
+      /*gyroscope (values not needed)
       (long)(mpu.getGyroX()*1000),
       (long)(mpu.getGyroY()*1000),
-      (long)(mpu.getGyroZ()*1000),
+      (long)(mpu.getGyroZ()*1000),*/
       //temperature
       (long)(mpu.getTemperature()*1000),
       //execution time in microseconds

BIN
arduino/src/ultrasonic.cpp


BIN
images/Aufgabenstellung.jpg


+ 19 - 0
platformio.ini

@@ -0,0 +1,19 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:uno]
+platform = atmelavr
+board = uno
+framework = arduino
+monitor_speed = 1000000
+board_build.f_cpu = 16000000L
+lib_deps = 
+	hideakitai/MPU9250@^0.2.3
+	martinl1/BMP280_DEV@^1.0.18

BIN
raspberry-pi/.vscode/settings.json


+ 11 - 3
raspberry-pi/config.ini

@@ -36,8 +36,6 @@
   overhead_left  = 20
   overhead_right = 20
 
-[mag_sensor]
-
 [opt_sensor]
   capture_device = -1
   debug_image = yes
@@ -52,6 +50,16 @@
   # RaspiCam datasheet: https://www.raspberrypi.org/documentation/hardware/camera/
   fov = 53.50
 
+[mag_sensor]
+max_x = 800
+max_y = 800
+off_x = 0
+off_y = 0
+off_z = 0
+scale_x = 0
+scale_y = 0
+scale_z = 0
+
 [gui]
-  fullscreen = yes
+  fullscreen = no
   log_lines  = 100

+ 131 - 21
raspberry-pi/gui/SourceSansPro-Semibold.otf

@@ -7,18 +7,21 @@ import numpy as np
 from gui.popup import CalibrationPopUp
 from gui.graph import Graph
 from gui.logScreen import LogScreen
+from sensors.opticalSensor import OpticalSensor
 import logHandler
 
 
 class MainWindow(tk.Frame):
-  def __init__(self, root, conf, ac_sensor, opt_sensor):
-    self.root        = root
-    self.conf        = conf
-    self.ac_sensor   = ac_sensor
-    self.opt_sensor  = opt_sensor
-    self.log_handler = logHandler.get_log_handler()
-    self.popup_window       = None
-    self.log_window  = None
+  def __init__(self, root, conf, ac_sensor, opt_sensor, mag_sensor):
+    self.root         = root
+    self.conf         = conf
+    self.ac_sensor    = ac_sensor
+    self.opt_sensor   = opt_sensor
+    self.mag_sensor   = mag_sensor
+    self.log_handler  = logHandler.get_log_handler()
+    self.popup_window = None
+    self.log_window   = None
+    self.mainWindow   = None
 
     tk.Frame.__init__(self, root)
     # data plot at left side
@@ -55,17 +58,44 @@ class MainWindow(tk.Frame):
     tk.Label(self.controls, textvariable=self.opt_dro_offset, anchor="nw").pack(side="top", fill="both", expand=False)
     tk.Label(self.controls, textvariable=self.opt_dro_size, anchor="nw").pack(side="top", fill="both", expand=False)
 
-    quit_button = tk.Button(self.controls, text="Quit", command=self.root.destroy, height=2, foreground="red")
-    quit_button.pack(side="bottom", fill="both")
+    self.mag_dro_val_sums = np.ndarray((4), dtype=np.float)
+    self.mag_dro_val_count = 0
+    self.mag_dro_x = tk.StringVar()
+    self.mag_dro_y = tk.StringVar()
+    self.mag_label = tk.Label(self.controls, text="Magnetic Sensor", anchor="c", font=("Helvatica", 10, 'bold'))
+    self.mag_label.pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.mag_dro_x, anchor = "nw").pack(side = "top", fill = "both", expand = False)
+    tk.Label(self.controls, textvariable=self.mag_dro_y, anchor = "nw").pack(side = "top", fill = "both", expand = False)
 
-    calibrate_button = tk.Button(self.controls, text="Calibrate AC", command=self.calibrate_ac, height=4)
-    calibrate_button.pack(side="bottom", fill="both")
+    self.quit_button = tk.Button(self.controls, text="Quit", command=self.root.destroy, height=2, foreground="red")
+    self.quit_button.pack(side="bottom", fill="both")
 
-    clear_button = tk.Button(self.controls, text="Clear graph", command=self.graph.clear, height=4)
-    clear_button.pack(side="bottom", fill="both")
+    self.calibrate_submenu_button = tk.Button(self.controls, text="Calibrate", command=self.calibrate_submenu, height=2)
+    self.calibrate_submenu_button.pack_forget()
+    
+    self.calibrate_all_button = tk.Button(self.controls, text="Calibrate All", command=self.calibrate_all, height=2)
+    self.calibrate_all_button.pack_forget()
+    
+    self.calibrateac_button = tk.Button(self.controls, text="Calibrate AC", command=self.calibrate_ac, height=2)
+    self.calibrateac_button.pack_forget()
+
+    self.calibrateopt_button = tk.Button(self.controls, text = "Calibrate OPT", command = self.calibrate_opt,height = 2)
+    self.calibrateopt_button.pack_forget()
+    
+    self.calibratemag_button = tk.Button(self.controls, text = "Calibrate MAG", command = self.calibrate_mag,height = 2)
+    self.calibratemag_button.pack_forget()
 
-    logscreen_button = tk.Button(self.controls, text="Log", command=self.open_log, height=4)
-    logscreen_button.pack(side="bottom", fill="both")
+    self.clear_button = tk.Button(self.controls, text="Clear graph", command=self.graph.clear, height=2)
+    self.clear_button.pack_forget()
+
+    self.logscreen_button = tk.Button(self.controls, text="Log", command=self.open_log, height=2)
+    self.logscreen_button.pack_forget()
+
+    self.menu_button = tk.Button(self.controls, text="Menu", command=self.menu, height=2)
+    self.menu_button.pack(side="bottom", fill="both")
+    
+    self.menu_back_button = tk.Button(self.controls, text="Back", command=self.back, height=2)
+    self.menu_back_button.pack_forget()
 
   def update(self):
     if not self.root.winfo_exists():
@@ -87,8 +117,17 @@ class MainWindow(tk.Frame):
         opt_positions.append(data[0:2])
         self.opt_dro_val_sums += data
         self.opt_dro_val_count += 1
+    
+    mag_positions = []
+    while self.mag_sensor.queue.qsize() > 0:
+      name, data = self.mag_sensor.queue.get()
+      if name == "data":
+        mag_positions.append(data[0:2])
+        self.mag_dro_val_sums += data
+        self.mag_dro_val_count += 1
+    
     # graph shows all values as a line
-    self.graph.update([ac_positions, opt_positions])
+    self.graph.update([ac_positions, opt_positions, mag_positions])
 
     # update status color
     if self.ac_sensor.dummyActive:
@@ -106,6 +145,14 @@ class MainWindow(tk.Frame):
     else:
       self.opt_label.config(fg="black", bg="yellow")
 
+    if not self.mag_sensor.success:
+     self.mag_label.config(fg="white", bg="red")
+    elif len(mag_positions) > 0:
+     self.mag_label.config(fg="white", bg="green")
+    else:
+     self.mag_label.config(fg="black", bg="yellow")
+
+
     # readouts will only be updated so often
     if self.controlsUpdateTime + 0.4 < time.time():
       self.controlsUpdateTime = time.time()
@@ -120,7 +167,7 @@ class MainWindow(tk.Frame):
       self.ac_dro_t1.set("t1: {:.3f} ms".format(self.ac_dro_val_sums[2]/1000))
       self.ac_dro_t2.set("t2: {:.3f} ms".format(self.ac_dro_val_sums[3]/1000))
 
-      self.ac_dro_val_sums.fill(0)
+      self.ac_dro_val_sums.fill(0)    
       self.ac_dro_val_count = 0
 
       if self.opt_dro_val_count > 0:
@@ -130,13 +177,23 @@ class MainWindow(tk.Frame):
 
       self.opt_dro_x.set("X: {:.1f} mm".format(self.opt_dro_val_sums[0]))
       self.opt_dro_y.set("Y: {:.1f} mm".format(self.opt_dro_val_sums[1]))
-      self.opt_dro_offset.set("offset: {:.1f} %".format(self.opt_dro_val_sums[2]*100))
-      self.opt_dro_size.set("size: {:.1f} %".format(self.opt_dro_val_sums[3]*100))
+      self.opt_dro_offset.set("X Offset: {:.1f} %".format(self.opt_dro_val_sums[2]*100))
+      self.opt_dro_size.set("Y Offset: {:.1f} %".format(self.opt_dro_val_sums[3]*100))
 
       self.opt_dro_val_sums.fill(0)
       self.opt_dro_val_count = 0
 
-    if self.popup_window:
+      if self.mag_dro_val_count > 0:
+        self.mag_dro_val_sums /= self.mag_dro_val_count
+      else:
+        self.mag_dro_val_sums.fill(0)
+
+      self.mag_dro_x.set("X: {:.1f} mm".format(self.mag_dro_val_sums[0]))
+      self.mag_dro_y.set("Y: {:.1f} mm".format(self.mag_dro_val_sums[1]))
+      #self.mag_dro_offset_x.set("X Offset: {:.1f} %".format(self.mag_dro_val_sums[2]*100))
+      #self.mag_dro_offset_y.set("Y Offset: {:.1f} %".format(self.mag_dro_val_sums[3]*100))
+
+    if self.popup_window: # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!~~~~~~~+++*
       self.popup_window.update()
     
     if self.log_window:
@@ -144,6 +201,21 @@ class MainWindow(tk.Frame):
 
     self.root.after(30, self.update)
 
+  def calibrate_submenu(self):
+    
+    self.calibrate_submenu_button.pack_forget()
+    self.menu_button.pack_forget()
+    self.clear_button.pack_forget()
+    self.logscreen_button.pack_forget()
+    self.menu_back_button.pack(side="bottom", fill="both")
+    self.calibratemag_button.pack(side="bottom", fill="both")
+    self.calibrateopt_button.pack(side="bottom", fill="both")
+    self.calibrateac_button.pack(side="bottom", fill="both")
+    self.calibrate_all_button.pack(side="bottom", fill="both")
+  
+  def calibrate_all(self):
+    pass
+  
   def calibrate_ac(self):
     self.ac_sensor.start_calibration()
     if not self.popup_window or not self.pu_root.winfo_exists():
@@ -160,6 +232,25 @@ class MainWindow(tk.Frame):
       self.popup_window = CalibrationPopUp(self.pu_root, self.ac_sensor.calibration_state, self.conf)
       self.popup_window.pack(side="top", fill="both", expand=True)
 
+  def calibrate_opt(self):
+    pass
+  
+  def calibrate_mag(self): ###
+    self.mag_sensor.start_calibration()
+    if not self.popup_window or not self.pu_root.winfo_exists():
+      # create new window
+      self.pu_root = tk.Toplevel(self.root)
+      self.pu_root.wm_transient(self.root)
+      self.pu_root.wm_title("Magnetic Sensor Calibration")
+      # make it centered
+      x = (self.pu_root.winfo_screenwidth()  - 500) / 2
+      y = (self.pu_root.winfo_screenheight() - 200) / 2
+      self.pu_root.geometry(f'500x200+{int(x)}+{int(y)}')
+      # deactivate mainWindow
+      self.pu_root.grab_set()
+      self.popup_window = CalibrationPopUp(self.pu_root, self.mag_sensor.calibration_state, self.conf)
+      self.popup_window.pack(side="top", fill="both", expand=True)
+
   def open_log(self):
     #create new window
     self.log_root = tk.Toplevel(self.root)
@@ -174,3 +265,22 @@ class MainWindow(tk.Frame):
     self.log_window = LogScreen(self.log_root)
     self.log_window.pack(side="top", fill="both", expand=True)
 
+  
+  # Menu Button
+  def menu(self):
+    self.menu_back_button.pack(side="bottom", fill="both")
+    self.calibrate_submenu_button.pack(side="bottom", fill="both")
+    self.clear_button.pack(side="bottom", fill="both")
+    self.logscreen_button.pack(side="bottom", fill="both")
+    self.menu_button.pack_forget()
+
+  #Back Button
+  def back(self):
+    self.calibratemag_button.pack_forget()
+    self.calibrateopt_button.pack_forget()
+    self.calibrateac_button.pack_forget()
+    self.clear_button.pack_forget()
+    self.logscreen_button.pack_forget()
+    self.calibrate_submenu_button.pack_forget()
+    self.menu_button.pack(side="bottom", fill="both")
+    self.menu_back_button.pack_forget()

+ 7 - 5
raspberry-pi/gui/popup.py

@@ -2,6 +2,7 @@
 
 from sensors.acousticSensor import AcousticSensor
 from sensors.opticalSensor import OpticalSensor
+from sensors.magneticSensor import MagneticSensor
 from gui.mainWindow import MainWindow
 
 import configparser
@@ -11,19 +12,22 @@ import logHandler
 
 conf = configparser.ConfigParser()
 conf.read('config.ini')
+print(conf.sections())
 
 def main():
   log_handler = logHandler.get_log_handler(int(conf['gui']['log_lines']))
   ac_sensor = AcousticSensor(conf)
   opt_sensor = OpticalSensor(conf)
+  mag_sensor = MagneticSensor(conf)
 
   try:
     ac_sensor.start()
     opt_sensor.start()
+    mag_sensor.start()
     root = tk.Tk()
     root.title("Tracking System")
     root.attributes('-fullscreen', conf['gui']['fullscreen'] == "yes")
-    view = MainWindow(root, conf, ac_sensor, opt_sensor)
+    view = MainWindow(root, conf, ac_sensor, opt_sensor, mag_sensor)
     view.pack(side="top", fill="both", expand=True)
     view.update()
     root.mainloop()
@@ -36,8 +40,6 @@ def main():
   finally:
     ac_sensor.stop()
     opt_sensor.stop()
+    mag_sensor.stop()
 
-main()
-
-
-
+main()

+ 1 - 0
raspberry-pi/markers.png


+ 21 - 0
raspberry-pi/sensors/calibration.py

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Abhishek Padalkar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 18 - 0
raspberry-pi/sensors/camera_calibration-master/README.md

@@ -0,0 +1,18 @@
+# Calibrate camera with arUco markers using opencv and python. 
+(Code developed and tested on opencv 3.3.1)
+
+
+# camera_calibration
+Code and resources for camera calibration using arUco markers and opencv 
+
+1. Print the aruco marker board provided. (You can generate your own board, see the code "camera_calibration.py")
+2. Take around 50 images of the printed board pasted on a flat card-board, from different angles. Use Use data_generation node for this.
+3. Set path to store images first.
+(See sample images of arUco board in aruco_data folder)
+
+## Calibrating camera
+1. Measure length of the side of individual marker and spacing between two marker.
+2. Input above data (length and spacing) in "camera_calibration.py".
+3. Set path to stored images of aruco marker.
+4. Set calibrate_camera flag to true.
+

BIN
raspberry-pi/sensors/camera_calibration-master/aruco_marker_board.pdf


+ 115 - 0
raspberry-pi/sensors/camera_calibration-master/camera_calibration.py

@@ -0,0 +1,115 @@
+"""
+This code assumes that images used for calibration are of the same arUco marker board provided with code
+
+"""
+
+import cv2
+from cv2 import aruco
+import yaml
+import numpy as np
+from pathlib import Path
+from tqdm import tqdm
+
+# root directory of repo for relative path specification.
+root = Path(__file__).parent.absolute()
+
+# Set this flsg True for calibrating camera and False for validating results real time
+calibrate_camera = True
+
+# Set path to the images
+calib_imgs_path = root.joinpath("aruco_data")
+
+# For validating results, show aruco board to camera.
+aruco_dict = aruco.getPredefinedDictionary( aruco.DICT_6X6_1000 ) # !! Hier Format unserer Marker eingeben
+
+#Provide length of the marker's side
+markerLength = 3.75  # Here, measurement unit is centimetre.
+
+# Provide separation between markers
+markerSeparation = 0.5   # Here, measurement unit is centimetre.
+
+# create arUco board
+board = aruco.GridBoard_create(4, 5, markerLength, markerSeparation, aruco_dict) # !! Das GridBoard könnte man aus opticalSensor.py imortieren? oder einfach nochmal kreieren
+
+'''uncomment following block to draw and show the board'''
+#img = board.draw((864,1080))
+#cv2.imshow("aruco", img)
+
+arucoParams = aruco.DetectorParameters_create()
+
+if calibrate_camera == True:
+    img_list = []
+    calib_fnms = calib_imgs_path.glob('*.jpg') # auf png bzgl. unserer Marker
+    print('Using ...', end='')
+    for idx, fn in enumerate(calib_fnms):
+        print(idx, '', end='')
+        img = cv2.imread( str(root.joinpath(fn) ))
+        img_list.append( img )
+        h, w, c = img.shape
+    print('Calibration images')
+
+    counter, corners_list, id_list = [], [], []
+    first = True
+    for im in tqdm(img_list):
+        img_gray = cv2.cvtColor(im,cv2.COLOR_RGB2GRAY)
+        corners, ids, rejectedImgPoints = aruco.detectMarkers(img_gray, aruco_dict, parameters=arucoParams)
+        if first == True:
+            corners_list = corners
+            id_list = ids
+            first = False
+        else:
+            corners_list = np.vstack((corners_list, corners))
+            id_list = np.vstack((id_list,ids))
+        counter.append(len(ids))
+    print('Found {} unique markers'.format(np.unique(ids)))
+
+    counter = np.array(counter)
+    print ("Calibrating camera .... Please wait...")
+    #mat = np.zeros((3,3), float)
+    ret, mtx, dist, rvecs, tvecs = aruco.calibrateCameraAruco(corners_list, id_list, counter, board, img_gray.shape, None, None )
+
+    print("Camera matrix is \n", mtx, "\n And is stored in calibration.yaml file along with distortion coefficients : \n", dist)
+    data = {'camera_matrix': np.asarray(mtx).tolist(), 'dist_coeff': np.asarray(dist).tolist()}
+    with open("calibration.yaml", "w") as f: # !!!
+        yaml.dump(data, f)
+
+else:
+    camera = cv2.VideoCapture(0)
+    ret, img = camera.read()
+
+    with open('calibration.yaml') as f: # !!!
+        loadeddict = yaml.load(f)
+    mtx = loadeddict.get('camera_matrix')
+    dist = loadeddict.get('dist_coeff')
+    mtx = np.array(mtx)
+    dist = np.array(dist)
+
+    ret, img = camera.read()
+    img_gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
+    h,  w = img_gray.shape[:2]
+    newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
+
+    pose_r, pose_t = [], []
+    while True:
+        ret, img = camera.read()
+        img_aruco = img
+        im_gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
+        h,  w = im_gray.shape[:2]
+        dst = cv2.undistort(im_gray, mtx, dist, None, newcameramtx)
+        corners, ids, rejectedImgPoints = aruco.detectMarkers(dst, aruco_dict, parameters=arucoParams)
+        #cv2.imshow("original", img_gray)
+        if corners == None:
+            print ("pass")
+        else:
+
+            ret, rvec, tvec = aruco.estimatePoseBoard(corners, ids, board, newcameramtx, dist) # For a board
+            print ("Rotation ", rvec, "Translation", tvec)
+            if ret != 0:
+                img_aruco = aruco.drawDetectedMarkers(img, corners, ids, (0,255,0))
+                img_aruco = aruco.drawAxis(img_aruco, newcameramtx, dist, rvec, tvec, 10)    # axis length 100 can be changed according to your requirement
+
+            if cv2.waitKey(0) & 0xFF == ord('q'):
+                break;
+        cv2.imshow("World co-ordinate frame axes", img_aruco)
+
+cv2.destroyAllWindows()

+ 28 - 0
raspberry-pi/sensors/camera_calibration-master/data_generation.py

@@ -0,0 +1,28 @@
+'''This script is for generating data
+1. Provide desired path to store images.
+2. Press 'c' to capture image and display it.
+3. Press any button to continue.
+4. Press 'q' to quit.
+'''
+
+import cv2
+
+camera = cv2.VideoCapture(0)
+ret, img = camera.read()
+
+
+#path = "/home/abhishek/stuff/object_detection/explore/aruco_data/" # !!
+count = 0
+while True:
+    name = path + str(count)+".jpg"
+    ret, img = camera.read()
+    cv2.imshow("img", img)
+
+
+    if cv2.waitKey(20) & 0xFF == ord('c'):
+        cv2.imwrite(name, img)
+        cv2.imshow("img", img)
+        count += 1
+        if cv2.waitKey(0) & 0xFF == ord('q'):
+
+            break;

+ 11 - 0
raspberry-pi/sensors/camera_calibration-master/importante.txt

@@ -0,0 +1,11 @@
+### bzgl.Optische Kalibrierung ###
+
+-   da es schon funktioniert hat, müssen (nur) einige Parameter angepasst werden bzw. der Code so umgeschrieben werden, sodass die distortion coefficients, sowie
+    die camera matrix, nicht in einer .yaml, sondern in der config.ini gespeichert werden
+-   dafür den ConfigParser verwenden
+    Links:  https://docs.python.org/3/library/configparser.html
+            https://wiki.python.org/moin/ConfigParserExamples
+            https://learntutorials.net/de/python/topic/9186/configparser
+-   Wie es genau funktioniert steht in den Comments beider .py-Dateien
+-   Wie man es benutzt, steht in der README.md
+-   (Wenn möglich) data_generation.py & camera_calibration.py als eine datei (nicht pflicht)

BIN
raspberry-pi/sensors/camera_calibration-master/markers.png


+ 9 - 9
raspberry-pi/sensors/connection.py

@@ -124,17 +124,17 @@ class ArduinoSlave(SerialConnection):
   
   def getAccelValues(self):
     return (
-      int(self.sensorData[5]) / 1000,
-      int(self.sensorData[6]) / 1000,
-      int(self.sensorData[7]) / 1000
+      int(self.sensorData[8]) / 1000,
+      int(self.sensorData[9]) / 1000,
+      int(self.sensorData[10]) / 1000,
     )
   
-  def getGyroValues(self):
-    return (
-      int(self.sensorData[8])  / 1000,
-      int(self.sensorData[9])  / 1000,
-      int(self.sensorData[10]) / 1000
-    )
+  #def getGyroValues(self):
+    #return (
+    #  int(self.sensorData[11])  / 1000, #
+    #  int(self.sensorData[12])  / 1000, #
+    #  int(self.sensorData[13]) / 1000  #
+    #)
   
   def getTemperature(self): # in °C
     return int(self.sensorData[11]) / 1000

+ 97 - 8
raspberry-pi/sensors/magneticSensor.py

@@ -1,24 +1,113 @@
+from distutils.command.config import config
+import queue
+import statistics
+from struct import calcsize
+import numpy as np
 import time
-
+from configparser import ConfigParser
+from sensors.calibration import CalibrationStateMashine
 from sensors.connection import globalArduinoSlave
+import logHandler
 
 conn = globalArduinoSlave()
 
 
 class MagneticSensor:
-  def __init__(self):
-    pass
-
+  def __init__(self, conf):
+    self.conf = conf
+    self.queue = queue.Queue()
+    self.calibration_state = CalibrationStateMashine()
+    self.success = False
+    self.offset_x = float(conf["mag_sensor"]["off_x"])
+    self.offset_y = float(conf["mag_sensor"]["off_y"])
+    self.scale_x = float(conf["mag_sensor"]["scale_x"])
+    self.scale_y = float(conf["mag_sensor"]["scale_y"])
+    self.max_x = float(conf["mag_sensor"]["max_x"])
+    self.max_y = float(conf["mag_sensor"]["max_y"])
+    self.log_handler = logHandler.get_log_handler()
+  
   def start(self):
     if not conn.isConnected():
       conn.open()
+    self.success = True
     conn.addRecvCallback(self._readCb)
 
   def _readCb(self, raw):
-    print("mag: ", conn.getMagneticField())
+    value = conn.getMagneticField()
+    if value[0] >= 0 and value[1] >= 0:
+      self.calibrate(value)
+      position = self.calculate_position(value)
+      if position != None:
+        self.pass_to_gui(position + value)
+  
+  def start_calibration(self): ###
+    self.calibration_state.reset_state()
+    self.time_vals = [[],[],[]]
+    self.calibration_state.next_state()
+  
+  def calibrate(self, value): ### öffnet erstmal popup :) # how?
+     if self.calibration_state.get_state() == self.calibration_state.ACCUMULATING_1:
+      self.time_vals[0].append(value[0])
+      self.time_vals[1].append(value[1])
+      self.time_vals[2].append(value[2])
+      self.calibration_state.progress = len(self.time_vals[0]) / 2
+      if len(self.time_vals[0]) >= 100:
+        self.cal_values["front"][0]  = statistics.mean(self.time_vals[0])
+        self.cal_values["front"][1] = statistics.mean(self.time_vals[1])
+        self.cal_values["front"][2] = statistics.mean(self.time_vals[2])
+        self.time_vals = [[],[],[]]
+        self.calibration_state.next_state() # signal gui to get next position
+
+      elif self.calibration_state.get_state() == self.calibration_state.ACCUMULATING_2:
+        self.time_vals[0].append(value[0])
+        self.time_vals[1].append(value[1])
+        self.time_vals[2].append(value[2])
+        self.calibration_state.progress = 50 + len(self.time_vals[0]) / 2
+        if len(self.time_vals[0]) >= 100:
+          self.cal_values["back"][0] = statistics.mean(self.time_vals[0])
+          self.cal_values["back"][1] = statistics.mean(self.time_vals[1])
+          self.cal_values["back"][2] = statistics.mean(self.time_vals[2])
+        
+          # all values have been captured
+          
+          # Hard iron distortion & Soft iron distortion 
+          self.offset_x = (self.cal_values["back"][0] - self.cal_values["front"][0]) / 2
+          self.offset_y = (self.cal_values["back"][1] - self.cal_values["front"][1]) / 2
+
+          avg_delta_x = (self.cal_values["back"][0] - self.cal_values["front"][0]) / 2
+          avg_delta_y = (self.cal_values["back"][1] - self.cal_values["front"][1]) / 2
+          avg_delta_z = (self.cal_values["back"][2] - self.cal_values["front"][2]) / 2
+
+          avg_delta = (avg_delta_x + avg_delta_y + avg_delta_z) / 3
 
-  def calibrate(self, x, y):
-    pass
+          self.scale_x = avg_delta / avg_delta_x
+          self.scale_y = avg_delta / avg_delta_y
+          self.scale_z = avg_delta / avg_delta_z
+
+          # max values for placeholder algorithm
+          self.max_x = (self.cal_values["back"][0] - self.offset_x) * self.scale_x
+          self.max_y = (self.cal_values["back"][1] - self.offset_y) * self.scale_y
+        
+          self.calibration_state.next_state()
+
+          return self.offset_x, self.offset_y, self.scale_x, self.scale_y, self.max_x, self.max_y
 
   def read(self):
-    return conn.getMagneticField()
+    value = conn.getMagneticField()
+    return value
+  
+  def calculate_position(self): ###
+    corrected_x = (conn.getMagneticField[0] - self.offset_x) * self.scale_x
+    corrected_y = (conn.getMagneticField[1] - self.offset_y) * self.scale_y
+    # placeholder algorithm (to see if the sensor even works)
+    x = (corrected_x * 400) / self.max_x
+    y = (corrected_y * 400) / self.max_y
+    return (x, y)
+
+  def stop(self):
+    self.log_handler.log_and_print("stop magnetic sensor")
+    self.success = False
+    conn.close()
+
+  def pass_to_gui(self, data):
+    self.queue.put(("data", data))

+ 12 - 0
raspberry-pi/sensors/opticalSensor.py

@@ -0,0 +1,12 @@
+{
+	"folders": [
+		{
+			"path": "../.."
+		},
+		{
+			"name": "arduino",
+			"path": "../../arduino"
+		}
+	],
+	"settings": {}
+}

+ 3 - 0
start.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/bash
+cd ./raspberry-pi
+python3 main.py

+ 0 - 0
ultrasound-tests.ods


Some files were not shown because too many files changed in this diff