diff --git a/lib/bno080/BNO080.cpp b/lib/bno080/BNO080.cpp index c21dc765d..430712466 100644 --- a/lib/bno080/BNO080.cpp +++ b/lib/bno080/BNO080.cpp @@ -43,14 +43,14 @@ //Attempt communication with the device //Return true if we got a 'Polo' back from Marco -boolean BNO080::begin(uint8_t deviceAddress, TwoWire &wirePort, uint8_t intPin) +boolean BNO080::begin(uint8_t deviceAddress, TwoWire &wirePort, PinInterface* intPin) { _deviceAddress = deviceAddress; //If provided, store the I2C address from user _i2cPort = &wirePort; //Grab which port the user wants us to use _int = intPin; //Get the pin that the user wants to use for interrupts. By default, it's 255 and we'll not use it in dataAvailable() function. - if (_int != 255) + if (_int != nullptr) { - pinMode(_int, INPUT_PULLUP); + _int->pinMode(INPUT_PULLUP); } //We expect caller to begin their I2C port, with the speed of their choice external to the library @@ -99,7 +99,7 @@ boolean BNO080::begin(uint8_t deviceAddress, TwoWire &wirePort, uint8_t intPin) return tBoardInfoReceived; } -boolean BNO080::beginSPI(uint8_t user_CSPin, uint8_t user_WAKPin, uint8_t user_INTPin, uint8_t user_RSTPin, uint32_t spiPortSpeed, SPIClass &spiPort) +boolean BNO080::beginSPI(PinInterface* user_CSPin, PinInterface* user_WAKPin, PinInterface* user_INTPin, PinInterface* user_RSTPin, uint32_t spiPortSpeed, SPIClass &spiPort) { _i2cPort = NULL; //This null tells the send/receive functions to use SPI @@ -114,18 +114,18 @@ boolean BNO080::beginSPI(uint8_t user_CSPin, uint8_t user_WAKPin, uint8_t user_I _int = user_INTPin; _rst = user_RSTPin; - pinMode(_cs, OUTPUT); - pinMode(_wake, OUTPUT); - pinMode(_int, INPUT_PULLUP); - pinMode(_rst, OUTPUT); + _cs->pinMode(OUTPUT); + _wake->pinMode(OUTPUT); + _int->pinMode(INPUT_PULLUP); + _rst->pinMode(OUTPUT); - digitalWrite(_cs, HIGH); //Deselect BNO080 + _cs->digitalWrite(HIGH); //Deselect BNO080 //Configure the BNO080 for SPI communication - digitalWrite(_wake, HIGH); //Before boot up the PS0/WAK pin must be high to enter SPI mode - digitalWrite(_rst, LOW); //Reset BNO080 + _wake->digitalWrite(HIGH); //Before boot up the PS0/WAK pin must be high to enter SPI mode + _rst->digitalWrite(LOW); //Reset BNO080 delay(2); //Min length not specified in datasheet? - digitalWrite(_rst, HIGH); //Bring out of reset + _rst->digitalWrite(HIGH); //Bring out of reset //Wait for first assertion of INT before using WAK pin. Can take ~104ms waitForSPI(); @@ -203,9 +203,9 @@ uint16_t BNO080::getReadings(void) //If we have an interrupt pin connection available, check if data is available. //If int pin is not set, then we'll rely on receivePacket() to timeout //See issue 13: https://github.com/sparkfun/SparkFun_BNO080_Arduino_Library/issues/13 - if (_int != 255) + if (_int != nullptr) { - if (digitalRead(_int) == HIGH) + if (_int->digitalRead() == HIGH) return 0; } @@ -256,6 +256,13 @@ uint16_t BNO080::parseCommandReport(void) if (command == COMMAND_ME_CALIBRATE) { calibrationStatus = shtpData[5 + 0]; //R0 - Status (0 = success, non-zero = fail) + _calibrationResponseStatus = shtpData[5 + 0]; + _accelCalEnabled = shtpData[6 + 0]; + _gyroCalEnabled = shtpData[7 + 0]; + _magCalEnabled = shtpData[8 + 0]; + _planarAccelCalEnabled = shtpData[9 + 0]; + _onTableCalEnabled = shtpData[10 + 0]; + _hasNewCliabrationStatus = true; } return shtpData[0]; } @@ -1381,6 +1388,7 @@ void BNO080::sendCalibrateCommand(uint8_t thingToCalibrate) //Make the internal calStatus variable non-zero (operation failed) so that user can test while we wait calibrationStatus = 1; + _hasNewCliabrationStatus = false; //Using this shtpData packet, send a command sendCommand(COMMAND_ME_CALIBRATE); @@ -1404,7 +1412,7 @@ void BNO080::requestCalibrationStatus() shtpData[x] = 0; shtpData[6] = 0x01; //P3 - 0x01 - Subcommand: Get ME Calibration - + _hasNewCliabrationStatus = false; //Using this shtpData packet, send a command sendCommand(COMMAND_ME_CALIBRATE); } @@ -1430,6 +1438,43 @@ void BNO080::saveCalibration() sendCommand(COMMAND_DCD); //Save DCD command } +void BNO080::saveCalibrationPeriodically(bool save) +{ + /*shtpData[3] = 0; //P0 - Enable/Disable Periodic DCD Save + shtpData[4] = 0; //P1 - Reserved + shtpData[5] = 0; //P2 - Reserved + shtpData[6] = 0; //P3 - Reserved + shtpData[7] = 0; //P4 - Reserved + shtpData[8] = 0; //P5 - Reserved + shtpData[9] = 0; //P6 - Reserved + shtpData[10] = 0; //P7 - Reserved + shtpData[11] = 0; //P8 - Reserved*/ + + for (uint8_t x = 3; x < 12; x++) //Clear this section of the shtpData array + shtpData[x] = 0; + shtpData[3] = save ? 1 : 0; + + //Using this shtpData packet, send a command + sendCommand(COMMAND_DCD_PERIOD_SAVE); //Save DCD command +} + +bool BNO080::hasNewCliabrationStatus() +{ + return _hasNewCliabrationStatus; +} + +void BNO080::getCalibrationStatus(uint8_t &calibrationResponseStatus, uint8_t &accelCalEnabled, uint8_t &gyroCalEnabled, uint8_t &magCalEnabled, uint8_t &planarAccelCalEnabled, uint8_t &onTableCalEnabled) +{ + _hasNewCliabrationStatus = false; + calibrationResponseStatus = _calibrationResponseStatus; + accelCalEnabled = _accelCalEnabled; + gyroCalEnabled = _gyroCalEnabled; + magCalEnabled = _magCalEnabled; + planarAccelCalEnabled = _planarAccelCalEnabled; + onTableCalEnabled = _onTableCalEnabled; +} + + //Wait a certain time for incoming I2C bytes before giving up //Returns false if failed boolean BNO080::waitForI2C() @@ -1460,7 +1505,7 @@ boolean BNO080::waitForSPI() { for (uint8_t counter = 0; counter < 125; counter++) //Don't got more than 255 { - if (digitalRead(_int) == LOW) + if (_int->digitalRead() == LOW) return (true); if (_printDebug == true) _debugPort->println(F("SPI Wait")); @@ -1478,7 +1523,7 @@ boolean BNO080::receivePacket(void) { if (_i2cPort == NULL) //Do SPI { - if (digitalRead(_int) == HIGH) + if (_int->digitalRead() == HIGH) return (false); //Data is not available //Old way: if (waitForSPI() == false) return (false); //Something went wrong @@ -1486,7 +1531,7 @@ boolean BNO080::receivePacket(void) //Get first four bytes to find out how much data we need to read _spiPort->beginTransaction(SPISettings(_spiPortSpeed, MSBFIRST, SPI_MODE3)); - digitalWrite(_cs, LOW); + _cs->digitalWrite(LOW); //Get the first four bytes, aka the packet header uint8_t packetLSB = _spiPort->transfer(0); @@ -1521,7 +1566,7 @@ boolean BNO080::receivePacket(void) shtpData[dataSpot] = incoming; //Store data into the shtpData array } - digitalWrite(_cs, HIGH); //Release BNO080 + _cs->digitalWrite(HIGH); //Release BNO080 _spiPort->endTransaction(); printPacket(); @@ -1627,7 +1672,7 @@ boolean BNO080::sendPacket(uint8_t channelNumber, uint8_t dataLength) //BNO080 has max CLK of 3MHz, MSB first, //The BNO080 uses CPOL = 1 and CPHA = 1. This is mode3 _spiPort->beginTransaction(SPISettings(_spiPortSpeed, MSBFIRST, SPI_MODE3)); - digitalWrite(_cs, LOW); + _cs->digitalWrite(LOW); //Send the 4 byte packet header _spiPort->transfer(packetLength & 0xFF); //Packet length LSB @@ -1641,7 +1686,7 @@ boolean BNO080::sendPacket(uint8_t channelNumber, uint8_t dataLength) _spiPort->transfer(shtpData[i]); } - digitalWrite(_cs, HIGH); + _cs->digitalWrite(HIGH); _spiPort->endTransaction(); } else //Do I2C diff --git a/lib/bno080/BNO080.h b/lib/bno080/BNO080.h index 11660aa84..7c1339b8f 100644 --- a/lib/bno080/BNO080.h +++ b/lib/bno080/BNO080.h @@ -49,6 +49,8 @@ #include #include +#include +#include "PinInterface.h" //The default I2C address for the BNO080 on the SparkX breakout is 0x4B. 0x4A is also possible. #define BNO080_DEFAULT_ADDRESS 0x4B @@ -150,8 +152,8 @@ struct BNO080Error { class BNO080 { public: - boolean begin(uint8_t deviceAddress = BNO080_DEFAULT_ADDRESS, TwoWire &wirePort = Wire, uint8_t intPin = 255); //By default use the default I2C addres, and use Wire port, and don't declare an INT pin - boolean beginSPI(uint8_t user_CSPin, uint8_t user_WAKPin, uint8_t user_INTPin, uint8_t user_RSTPin, uint32_t spiPortSpeed = 3000000, SPIClass &spiPort = SPI); + boolean begin(uint8_t deviceAddress = BNO080_DEFAULT_ADDRESS, TwoWire &wirePort = Wire, PinInterface* intPin = nullptr); //By default use the default I2C addres, and use Wire port, and don't declare an INT pin + boolean beginSPI(PinInterface* user_CSPin, PinInterface* user_WAKPin, PinInterface* user_INTPin, PinInterface* user_RSTPin, uint32_t spiPortSpeed = 3000000, SPIClass &spiPort = SPI); void enableDebugging(Stream &debugPort = Serial); //Turn on debug printing. If user doesn't specify then Serial will be used. @@ -237,11 +239,13 @@ class BNO080 float getMagY(); float getMagZ(); uint8_t getMagAccuracy(); - + void endCalibration(); void saveCalibration(); void requestCalibrationStatus(); //Sends command to get status - boolean calibrationComplete(); //Checks ME Cal response for byte 5, R0 - Status + bool calibrationComplete(); //Checks ME Cal response for byte 5, R0 - Status + bool hasNewCliabrationStatus(); + void getCalibrationStatus(uint8_t &calibrationResponseStatus, uint8_t &accelCalEnabled, uint8_t &gyroCalEnabled, uint8_t &magCalEnabled, uint8_t &planarAccelCalEnabled, uint8_t &onTableCalEnabled); uint8_t getTapDetector(); bool getTapDetected(); @@ -270,6 +274,7 @@ class BNO080 void setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig); void sendCommand(uint8_t command); void sendCalibrateCommand(uint8_t thingToCalibrate); + void saveCalibrationPeriodically(bool save); //Metadata functions int16_t getQ1(uint16_t recordID); @@ -308,10 +313,10 @@ class BNO080 SPIClass *_spiPort; //The generic connection to user's chosen SPI hardware unsigned long _spiPortSpeed; //Optional user defined port speed - uint8_t _cs; //Pins needed for SPI - uint8_t _wake; - uint8_t _int; - uint8_t _rst; + PinInterface* _cs; //Pins needed for SPI + PinInterface* _wake; + PinInterface* _int; + PinInterface* _rst; //These are the raw sensor values (without Q applied) pulled from the user requested Input Report uint16_t rawAccelX, rawAccelY, rawAccelZ, accelAccuracy; @@ -344,4 +349,7 @@ class BNO080 int16_t gyro_Q1 = 9; int16_t magnetometer_Q1 = 4; int16_t angular_velocity_Q1 = 10; + + bool _hasNewCliabrationStatus = false; + uint8_t _calibrationResponseStatus, _accelCalEnabled, _gyroCalEnabled, _magCalEnabled, _planarAccelCalEnabled, _onTableCalEnabled; }; diff --git a/lib/bno080/PinInterface.h b/lib/bno080/PinInterface.h new file mode 100644 index 000000000..f681b699b --- /dev/null +++ b/lib/bno080/PinInterface.h @@ -0,0 +1,37 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 _H_PIN_INTERFACE_ +#define _H_PIN_INTERFACE_ + +#include + +class PinInterface +{ +public: + virtual int digitalRead(); + virtual void pinMode(uint8_t mode); + virtual void digitalWrite(uint8_t val); + +}; + +#endif // _H_PIN_INTERFACE_ \ No newline at end of file diff --git a/platformio-tools.ini b/platformio-tools.ini index 8b2d8dac3..3098d22a4 100644 --- a/platformio-tools.ini +++ b/platformio-tools.ini @@ -2,6 +2,8 @@ lib_deps= https://github.com/SlimeVR/CmdParser.git https://github.com/SlimeVR/base64_arduino.git + https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library.git + https://github.com/hideakitai/PCA9547.git monitor_speed = 115200 framework = arduino build_flags = diff --git a/platformio.ini b/platformio.ini index fb654067f..b7450101c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,6 +18,8 @@ build_cache_dir = cache lib_deps= https://github.com/SlimeVR/CmdParser.git https://github.com/SlimeVR/base64_arduino.git + https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library.git + https://github.com/hideakitai/PCA9547.git monitor_speed = 115200 monitor_echo = yes monitor_filters = colorize @@ -109,6 +111,7 @@ upload_speed = 921600 ; ${env.build_flags} ; -DESP32C3 ;board = lolin_c3_mini +;monitor_filters = colorize, esp32_exception_decoder ; If you want to use a ESP32C6, you can use this (experimental) ;[env:esp32c6] @@ -118,4 +121,4 @@ upload_speed = 921600 ; ${env.build_flags} ; -DESP32C6 ; -DARDUINO_USB_MODE=1 -; -DARDUINO_USB_CDC_ON_BOOT=1 \ No newline at end of file +; -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/src/consts.h b/src/consts.h index 94b3edc67..1bbfc26e0 100644 --- a/src/consts.h +++ b/src/consts.h @@ -42,6 +42,7 @@ enum class ImuID { LSM6DSV, LSM6DSO, LSM6DSR, + ADC_RESISTANCE, Empty = 255 }; @@ -85,6 +86,7 @@ enum class ImuID { #define BOARD_XIAO_ESP32C3 17 #define BOARD_HARITORA 18 // Used by Haritora/SlimeTora #define BOARD_ES32C6DEVKITC1 19 +#define BOARD_GLOVE_IMU_SLIMEVR_DEV 20 // IMU Glove #define BOARD_DEV_RESERVED 250 // Reserved, should not be used in any release firmware #define BAT_EXTERNAL 1 @@ -143,6 +145,14 @@ enum class ImuID { #define MCU_HARITORA 8 // Used by Haritora/SlimeTora #define MCU_DEV_RESERVED 250 // Reserved, should not be used in any release firmware +#define SENSOR_DATATYPE_ROTATION 0 +#define SENSOR_DATATYPE_FLEX_RESISTANCE 1 +#define SENSOR_DATATYPE_FLEX_ANGLE 2 + +#define TRACKER_TYPE_SVR_ROTATION 0 +#define TRACKER_TYPE_SVR_GLOVE_LEFT 1 +#define TRACKER_TYPE_SVR_GLOVE_RIGHT 1 + #ifdef ESP8266 #define HARDWARE_MCU MCU_ESP8266 #elif defined(ESP32) @@ -153,4 +163,6 @@ enum class ImuID { #define CURRENT_CONFIGURATION_VERSION 1 +#include "sensors/sensorposition.h" + #endif // SLIMEVR_CONSTS_H_ diff --git a/src/debug.h b/src/debug.h index 79cc6ec52..1f26a31d7 100644 --- a/src/debug.h +++ b/src/debug.h @@ -94,7 +94,7 @@ // Not recommended for production #define ENABLE_INSPECTION false -#define PROTOCOL_VERSION 18 +#define PROTOCOL_VERSION 19 #ifndef FIRMWARE_VERSION #define FIRMWARE_VERSION "UNKNOWN" diff --git a/src/defines.h b/src/defines.h index 992c25391..3dc283e29 100644 --- a/src/defines.h +++ b/src/defines.h @@ -35,45 +35,179 @@ #define PRIMARY_IMU_OPTIONAL false #define SECONDARY_IMU_OPTIONAL true +#if BOARD != BOARD_GLOVE_IMU_SLIMEVR_DEV +#define MAX_SENSORS_COUNT 2 +#define TRACKER_TYPE TRACKER_TYPE_SVR_ROTATION // Set I2C address here or directly in IMU_DESC_ENTRY for each IMU used // If not set, default address is used based on the IMU and Sensor ID // #define PRIMARY_IMU_ADDRESS_ONE 0x4a // #define SECONDARY_IMU_ADDRESS_TWO 0x4b -#define MAX_IMU_COUNT 2 - // Axis mapping example /* #include "sensors/axisremap.h" #define BMI160_QMC_REMAP AXIS_REMAP_BUILD(AXIS_REMAP_USE_Y, AXIS_REMAP_USE_XN, AXIS_REMAP_USE_Z, \ AXIS_REMAP_USE_YN, AXIS_REMAP_USE_X, AXIS_REMAP_USE_Z) -IMU_DESC_ENTRY(IMU_BMP160, PRIMARY_IMU_ADDRESS_ONE, IMU_ROTATION, PIN_IMU_SCL, +SENSOR_DESC_ENTRY(IMU_BMP160, PRIMARY_IMU_ADDRESS_ONE, IMU_ROTATION, PIN_IMU_SCL, PIN_IMU_SDA, PRIMARY_IMU_OPTIONAL, BMI160_QMC_REMAP) \ */ -#ifndef IMU_DESC_LIST -#define IMU_DESC_LIST \ - IMU_DESC_ENTRY( \ - IMU, \ - PRIMARY_IMU_ADDRESS_ONE, \ - IMU_ROTATION, \ - PIN_IMU_SCL, \ - PIN_IMU_SDA, \ - PRIMARY_IMU_OPTIONAL, \ - PIN_IMU_INT \ - ) \ - IMU_DESC_ENTRY( \ - SECOND_IMU, \ - SECONDARY_IMU_ADDRESS_TWO, \ - SECOND_IMU_ROTATION, \ - PIN_IMU_SCL, \ - PIN_IMU_SDA, \ - SECONDARY_IMU_OPTIONAL, \ - PIN_IMU_INT_2 \ +#ifndef SENSOR_DESC_LIST +#define SENSOR_DESC_LIST \ + SENSOR_DESC_ENTRY( \ + IMU, \ + PRIMARY_IMU_ADDRESS_ONE, \ + IMU_ROTATION, \ + DIRECT_WIRE(PIN_IMU_SCL, PIN_IMU_SDA), \ + PRIMARY_IMU_OPTIONAL, \ + DIRECT_PIN(PIN_IMU_INT), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + SECOND_IMU, \ + SECONDARY_IMU_ADDRESS_TWO, \ + SECOND_IMU_ROTATION, \ + DIRECT_WIRE(PIN_IMU_SCL, PIN_IMU_SDA), \ + SECONDARY_IMU_OPTIONAL, \ + DIRECT_PIN(PIN_IMU_INT_2), \ + 0 \ ) #endif +#else + +// Predefines for the GLOVE +#ifndef SENSOR_DESC_LIST +#define MAX_SENSORS_COUNT 10 +#define TRACKER_TYPE TRACKER_TYPE_SVR_GLOVE_LEFT +#define GLOVE_SIDE GLOVE_LEFT +#define PRIMARY_IMU_ADDRESS_ONE 0x4a +#define SECONDARY_IMU_ADDRESS_TWO 0x4b + +#define SENSOR_DESC_LIST \ + SENSOR_DESC_ENTRY( \ + IMU, \ + (PRIMARY_IMU_ADDRESS_ONE ^ 0x02), \ + IMU_ROTATION, \ + DIRECT_WIRE(PIN_IMU_SCL, PIN_IMU_SDA), \ + false, \ + MCP_PIN(MCP_GPA6), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + (SECONDARY_IMU_ADDRESS_TWO ^ 0x02), \ + IMU_ROTATION, \ + DIRECT_WIRE(PIN_IMU_SCL, PIN_IMU_SDA), \ + true, \ + MCP_PIN(MCP_GPA5), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + PRIMARY_IMU_ADDRESS_ONE, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 0), \ + true, \ + MCP_PIN(MCP_GPB0), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + SECONDARY_IMU_ADDRESS_TWO, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 0), \ + true, \ + MCP_PIN(MCP_GPB1), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + PRIMARY_IMU_ADDRESS_ONE, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 1), \ + true, \ + MCP_PIN(MCP_GPB2), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + SECONDARY_IMU_ADDRESS_TWO, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 1), \ + true, \ + MCP_PIN(MCP_GPB3), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + PRIMARY_IMU_ADDRESS_ONE, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 2), \ + true, \ + MCP_PIN(MCP_GPB4), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + SECONDARY_IMU_ADDRESS_TWO, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 2), \ + true, \ + MCP_PIN(MCP_GPB5), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + PRIMARY_IMU_ADDRESS_ONE, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 3), \ + true, \ + MCP_PIN(MCP_GPB6), \ + 0 \ + ) \ + SENSOR_DESC_ENTRY( \ + IMU, \ + SECONDARY_IMU_ADDRESS_TWO, \ + IMU_ROTATION, \ + PCA_WIRE(PIN_IMU_SCL, PIN_IMU_SDA, PCA_ADDR, 3), \ + true, \ + MCP_PIN(MCP_GPA1), \ + 0 \ + ) + +#if GLOVE_SIDE == GLOVE_LEFT +#define SENSOR_INFO_LIST \ + SENSOR_INFO_ENTRY(0, POSITION_LEFT_HAND) \ + SENSOR_INFO_ENTRY(1, POSITION_LEFT_LITTLE_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(2, POSITION_LEFT_RING_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(3, POSITION_LEFT_RING_DISTAL) \ + SENSOR_INFO_ENTRY(4, POSITION_LEFT_MIDDLE_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(5, POSITION_LEFT_MIDDLE_DISTAL) \ + SENSOR_INFO_ENTRY(6, POSITION_LEFT_INDEX_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(7, POSITION_LEFT_INDEX_DISTAL) \ + SENSOR_INFO_ENTRY(8, POSITION_LEFT_THUMB_PROXIMAL) \ + SENSOR_INFO_ENTRY(9, POSITION_LEFT_THUMB_DISTAL) +#elif GLOVE_SDIE == GLOVE_RIGHT +#define SENSOR_INFO_LIST \ + SENSOR_INFO_ENTRY(0, POSITION_RIGHT_HAND) \ + SENSOR_INFO_ENTRY(1, POSITION_RIGHT_LITTLE_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(2, POSITION_RIGHT_RING_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(3, POSITION_RIGHT_RING_DISTAL) \ + SENSOR_INFO_ENTRY(4, POSITION_RIGHT_MIDDLE_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(5, POSITION_RIGHT_MIDDLE_DISTAL) \ + SENSOR_INFO_ENTRY(6, POSITION_RIGHT_INDEX_INTERMEDIATE) \ + SENSOR_INFO_ENTRY(7, POSITION_RIGHT_INDEX_DISTAL) \ + SENSOR_INFO_ENTRY(8, POSITION_RIGHT_THUMB_PROXIMAL) \ + SENSOR_INFO_ENTRY(9, POSITION_RIGHT_THUMB_DISTAL) +#else // GLOVE_SDIE +#error "Glove side not defined" +#endif // GLOVE_SDIE + +#endif // SENSOR_DESC_LIST +#endif // BOARD != BOARD_GLOVE_IMU_SLIMEVR_DEV + // Battery monitoring options (comment to disable): // BAT_EXTERNAL for ADC pin, // BAT_INTERNAL for internal - can detect only low battery, @@ -232,4 +366,22 @@ PIN_IMU_SDA, PRIMARY_IMU_OPTIONAL, BMI160_QMC_REMAP) \ #ifndef BATTERY_SHIELD_R2 #define BATTERY_SHIELD_R2 100 #endif +#elif BOARD == BOARD_GLOVE_IMU_SLIMEVR_DEV +#define PIN_IMU_SDA 1 +#define PIN_IMU_SCL 0 +#define PCA_ADDR 0x70 +#define PIN_IMU_INT 16 +#define PIN_IMU_INT_2 13 +#define PIN_BATTERY_LEVEL 3 +#define LED_PIN 2 +#define LED_INVERTED true +#ifndef BATTERY_SHIELD_RESISTANCE +#define BATTERY_SHIELD_RESISTANCE 0 +#endif +#ifndef BATTERY_SHIELD_R1 +#define BATTERY_SHIELD_R1 10 +#endif +#ifndef BATTERY_SHIELD_R2 +#define BATTERY_SHIELD_R2 40.2 +#endif #endif diff --git a/src/globals.h b/src/globals.h index 67eaac12e..447e1f967 100644 --- a/src/globals.h +++ b/src/globals.h @@ -76,4 +76,8 @@ #define LED__OFF LOW #endif +#ifndef SENSOR_INFO_LIST +#define SENSOR_INFO_LIST +#endif + #endif // SLIMEVR_GLOBALS_H_ diff --git a/src/main.cpp b/src/main.cpp index 36bc0f045..1cea335c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ #include "logging/Logger.h" #include "ota.h" #include "serial/serialcommands.h" +#include "status/TPSCounter.h" Timer<> globalTimer; SlimeVR::Logging::Logger logger("SlimeVR"); @@ -48,8 +49,14 @@ unsigned long loopTime = 0; unsigned long lastStatePrint = 0; bool secondImuActive = false; BatteryMonitor battery; +TPSCounter tpsCounter; void setup() { + // For some reason, serial on C3 just doesn't work without the delay + // It can probably be less, but it needs investigation +#ifdef ESP32C3 + delay(3000); +#endif Serial.begin(serialBaudRate); globalTimer = timer_create_default(); @@ -65,14 +72,15 @@ void setup() { configuration.setup(); SerialCommands::setUp(); - - I2CSCAN::clearBus( - PIN_IMU_SDA, - PIN_IMU_SCL - ); // Make sure the bus isn't stuck when resetting ESP without powering it down + // Make sure the bus isn't stuck when resetting ESP without powering it down // Fixes I2C issues for certain IMUs. Previously this feature was enabled for // selected IMUs, now it's enabled for all. If some IMU turned out to be broken by // this, check needs to be re-added. + auto clearResult = I2CSCAN::clearBus(PIN_IMU_SDA, PIN_IMU_SCL); + if (clearResult != 0) { + logger.error("Can't clear I2C bus, error %d", clearResult); + delay(60000); + } // join I2C bus @@ -108,9 +116,11 @@ void setup() { sensorManager.postSetup(); loopTime = micros(); + tpsCounter.reset(); } void loop() { + tpsCounter.update(); globalTimer.tick(); SerialCommands::update(); OTA::otaUpdate(); diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 36dec41bc..c158f9dba 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -297,6 +297,15 @@ void Connection::sendSensorInfo(Sensor& sensor) { MUST(sendByte(static_cast(sensor.getSensorState()))); MUST(sendByte(static_cast(sensor.getSensorType()))); MUST(sendShort(sensor.getSensorConfigData())); + MUST(sendByte(sensor.getSensorPosition())); + MUST(sendByte(sensor.getDataType())); + // ADD NEW FILEDS ABOVE THIS COMMENT ^^^^^^^^ + // WARNING! Only for debug purposes and SHOULD ALWAYS BE LAST IN THE PACKET. + // It WILL BE REMOVED IN THE FUTURE + // ADD NEW FILEDS ABOVE THIS COMMENT ^^^^^^^^ + // Send TPS + MUST(sendFloat(sensor.m_tpsCounter.getAveragedTPS())); + MUST(sendFloat(sensor.m_dataCounter.getAveragedTPS())); MUST(endPacket()); } @@ -419,6 +428,22 @@ void Connection::sendTrackerDiscovery() { MUST(sendShortString(FIRMWARE_VERSION)); // MAC address string MUST(sendBytes(mac, 6)); + MUST(sendByte(TRACKER_TYPE)); // Tracker type to hint the server if it's a glove or + // normal tracker or something else + + MUST(endPacket()); +} + +// PACKET_FLEX_DATA 24 +void Connection::sendFlexData(uint8_t sensorId, float flexLevel) { + MUST(m_Connected); + + MUST(beginPacket()); + + MUST(sendPacketType(PACKET_FLEX_DATA)); + MUST(sendPacketNumber()); + MUST(sendByte(sensorId)); + MUST(sendFloat(flexLevel)); MUST(endPacket()); } @@ -621,7 +646,7 @@ void Connection::reset() { m_Connected = false; std::fill( m_AckedSensorState, - m_AckedSensorState + MAX_IMU_COUNT, + m_AckedSensorState + MAX_SENSORS_COUNT, SensorStatus::SENSOR_OFFLINE ); @@ -631,23 +656,23 @@ void Connection::reset() { } void Connection::update() { - auto& sensors = sensorManager.getSensors(); - - updateSensorState(sensors); - maybeRequestFeatureFlags(); - if (!m_Connected) { searchForServer(); return; } + auto& sensors = sensorManager.getSensors(); + + updateSensorState(sensors); + maybeRequestFeatureFlags(); + if (m_LastPacketTimestamp + TIMEOUT < millis()) { statusManager.setStatus(SlimeVR::Status::SERVER_CONNECTING, true); m_Connected = false; std::fill( m_AckedSensorState, - m_AckedSensorState + MAX_IMU_COUNT, + m_AckedSensorState + MAX_SENSORS_COUNT, SensorStatus::SENSOR_OFFLINE ); m_Logger.warn("Connection to server timed out"); diff --git a/src/network/connection.h b/src/network/connection.h index 1b4240024..ade4f455f 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -84,6 +84,9 @@ class Connection { // PACKET_FEATURE_FLAGS 22 void sendFeatureFlags(); + // PACKET_FLEX_DATA 24 + void sendFlexData(uint8_t sensorId, float flexLevel); + #if ENABLE_INSPECTION void sendInspectionRawIMUData( uint8_t sensorId, @@ -170,7 +173,7 @@ class Connection { unsigned long m_LastConnectionAttemptTimestamp; unsigned long m_LastPacketTimestamp; - SensorStatus m_AckedSensorState[MAX_IMU_COUNT] = {SensorStatus::SENSOR_OFFLINE}; + SensorStatus m_AckedSensorState[MAX_SENSORS_COUNT] = {SensorStatus::SENSOR_OFFLINE}; unsigned long m_LastSensorInfoPacketTimestamp = 0; uint8_t m_FeatureFlagsRequestAttempts = 0; diff --git a/src/network/packets.h b/src/network/packets.h index f6c3d9b53..1065c76c7 100644 --- a/src/network/packets.h +++ b/src/network/packets.h @@ -51,6 +51,7 @@ // packet #define PACKET_ACKNOWLEDGE_CONFIG_CHANGE 24 #define PACKET_SET_CONFIG_FLAG 25 +#define PACKET_FLEX_DATA 26 #define PACKET_BUNDLE 100 diff --git a/src/sensorinterface/DirectPinInterface.cpp b/src/sensorinterface/DirectPinInterface.cpp new file mode 100644 index 000000000..dc89c4165 --- /dev/null +++ b/src/sensorinterface/DirectPinInterface.cpp @@ -0,0 +1,29 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 "DirectPinInterface.h" + +int DirectPinInterface::digitalRead() { return ::digitalRead(_pinNum); } + +void DirectPinInterface::pinMode(uint8_t mode) { ::pinMode(_pinNum, mode); } + +void DirectPinInterface::digitalWrite(uint8_t val) { ::digitalWrite(_pinNum, val); } \ No newline at end of file diff --git a/src/sensorinterface/DirectPinInterface.h b/src/sensorinterface/DirectPinInterface.h new file mode 100644 index 000000000..56ae9ed80 --- /dev/null +++ b/src/sensorinterface/DirectPinInterface.h @@ -0,0 +1,46 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 _H_DIRECT_PIN_INTERFACE_ +#define _H_DIRECT_PIN_INTERFACE_ + +#include +#include + +/** + * Pin interface using direct pins + * + */ +class DirectPinInterface : public PinInterface { +public: + DirectPinInterface(uint8_t pin) + : _pinNum(pin){}; + + int digitalRead() override final; + void pinMode(uint8_t mode) override final; + void digitalWrite(uint8_t val) override final; + +private: + uint8_t _pinNum; +}; + +#endif // _H_DIRECT_PIN_INTERFACE_ diff --git a/src/sensorinterface/I2CPCAInterface.cpp b/src/sensorinterface/I2CPCAInterface.cpp new file mode 100644 index 000000000..b0206dcb9 --- /dev/null +++ b/src/sensorinterface/I2CPCAInterface.cpp @@ -0,0 +1,37 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR Contributors + + 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 "I2CPCAInterface.h" + +void SlimeVR::I2CPCASensorInterface::init() { m_Wire.init(); } + +void SlimeVR::I2CPCASensorInterface::swapIn() { + m_Wire.swapIn(); + Wire.beginTransmission(m_Address); + Wire.write(1 << m_Channel); + Wire.endTransmission(); +#if ESP32 + // On ESP32 we need to reconnect to I2C bus for some reason + m_Wire.disconnect(); + m_Wire.swapIn(); +#endif +} diff --git a/src/sensorinterface/I2CPCAInterface.h b/src/sensorinterface/I2CPCAInterface.h new file mode 100644 index 000000000..ef5a278e6 --- /dev/null +++ b/src/sensorinterface/I2CPCAInterface.h @@ -0,0 +1,57 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR Contributors + + 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 I2C_PCA_INTERFACE_H +#define I2C_PCA_INTERFACE_H + +#include "I2CWireSensorInterface.h" + +namespace SlimeVR { +/** + * I2C Sensor interface for use with PCA9547 (8-channel I2C-buss multiplexer) + * or PCA9546A (4-channel I2C-bus multiplexer) or analogs + */ +class I2CPCASensorInterface : public SensorInterface { +public: + I2CPCASensorInterface( + uint8_t sclpin, + uint8_t sdapin, + uint8_t address, + uint8_t channel + ) + : m_Wire(sclpin, sdapin) + , m_Address(address) + , m_Channel(channel){}; + ~I2CPCASensorInterface(){}; + + void init() override final; + void swapIn() override final; + +protected: + I2CWireSensorInterface m_Wire; + uint8_t m_Address; + uint8_t m_Channel; +}; + +} // namespace SlimeVR + +#endif diff --git a/src/sensorinterface/I2CWireSensorInterface.cpp b/src/sensorinterface/I2CWireSensorInterface.cpp new file mode 100644 index 000000000..28ebc9976 --- /dev/null +++ b/src/sensorinterface/I2CWireSensorInterface.cpp @@ -0,0 +1,71 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR Contributors + + 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 "I2CWireSensorInterface.h" + +#if ESP32 +#include "driver/i2c.h" +#endif + +uint8_t activeSCLPin; +uint8_t activeSDAPin; +bool isI2CActive = false; + +namespace SlimeVR { +void swapI2C(uint8_t sclPin, uint8_t sdaPin) { + if (sclPin != activeSCLPin || sdaPin != activeSDAPin || !isI2CActive) { + Wire.flush(); +#if ESP32 + if (isI2CActive) { + } else { + // Reset HWI2C to avoid being affected by I2CBUS reset + Wire.end(); + } + // Disconnect pins from HWI2C + gpio_set_direction((gpio_num_t)activeSCLPin, GPIO_MODE_INPUT); + gpio_set_direction((gpio_num_t)activeSDAPin, GPIO_MODE_INPUT); + + if (isI2CActive) { + i2c_set_pin(I2C_NUM_0, sdaPin, sclPin, false, false, I2C_MODE_MASTER); + } else { + Wire.begin(static_cast(sdaPin), static_cast(sclPin), I2C_SPEED); + Wire.setTimeOut(150); + } +#else + Wire.begin(static_cast(sdaPin), static_cast(sclPin)); +#endif + + activeSCLPin = sclPin; + activeSDAPin = sdaPin; + isI2CActive = true; + } +} + +void disconnectI2C() { + Wire.flush(); + isI2CActive = false; +#if ESP32 + Wire.end(); +#endif +} +} // namespace SlimeVR diff --git a/src/sensorinterface/I2CWireSensorInterface.h b/src/sensorinterface/I2CWireSensorInterface.h new file mode 100644 index 000000000..5b97fbc62 --- /dev/null +++ b/src/sensorinterface/I2CWireSensorInterface.h @@ -0,0 +1,59 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR Contributors + + 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 SENSORINTERFACE_I2CWIRE_H +#define SENSORINTERFACE_I2CWIRE_H + +#include +#include + +#include "SensorInterface.h" +#include "globals.h" + +namespace SlimeVR { +void swapI2C(uint8_t sclPin, uint8_t sdaPin); +void disconnectI2C(); + +/** + * I2C Sensor interface using direct arduino Wire on provided pins + * + */ +class I2CWireSensorInterface : public SensorInterface { +public: + I2CWireSensorInterface(uint8_t sclpin, uint8_t sdapin) + : _sdaPin(sdapin) + , _sclPin(sclpin){}; + ~I2CWireSensorInterface(){}; + + void init() override final {} + void swapIn() override final { swapI2C(_sclPin, _sdaPin); } + void disconnect() { disconnectI2C(); } + +protected: + uint8_t _sdaPin; + uint8_t _sclPin; +}; + +} // namespace SlimeVR + +#endif // SENSORINTERFACE_I2CWIRE_H diff --git a/src/sensorinterface/MCP23X17PinInterface.cpp b/src/sensorinterface/MCP23X17PinInterface.cpp new file mode 100644 index 000000000..68eb9f544 --- /dev/null +++ b/src/sensorinterface/MCP23X17PinInterface.cpp @@ -0,0 +1,31 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 "MCP23X17PinInterface.h" + +int MCP23X17PinInterface::digitalRead() { return _mcp23x17->digitalRead(_pinNum); } + +void MCP23X17PinInterface::pinMode(uint8_t mode) { _mcp23x17->pinMode(_pinNum, mode); } + +void MCP23X17PinInterface::digitalWrite(uint8_t val) { + _mcp23x17->digitalWrite(_pinNum, val); +} \ No newline at end of file diff --git a/src/sensorinterface/MCP23X17PinInterface.h b/src/sensorinterface/MCP23X17PinInterface.h new file mode 100644 index 000000000..ed13a53d9 --- /dev/null +++ b/src/sensorinterface/MCP23X17PinInterface.h @@ -0,0 +1,64 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 _H_MCP23X17PinInterface_ +#define _H_MCP23X17PinInterface_ + +#include +#include + +#define MCP_GPA0 0 +#define MCP_GPA1 1 +#define MCP_GPA2 2 +#define MCP_GPA3 3 +#define MCP_GPA4 4 +#define MCP_GPA5 5 +#define MCP_GPA6 6 +#define MCP_GPA7 7 +#define MCP_GPB0 8 +#define MCP_GPB1 9 +#define MCP_GPB2 10 +#define MCP_GPB3 11 +#define MCP_GPB4 12 +#define MCP_GPB5 13 +#define MCP_GPB6 14 +#define MCP_GPB7 15 + +/** + * Pin interface to use MCP23008/17 I2C GPIO port extenders + */ +class MCP23X17PinInterface : public PinInterface { +public: + MCP23X17PinInterface(Adafruit_MCP23X17* mcp, uint8_t pin) + : _mcp23x17(mcp) + , _pinNum(pin){}; + + int digitalRead() override final; + void pinMode(uint8_t mode) override final; + void digitalWrite(uint8_t val) override final; + +private: + Adafruit_MCP23X17* _mcp23x17; + uint8_t _pinNum; +}; + +#endif // _H_MCP23X17PinInterface_ diff --git a/src/sensorinterface/SensorInterface.h b/src/sensorinterface/SensorInterface.h new file mode 100644 index 000000000..80c688ec2 --- /dev/null +++ b/src/sensorinterface/SensorInterface.h @@ -0,0 +1,43 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR Contributors + + 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 SENSORINTERFACE_H +#define SENSORINTERFACE_H + +namespace SlimeVR { +class SensorInterface { +public: + virtual void init(); + virtual void swapIn(); +}; + +class EmptySensorInterface : public SensorInterface { +public: + EmptySensorInterface(); + ~EmptySensorInterface(); + void init() override final; + void swapIn() override final; +}; +} // namespace SlimeVR + +#endif // SENSORINTERFACE_H diff --git a/src/sensors/ADCResistanceSensor.cpp b/src/sensors/ADCResistanceSensor.cpp new file mode 100644 index 000000000..f301a7435 --- /dev/null +++ b/src/sensors/ADCResistanceSensor.cpp @@ -0,0 +1,42 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 "ADCResistanceSensor.h" + +#include "GlobalVars.h" + +void ADCResistanceSensor::motionLoop() { +#if ESP8266 + float voltage = ((float)analogRead(m_Pin)) * ADCVoltageMax / ADCResolution; + m_Data = m_ResistanceDivider + * (ADCVoltageMax / voltage - 1.0f); // Convert voltage to resistance +#endif +#if ESP32 + float voltage = ((float)analogReadMilliVolts(m_Pin)) / 1000; + m_Data = m_ResistanceDivider + * (m_VCC / voltage - 1.0f); // Convert voltage to resistance +#endif +} + +void ADCResistanceSensor::sendData() { + networkConnection.sendFlexData(sensorId, m_Data); +} diff --git a/src/sensors/ADCResistanceSensor.h b/src/sensors/ADCResistanceSensor.h new file mode 100644 index 000000000..b84ab5b45 --- /dev/null +++ b/src/sensors/ADCResistanceSensor.h @@ -0,0 +1,70 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 ADC_FLEX_SENSOR_H +#define ADC_FLEX_SENSOR_H + +#include "sensor.h" +#include "sensorinterface/SensorInterface.h" + +class ADCResistanceSensor : Sensor { +public: + static constexpr auto TypeID = ImuID::ADC_RESISTANCE; + + ADCResistanceSensor( + uint8_t id, + uint8_t pin, + float VCC, + float resistanceDivider, + float smoothFactor = 0.1f + ) + : Sensor( + "ADCResistanceSensor", + ImuID::ADC_RESISTANCE, + id, + pin, + 0.0f, + new SlimeVR::EmptySensorInterface + ) + , m_Pin(pin) + , m_VCC(VCC) + , m_ResistanceDivider(resistanceDivider) + , m_SmoothFactor(smoothFactor){}; + ~ADCResistanceSensor(); + + void motionLoop() override final; + void sendData() override final; + + SensorStatus getSensorState() override final { return SensorStatus::SENSOR_OK; } + + uint8_t getDataType() override final { return SENSOR_DATATYPE_FLEX_RESISTANCE; }; + +private: + uint8_t m_Pin; + float m_VCC; + float m_ResistanceDivider; + float m_SmoothFactor; + + float m_Data = 0.0f; +}; + +#endif diff --git a/src/sensors/ErroneousSensor.cpp b/src/sensors/ErroneousSensor.cpp index 24524f02f..2cddaddc6 100644 --- a/src/sensors/ErroneousSensor.cpp +++ b/src/sensors/ErroneousSensor.cpp @@ -32,6 +32,8 @@ void ErroneousSensor::motionSetup() { "IMU of type %s failed to initialize", getIMUNameByType(m_ExpectedType) ); + m_tpsCounter.reset(); + m_dataCounter.reset(); } SensorStatus ErroneousSensor::getSensorState() { return SensorStatus::SENSOR_ERROR; }; diff --git a/src/sensors/SensorManager.cpp b/src/sensors/SensorManager.cpp index fbdeb8eb3..17711b769 100644 --- a/src/sensors/SensorManager.cpp +++ b/src/sensors/SensorManager.cpp @@ -23,12 +23,16 @@ #include "SensorManager.h" +#include + #include "bmi160sensor.h" #include "bno055sensor.h" #include "bno080sensor.h" #include "icm20948sensor.h" #include "mpu6050sensor.h" #include "mpu9250sensor.h" +#include "sensorinterface/I2CPCAInterface.h" +#include "sensorinterface/MCP23X17PinInterface.h" #include "softfusion/drivers/bmi270.h" #include "softfusion/drivers/icm42688.h" #include "softfusion/drivers/lsm6ds3trc.h" @@ -60,44 +64,58 @@ using SoftFusionLSM6DSR using SoftFusionMPU6050 = SoftFusionSensor; -// TODO Make it more generic in the future and move another place (abstract sensor -// interface) -void SensorManager::swapI2C(uint8_t sclPin, uint8_t sdaPin) { - if (sclPin != activeSCL || sdaPin != activeSDA || !running) { - Wire.flush(); -#if ESP32 - if (running) { - } else { - // Reset HWI2C to avoid being affected by I2CBUS reset - Wire.end(); - } - // Disconnect pins from HWI2C - gpio_set_direction((gpio_num_t)activeSCL, GPIO_MODE_INPUT); - gpio_set_direction((gpio_num_t)activeSDA, GPIO_MODE_INPUT); - - if (running) { - i2c_set_pin(I2C_NUM_0, sdaPin, sclPin, false, false, I2C_MODE_MASTER); - } else { - Wire.begin(static_cast(sdaPin), static_cast(sclPin), I2C_SPEED); - Wire.setTimeOut(150); +void SensorManager::setup() { + std::map directPinInterfaces; + std::map mcpPinInterfaces; + std::map, I2CWireSensorInterface*> i2cWireInterfaces; + std::map, I2CPCASensorInterface*> pcaWireInterfaces; + + auto directPin = [&](int pin) { + if (!directPinInterfaces.contains(pin)) { + auto ptr = new DirectPinInterface(pin); + directPinInterfaces[pin] = ptr; } -#else - Wire.begin(static_cast(sdaPin), static_cast(sclPin)); -#endif + return directPinInterfaces[pin]; + }; - activeSCL = sclPin; - activeSDA = sdaPin; + auto mcpPin = [&](int pin) { + if (!mcpPinInterfaces.contains(pin)) { + auto ptr = new MCP23X17PinInterface(&m_MCP, pin); + mcpPinInterfaces[pin] = ptr; + } + return mcpPinInterfaces[pin]; + }; + + auto directWire = [&](int scl, int sda) { + auto pair = std::make_tuple(scl, sda); + if (!i2cWireInterfaces.contains(pair)) { + auto ptr = new I2CWireSensorInterface(scl, sda); + i2cWireInterfaces[pair] = ptr; + } + return i2cWireInterfaces[pair]; + }; + + auto pcaWire = [&](int scl, int sda, int addr, int ch) { + auto pair = std::make_tuple(scl, sda, addr, ch); + if (!pcaWireInterfaces.contains(pair)) { + auto ptr = new I2CPCASensorInterface(scl, sda, addr, ch); + pcaWireInterfaces[pair] = ptr; + } + return pcaWireInterfaces[pair]; + }; + uint8_t sensorID = 0; + uint8_t activeSensorCount = 0; + if (m_MCP.begin_I2C()) { + m_Logger.info("MCP initialized"); } -} -void SensorManager::setup() { - running = false; - activeSCL = PIN_IMU_SCL; - activeSDA = PIN_IMU_SDA; +#define NO_PIN nullptr +#define DIRECT_PIN(pin) directPin(pin) +#define DIRECT_WIRE(scl, sda) directWire(scl, sda) +#define MCP_PIN(pin) mcpPin(pin) +#define PCA_WIRE(scl, sda, addr, ch) pcaWire(scl, sda, addr, ch) - uint8_t sensorID = 0; - uint8_t activeSensorCount = 0; -#define IMU_DESC_ENTRY(ImuType, ...) \ +#define SENSOR_DESC_ENTRY(ImuType, ...) \ { \ auto sensor = buildSensor(sensorID, __VA_ARGS__); \ if (sensor->isWorking()) { \ @@ -107,10 +125,18 @@ void SensorManager::setup() { m_Sensors.push_back(std::move(sensor)); \ sensorID++; \ } - // Apply descriptor list and expand to entrys - IMU_DESC_LIST; -#undef IMU_DESC_ENTRY + // Apply descriptor list and expand to entries + SENSOR_DESC_LIST; + +#define SENSOR_INFO_ENTRY(ImuID, ...) \ + { m_Sensors[ImuID]->setSensorInfo(__VA_ARGS__); } + SENSOR_INFO_LIST; + +#undef SENSOR_DESC_ENTRY +#undef NO_PIN +#undef DIRECT_PIN +#undef DIRECT_WIRE m_Logger.info("%d sensor(s) configured", activeSensorCount); // Check and scan i2c if no sensors active if (activeSensorCount == 0) { @@ -123,10 +149,9 @@ void SensorManager::setup() { } void SensorManager::postSetup() { - running = true; for (auto& sensor : m_Sensors) { if (sensor->isWorking()) { - swapI2C(sensor->sclPin, sensor->sdaPin); + sensor->m_hwInterface->swapIn(); sensor->postSetup(); } } @@ -137,7 +162,7 @@ void SensorManager::update() { bool allIMUGood = true; for (auto& sensor : m_Sensors) { if (sensor->isWorking()) { - swapI2C(sensor->sclPin, sensor->sdaPin); + sensor->m_hwInterface->swapIn(); sensor->motionLoop(); } if (sensor->getSensorState() == SensorStatus::SENSOR_ERROR) { @@ -193,6 +218,5 @@ void SensorManager::update() { networkConnection.endBundle(); #endif } - } // namespace Sensors } // namespace SlimeVR diff --git a/src/sensors/SensorManager.h b/src/sensors/SensorManager.h index c2ebdb5ca..0ccc13b88 100644 --- a/src/sensors/SensorManager.h +++ b/src/sensors/SensorManager.h @@ -41,6 +41,10 @@ #include "globals.h" #include "logging/Logger.h" #include "sensor.h" +#include "sensorinterface/DirectPinInterface.h" +#include "sensorinterface/I2CPCAInterface.h" +#include "sensorinterface/I2CWireSensorInterface.h" +#include "sensorinterface/MCP23X17PinInterface.h" namespace SlimeVR { namespace Sensors { @@ -65,27 +69,28 @@ class SensorManager { SlimeVR::Logging::Logger m_Logger; std::vector> m_Sensors; + Adafruit_MCP23X17 m_MCP; template std::unique_ptr buildSensor( uint8_t sensorID, std::optional imuAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SensorInterface* sensorInterface, bool optional = false, + PinInterface* intPin = nullptr, int extraParam = 0 ) { uint8_t i2cAddress = imuAddress.value_or(ImuType::Address + sensorID); m_Logger.trace( "Building IMU with: id=%d,\n\ - address=0x%02X, rotation=%f,\n\ - sclPin=%d, sdaPin=%d, extraParam=%d, optional=%d", + address=0x%02X, rotation=%f,\n\ + interface=%s, int=%s, extraParam=%d, optional=%d", sensorID, i2cAddress, rotation, - sclPin, - sdaPin, + sensorInterface, + intPin, extraParam, optional ); @@ -93,9 +98,9 @@ class SensorManager { // Now start detecting and building the IMU std::unique_ptr sensor; - // Clear and reset I2C bus for each sensor upon startup - I2CSCAN::clearBus(sdaPin, sclPin); - swapI2C(sclPin, sdaPin); + // Init I2C bus for each sensor upon startup + sensorInterface->init(); + sensorInterface->swapIn(); if (I2CSCAN::hasDevOnBus(i2cAddress)) { m_Logger @@ -119,23 +124,18 @@ class SensorManager { return sensor; } - uint8_t intPin = extraParam; sensor = std::make_unique( sensorID, i2cAddress, rotation, - sclPin, - sdaPin, - intPin + sensorInterface, + intPin, + extraParam ); sensor->motionSetup(); return sensor; } - uint8_t activeSCL = 0; - uint8_t activeSDA = 0; - bool running = false; - void swapI2C(uint8_t scl, uint8_t sda); uint32_t m_LastBundleSentAtMicros = micros(); }; diff --git a/src/sensors/bmi160sensor.cpp b/src/sensors/bmi160sensor.cpp index 7da6cbc76..690487eb9 100644 --- a/src/sensors/bmi160sensor.cpp +++ b/src/sensors/bmi160sensor.cpp @@ -280,9 +280,12 @@ void BMI160Sensor::motionSetup() { } working = true; + m_tpsCounter.reset(); + m_dataCounter.reset(); } void BMI160Sensor::motionLoop() { + m_tpsCounter.update(); #if ENABLE_INSPECTION { int16_t rX, rY, rZ, aX, aY, aZ; diff --git a/src/sensors/bmi160sensor.h b/src/sensors/bmi160sensor.h index 686f203ca..2c7210ff1 100644 --- a/src/sensors/bmi160sensor.h +++ b/src/sensors/bmi160sensor.h @@ -140,8 +140,8 @@ class BMI160Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SlimeVR::SensorInterface* sensorInterface, + PinInterface*, int axisRemapParam ) : Sensor( @@ -150,8 +150,7 @@ class BMI160Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ) , sfusion( BMI160_ODR_GYR_MICROS / 1e6f, diff --git a/src/sensors/bno055sensor.cpp b/src/sensors/bno055sensor.cpp index 086759f84..873709975 100644 --- a/src/sensors/bno055sensor.cpp +++ b/src/sensors/bno055sensor.cpp @@ -47,9 +47,12 @@ void BNO055Sensor::motionSetup() { m_Logger.info("Connected to BNO055 at address 0x%02x", addr); working = true; + m_tpsCounter.reset(); + m_dataCounter.reset(); } void BNO055Sensor::motionLoop() { + m_tpsCounter.update(); #if ENABLE_INSPECTION { Vector3 gyro = imu.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE); diff --git a/src/sensors/bno055sensor.h b/src/sensors/bno055sensor.h index 526b67c22..182a03547 100644 --- a/src/sensors/bno055sensor.h +++ b/src/sensors/bno055sensor.h @@ -37,8 +37,8 @@ class BNO055Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SlimeVR::SensorInterface* sensorInterface, + PinInterface*, uint8_t ) : Sensor( @@ -47,8 +47,7 @@ class BNO055Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ){}; ~BNO055Sensor(){}; void motionSetup() override final; diff --git a/src/sensors/bno080sensor.cpp b/src/sensors/bno080sensor.cpp index 5fa03281e..ce30fd04d 100644 --- a/src/sensors/bno080sensor.cpp +++ b/src/sensors/bno080sensor.cpp @@ -96,14 +96,57 @@ void BNO080Sensor::motionSetup() { imu.enableRawAccelerometer(10); imu.enableRawMagnetometer(10); #endif + // Calibration settings: + // EXPERIMENTAL Enable periodic calibration save to permanent memory + imu.saveCalibrationPeriodically(true); + imu.requestCalibrationStatus(); + // EXPERIMENTAL Disable accelerometer calibration after 1 minute to prevent + // "stomping" bug WARNING : Executing IMU commands outside of the update loop is not + // allowed since the address might have changed when the timer is executed! + if (sensorType == ImuID::BNO085) { + // For BNO085, disable accel calibration + globalTimer.in( + 60000, + [](void* sensor) { + ((BNO080*)sensor)->sendCalibrateCommand(SH2_CAL_MAG | SH2_CAL_ON_TABLE); + return true; + }, + &imu + ); + } else if (sensorType == ImuID::BNO086) { + // For BNO086, disable accel calibration + // TODO: Find default flags for BNO086 + globalTimer.in( + 60000, + [](void* sensor) { + ((BNO080*)sensor)->sendCalibrateCommand(SH2_CAL_MAG | SH2_CAL_ON_TABLE); + return true; + }, + &imu + ); + } else { + globalTimer.in( + 60000, + [](void* sensor) { + ((BNO080*)sensor)->requestCalibrationStatus(); + return true; + }, + &imu + ); + } + // imu.sendCalibrateCommand(SH2_CAL_ACCEL | SH2_CAL_GYRO_IN_HAND | SH2_CAL_MAG | + // SH2_CAL_ON_TABLE | SH2_CAL_PLANAR); lastReset = 0; lastData = millis(); working = true; configured = true; + m_tpsCounter.reset(); + m_dataCounter.reset(); } void BNO080Sensor::motionLoop() { + m_tpsCounter.update(); // Look for reports from the IMU while (imu.dataAvailable()) { hadData = true; @@ -192,7 +235,36 @@ void BNO080Sensor::motionLoop() { if (imu.getTapDetected()) { tap = imu.getTapDetector(); } - if (m_IntPin == 255 || imu.I2CTimedOut()) { + if (imu.hasNewCliabrationStatus()) { + uint8_t calibrationResponseStatus; + uint8_t accelCalEnabled; + uint8_t gyroCalEnabled; + uint8_t magCalEnabled; + uint8_t planarAccelCalEnabled; + uint8_t onTableCalEnabled; + imu.getCalibrationStatus( + calibrationResponseStatus, + accelCalEnabled, + gyroCalEnabled, + magCalEnabled, + planarAccelCalEnabled, + onTableCalEnabled + ); + m_Logger.info( + "BNO08X calibration satus received: Status: %d, Accel: %d, Gyro: %d, " + "Mag: %d, Planar: %d, OnTable: %d", + calibrationResponseStatus, + accelCalEnabled, + gyroCalEnabled, + magCalEnabled, + planarAccelCalEnabled, + onTableCalEnabled + ); + // Default calibration flags for BNO085: + // Accel: 1, Gyro: 0, Mag: 1, Planar: 0, OnTable: 0 (OnTable can't be + // disabled) + } + if (m_IntPin == nullptr || imu.I2CTimedOut()) { break; } } diff --git a/src/sensors/bno080sensor.h b/src/sensors/bno080sensor.h index 68ee4eb7a..f30faa230 100644 --- a/src/sensors/bno080sensor.h +++ b/src/sensors/bno080sensor.h @@ -39,9 +39,9 @@ class BNO080Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, - uint8_t intPin + SlimeVR::SensorInterface* sensorInterface, + PinInterface* intPin, + int ) : Sensor( "BNO080Sensor", @@ -49,8 +49,7 @@ class BNO080Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ) , m_IntPin(intPin){}; ~BNO080Sensor(){}; @@ -71,17 +70,17 @@ class BNO080Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, - uint8_t intPin + SlimeVR::SensorInterface* sensorInterface, + PinInterface* intPin, + int ) - : Sensor(sensorName, imuId, id, i2cAddress, rotation, sclPin, sdaPin) + : Sensor(sensorName, imuId, id, i2cAddress, rotation, sensorInterface) , m_IntPin(intPin){}; private: BNO080 imu{}; - uint8_t m_IntPin; + PinInterface* m_IntPin; uint8_t tap; unsigned long lastData = 0; @@ -104,9 +103,9 @@ class BNO085Sensor : public BNO080Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, - uint8_t intPin + SlimeVR::SensorInterface* sensorInterface, + PinInterface* intPin, + int extraParam ) : BNO080Sensor( "BNO085Sensor", @@ -114,9 +113,9 @@ class BNO085Sensor : public BNO080Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin, - intPin + sensorInterface, + intPin, + extraParam ){}; }; @@ -127,9 +126,9 @@ class BNO086Sensor : public BNO080Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, - uint8_t intPin + SlimeVR::SensorInterface* sensorInterface, + PinInterface* intPin, + int extraParam ) : BNO080Sensor( "BNO086Sensor", @@ -137,9 +136,9 @@ class BNO086Sensor : public BNO080Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin, - intPin + sensorInterface, + intPin, + extraParam ){}; }; diff --git a/src/sensors/icm20948sensor.cpp b/src/sensors/icm20948sensor.cpp index e33476836..8d7dd6d4c 100644 --- a/src/sensors/icm20948sensor.cpp +++ b/src/sensors/icm20948sensor.cpp @@ -46,9 +46,12 @@ void ICM20948Sensor::motionSetup() { loadCalibration(); startMotionLoop(); startCalibrationAutoSave(); + m_tpsCounter.reset(); + m_dataCounter.reset(); } void ICM20948Sensor::motionLoop() { + m_tpsCounter.update(); #if ENABLE_INSPECTION { (void)imu.getAGMT(); diff --git a/src/sensors/icm20948sensor.h b/src/sensors/icm20948sensor.h index 0d70d2edc..b06c3d467 100644 --- a/src/sensors/icm20948sensor.h +++ b/src/sensors/icm20948sensor.h @@ -37,8 +37,8 @@ class ICM20948Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SlimeVR::SensorInterface* sensorInterface, + PinInterface*, uint8_t ) : Sensor( @@ -47,8 +47,7 @@ class ICM20948Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ) {} ~ICM20948Sensor() override = default; void motionSetup() override final; diff --git a/src/sensors/mpu6050sensor.cpp b/src/sensors/mpu6050sensor.cpp index 4480c78db..e3402e0c3 100644 --- a/src/sensors/mpu6050sensor.cpp +++ b/src/sensors/mpu6050sensor.cpp @@ -132,9 +132,12 @@ void MPU6050Sensor::motionSetup() { // (if it's going to break, usually the code will be 1) m_Logger.error("DMP Initialization failed (code %d)", devStatus); } + m_tpsCounter.reset(); + m_dataCounter.reset(); } void MPU6050Sensor::motionLoop() { + m_tpsCounter.update(); #if ENABLE_INSPECTION { int16_t rX, rY, rZ, aX, aY, aZ; diff --git a/src/sensors/mpu6050sensor.h b/src/sensors/mpu6050sensor.h index 4f5f66cc1..7bcb4cd16 100644 --- a/src/sensors/mpu6050sensor.h +++ b/src/sensors/mpu6050sensor.h @@ -38,8 +38,8 @@ class MPU6050Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SlimeVR::SensorInterface* sensorInterface, + PinInterface*, uint8_t ) : Sensor( @@ -48,8 +48,7 @@ class MPU6050Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ){}; ~MPU6050Sensor(){}; void motionSetup() override final; diff --git a/src/sensors/mpu9250sensor.cpp b/src/sensors/mpu9250sensor.cpp index 6e4bc9bc4..6425d7b73 100644 --- a/src/sensors/mpu9250sensor.cpp +++ b/src/sensors/mpu9250sensor.cpp @@ -152,9 +152,12 @@ void MPU9250Sensor::motionSetup() { working = true; #endif + m_tpsCounter.reset(); + m_dataCounter.reset(); } void MPU9250Sensor::motionLoop() { + m_tpsCounter.update(); #if ENABLE_INSPECTION { int16_t rX, rY, rZ, aX, aY, aZ, mX, mY, mZ; diff --git a/src/sensors/mpu9250sensor.h b/src/sensors/mpu9250sensor.h index e6f18802d..802921746 100644 --- a/src/sensors/mpu9250sensor.h +++ b/src/sensors/mpu9250sensor.h @@ -51,8 +51,8 @@ class MPU9250Sensor : public Sensor { uint8_t id, uint8_t i2cAddress, float rotation, - uint8_t sclPin, - uint8_t sdaPin, + SlimeVR::SensorInterface* sensorInterface, + PinInterface*, uint8_t ) : Sensor( @@ -61,8 +61,7 @@ class MPU9250Sensor : public Sensor { id, i2cAddress, rotation, - sclPin, - sdaPin + sensorInterface ) #if !MPU_USE_DMPMAG , sfusion(MPU9250_ODR_TS) diff --git a/src/sensors/sensor.cpp b/src/sensors/sensor.cpp index db9573daa..da1bfae8b 100644 --- a/src/sensors/sensor.cpp +++ b/src/sensors/sensor.cpp @@ -45,6 +45,9 @@ void Sensor::setFusedRotation(Quat r) { newFusedRotation = true; lastFusedRotationSent = fusedRotation; } + if (changed) { + m_dataCounter.update(); + } } void Sensor::sendData() { diff --git a/src/sensors/sensor.h b/src/sensors/sensor.h index 6b52177b4..068d1613b 100644 --- a/src/sensors/sensor.h +++ b/src/sensors/sensor.h @@ -28,9 +28,14 @@ #include #include +#include + +#include "PinInterface.h" #include "configuration/Configuration.h" #include "globals.h" #include "logging/Logger.h" +#include "sensorinterface/SensorInterface.h" +#include "status/TPSCounter.h" #include "utils.h" #define DATA_TYPE_NORMAL 1 @@ -56,16 +61,14 @@ class Sensor { uint8_t id, uint8_t address, float rotation, - uint8_t sclpin = 0, - uint8_t sdapin = 0 + SlimeVR::SensorInterface* sensorInterface = nullptr ) - : addr(address) + : m_hwInterface(sensorInterface) + , addr(address) , sensorId(id) , sensorType(type) , sensorOffset({Quat(Vector3(0, 0, 1), rotation)}) - , m_Logger(SlimeVR::Logging::Logger(sensorName)) - , sclPin(sclpin) - , sdaPin(sdapin) { + , m_Logger(SlimeVR::Logging::Logger(sensorName)) { char buf[4]; sprintf(buf, "%u", id); m_Logger.setTag(buf); @@ -88,7 +91,7 @@ class Sensor { virtual uint16_t getSensorConfigData(); bool isWorking() { return working; }; bool getHadData() const { return hadData; }; - bool isValid() { return sclPin != sdaPin; }; + bool isValid() { return m_hwInterface != nullptr; }; bool isMagEnabled() { return magStatus == MagnetometerStatus::MAG_ENABLED; }; uint8_t getSensorId() { return sensorId; }; ImuID getSensorType() { return sensorType; }; @@ -97,6 +100,16 @@ class Sensor { const Quat& getFusedRotation() { return fusedRotation; }; bool hasNewDataToSend() { return newFusedRotation || newAcceleration; }; + virtual uint8_t getDataType() { return SENSOR_DATATYPE_ROTATION; }; + + uint16_t getSensorPosition() { return m_SensorPosition; }; + + void setSensorInfo(uint16_t sensorPosition) { m_SensorPosition = sensorPosition; }; + + TPSCounter m_tpsCounter; + TPSCounter m_dataCounter; + std::shared_ptr m_hwInterface; + protected: uint8_t addr = 0; uint8_t sensorId = 0; @@ -114,11 +127,9 @@ class Sensor { bool newAcceleration = false; Vector3 acceleration{}; - mutable SlimeVR::Logging::Logger m_Logger; + uint16_t m_SensorPosition = POSITION_NO; -public: - uint8_t sclPin = 0; - uint8_t sdaPin = 0; + mutable SlimeVR::Logging::Logger m_Logger; private: void printTemperatureCalibrationUnsupported(); diff --git a/src/sensors/sensorposition.h b/src/sensors/sensorposition.h new file mode 100644 index 000000000..8756df097 --- /dev/null +++ b/src/sensors/sensorposition.h @@ -0,0 +1,81 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 SENSOR_POSITION_H +#define SENSOR_POSITION_H + +#define POSITION_NO 0 +#define POSITION_HEAD 1 +#define POSITION_NECK 2 +#define POSITION_UPPER_CHEST 3 +#define POSITION_CHEST 4 +#define POSITION_WAIST 5 +#define POSITION_HIP 6 +#define POSITION_LEFT_UPPER_LEG 7 +#define POSITION_RIGHT_UPPER_LEG 8 +#define POSITION_LEFT_LOWER_LEG 9 +#define POSITION_RIGHT_LOWER_LEG 10 +#define POSITION_LEFT_FOOT 11 +#define POSITION_RIGHT_FOOT 12 +#define POSITION_LEFT_LOWER_ARM 13 +#define POSITION_RIGHT_LOWER_ARM 14 +#define POSITION_LEFT_UPPER_ARM 15 +#define POSITION_RIGHT_UPPER_ARM 16 +#define POSITION_LEFT_HAND 17 +#define POSITION_RIGHT_HAND 18 +#define POSITION_LEFT_SHOULDER 19 +#define POSITION_RIGHT_SHOULDER 20 +#define POSITION_LEFT_THUMB_PROXIMAL 21 +#define POSITION_LEFT_THUMB_INTERMEDIATE 22 +#define POSITION_LEFT_THUMB_DISTAL 23 +#define POSITION_LEFT_INDEX_PROXIMAL 24 +#define POSITION_LEFT_INDEX_INTERMEDIATE 25 +#define POSITION_LEFT_INDEX_DISTAL 26 +#define POSITION_LEFT_MIDDLE_PROXIMAL 27 +#define POSITION_LEFT_MIDDLE_INTERMEDIATE 28 +#define POSITION_LEFT_MIDDLE_DISTAL 29 +#define POSITION_LEFT_RING_PROXIMAL 30 +#define POSITION_LEFT_RING_INTERMEDIATE 31 +#define POSITION_LEFT_RING_DISTAL 32 +#define POSITION_LEFT_LITTLE_PROXIMAL 33 +#define POSITION_LEFT_LITTLE_INTERMEDIATE 34 +#define POSITION_LEFT_LITTLE_DISTAL 35 +#define POSITION_RIGHT_THUMB_PROXIMAL 36 +#define POSITION_RIGHT_THUMB_INTERMEDIATE 37 +#define POSITION_RIGHT_THUMB_DISTAL 38 +#define POSITION_RIGHT_INDEX_PROXIMAL 39 +#define POSITION_RIGHT_INDEX_INTERMEDIATE 40 +#define POSITION_RIGHT_INDEX_DISTAL 41 +#define POSITION_RIGHT_MIDDLE_PROXIMAL 42 +#define POSITION_RIGHT_MIDDLE_INTERMEDIATE 43 +#define POSITION_RIGHT_MIDDLE_DISTAL 44 +#define POSITION_RIGHT_RING_PROXIMAL 45 +#define POSITION_RIGHT_RING_INTERMEDIATE 46 +#define POSITION_RIGHT_RING_DISTAL 47 +#define POSITION_RIGHT_LITTLE_PROXIMAL 48 +#define POSITION_RIGHT_LITTLE_INTERMEDIATE 49 +#define POSITION_RIGHT_LITTLE_DISTAL 50 + +#define GLOVE_LEFT 1 +#define GLOVE_RIGHT 2 + +#endif diff --git a/src/status/TPSCounter.cpp b/src/status/TPSCounter.cpp new file mode 100644 index 000000000..ae0e201a0 --- /dev/null +++ b/src/status/TPSCounter.cpp @@ -0,0 +1,26 @@ +#include "TPSCounter.h" + +void TPSCounter::reset() { + _lastUpdate = _lastAverageUpdate = millis(); + _tps = _averagedTps = 0.0; + _averageUpdatesCounter = 0; +} + +void TPSCounter::update() { + long time = millis(); + long sinceLastUpdate = millis() - _lastUpdate; + long sinceAvgLastUpdate = millis() - _lastAverageUpdate; + _lastUpdate = time; + _tps = 1000.0 / static_cast(sinceLastUpdate); + if (sinceAvgLastUpdate > 1000) { + _lastAverageUpdate = time; + _averagedTps + = 1000.0 / static_cast(sinceAvgLastUpdate) * _averageUpdatesCounter; + _averageUpdatesCounter = 0; + } + _averageUpdatesCounter++; +} + +float TPSCounter::getAveragedTPS() { return _averagedTps; } + +float TPSCounter::getTPS() { return _tps; } diff --git a/src/status/TPSCounter.h b/src/status/TPSCounter.h new file mode 100644 index 000000000..2099461a4 --- /dev/null +++ b/src/status/TPSCounter.h @@ -0,0 +1,43 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Eiren Rain & SlimeVR contributors + + 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 _H_TPS_COUNTER_ +#define _H_TPS_COUNTER_ + +#include + +class TPSCounter { +public: + virtual void reset(); + virtual void update(); + virtual float getAveragedTPS(); + virtual float getTPS(); + +private: + long _lastUpdate; + long _lastAverageUpdate; + uint32_t _averageUpdatesCounter; + float _averagedTps; + float _tps; +}; + +#endif // _H_TPS_COUNTER_