Compare commits

...

12 Commits

Author SHA1 Message Date
gorbit99
d86309e554 Merge branch 'main' into ads111x-implementation 2025-04-22 18:29:05 +02:00
gorbit99
ac1e726c8f Formatting 2025-04-22 18:27:49 +02:00
gorbit99
6f86f42c93 Mux working 2025-04-22 18:08:54 +02:00
gorbit99
ec83c48fc6 Add generic parallel mux support 2025-04-08 22:38:26 +02:00
gorbit99
cf63f4e947 Formatting 2025-04-06 00:25:02 +02:00
gorbit99
04fbc7e33d Use DRDY pin and optimize ADS1115 with only one channel used 2025-04-05 23:54:23 +02:00
gorbit99
a232a503ed Merge remote-tracking branch 'origin/main' into ads111x-implementation 2025-04-05 02:41:37 +02:00
gorbit99
dc6f6d50ad Consts update 2025-04-05 02:41:24 +02:00
gorbit99
b2251e62af Formatting 2025-04-05 02:40:34 +02:00
gorbit99
bd80446cb6 Make everything work 2025-04-05 02:06:38 +02:00
gorbit99
91da63f1d9 Refactor ADS implementation to use generic sensor and pininterfaces instead 2025-03-21 13:37:53 +01:00
gorbit99
7e99feeae5 Initial ADS111x implementation 2025-03-21 05:53:57 +01:00
27 changed files with 791 additions and 82 deletions

View File

@@ -50,7 +50,7 @@
#include <Wire.h>
#include <SPI.h>
#include <memory>
#include "PinInterface.h"
#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

View File

@@ -1,34 +1,34 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2024 Eiren Rain & SlimeVR contributors
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:
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 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.
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.
*/
#pragma once
#include <cstdint>
class PinInterface
{
class PinInterface {
public:
virtual bool init() { return true; };
virtual int digitalRead() = 0;
virtual void pinMode(uint8_t mode) = 0;
virtual void digitalWrite(uint8_t val) = 0;
virtual float analogRead() = 0;
};

View File

@@ -27,20 +27,10 @@
#include <I2Cdev.h>
#include <i2cscan.h>
#include "consts.h"
#include "globals.h"
#include "logging/Logger.h"
#if ESP8266
#define ADCResolution 1023.0 // ESP8266 has 10bit ADC
#define ADCVoltageMax 1.0 // ESP8266 input is 1.0 V = 1023.0
#endif
#ifndef ADCResolution
#define ADCResolution 1023.0
#endif
#ifndef ADCVoltageMax
#define ADCVoltageMax 1.0
#endif
#ifndef BATTERY_SHIELD_RESISTANCE
#define BATTERY_SHIELD_RESISTANCE 180.0
#endif

View File

@@ -68,6 +68,7 @@ enum class SensorTypeID : uint8_t {
#define IMU_MPU6050_SF SoftFusionMPU6050
#define IMU_ICM45686 SoftFusionICM45686
#define IMU_ICM45605 SoftFusionICM45605
#define SENSOR_ADC ADCResistanceSensor
#define IMU_DEV_RESERVED 250 // Reserved, should not be used in any release firmware
@@ -173,6 +174,21 @@ enum class TrackerType : uint8_t {
#define CURRENT_CONFIGURATION_VERSION 1
#if ESP8266
#define ADCResolution 1023.0 // ESP8266 has 10bit ADC
#define ADCVoltageMax 1.0 // ESP8266 input is 1.0 V = 1023.0
#elif ESP32
#define ADCResolution 4095.0 // ESP32 has 12bit ADC
#define ADCVoltageMax 2.5 // ESP32 input is 2.5 V = 4095.0 by default
#endif
#ifndef ADCResolution
#define ADCResolution 1023.0
#endif
#ifndef ADCVoltageMax
#define ADCVoltageMax 1.0
#endif
#include "sensors/sensorposition.h"
#endif // SLIMEVR_CONSTS_H_

View File

@@ -0,0 +1,131 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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 "ADS111xInterface.h"
#include <cstring>
namespace SlimeVR {
ADS111xInterface::ADS111xInterface(
SensorInterface* interface,
PinInterface* drdy,
uint8_t address
)
: interface{interface}
, drdy{drdy}
, address{address} {}
bool ADS111xInterface::init() {
Registers::Config config{
.compQue = 0b11, // Disable comparator
.compLat = 0b0, // Doesn't matter
.compPol = 0b0, // Doesn't matter
.compMode = 0b0, // Doesn't matter
.dr = 0b100, // Doesn't matter
.mode = 0b1, // Single-shot
.pga = 0b010, // Gain (FSR): 2.048V
.mux = 0b000, // Doesn't matter
.os = 0b0, // Don't start read
};
if (!writeRegister(Registers::Addresses::Config, config)) {
logger.error("Couldn't initialize ADS interface!\n");
return false;
}
// Enable conversion ready functionality
writeRegister(Registers::Addresses::HiThresh, static_cast<uint16_t>(0x8000));
writeRegister(Registers::Addresses::LoThresh, static_cast<uint16_t>(0x0000));
drdy->pinMode(INPUT);
return true;
}
float ADS111xInterface::read(uint8_t channel) {
if (__builtin_popcount(usedPins) == 1) {
// Just read the last sample
uint16_t value = readRegister(Registers::Addresses::Conversion);
return static_cast<float>(value) / maxValue;
}
Registers::Config config{
.compQue = 0b00, // Alert after every sample
.compLat = 0b1, // Latch alert
.compPol = 0b0, // Doesn't matter
.compMode = 0b0, // Doesn't matter
.dr = 0b111, // 860 samples per second
.mode = 0b1, // Single-shot
.pga = 0b010, // Gain (FSR): 2.048V
.mux = static_cast<uint8_t>(0b100 | channel), // Current channel
.os = 0b1, // Start read
};
writeRegister(Registers::Addresses::Config, config);
while (drdy->digitalRead())
;
uint16_t value = readRegister(Registers::Addresses::Conversion);
return static_cast<float>(value) / maxValue;
}
uint16_t ADS111xInterface::readRegister(Registers::Addresses reg) {
Wire.beginTransmission(address);
Wire.write(static_cast<uint8_t>(Registers::Addresses::Conversion));
Wire.endTransmission();
Wire.beginTransmission(address);
Wire.requestFrom(address, 2);
uint8_t msb = Wire.read();
uint8_t lsb = Wire.read();
Wire.endTransmission();
return (msb << 8) | lsb;
}
void ADS111xInterface::registerChannel(uint8_t channel) {
usedPins |= 1 << channel;
if (__builtin_popcount(usedPins) != 1) {
return;
}
// If we have only one channel used, just set up continuous reads
Registers::Config config{
.compQue = 0b11, // Disable comparator
.compLat = 0b0, // Doesn't matter
.compPol = 0b0, // Doesn't matter
.compMode = 0b0, // Doesn't matter
.dr = 0b100, // 128 samples per second
.mode = 0b0, // Continuous mode
.pga = 0b010, // Gain (FSR): 2.048V
.mux = static_cast<uint8_t>(0b100 | channel), // Use the channel
.os = 0b1, // Start reads
};
writeRegister(Registers::Addresses::Config, config);
}
} // namespace SlimeVR

View File

@@ -0,0 +1,97 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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.
*/
#pragma once
#include <PinInterface.h>
#include <cstdint>
#include "../logging/Logger.h"
#include "DirectPinInterface.h"
#include "I2CWireSensorInterface.h"
#include "SensorInterface.h"
namespace SlimeVR {
class ADS111xInterface {
public:
ADS111xInterface(SensorInterface* interface, PinInterface* drdy, uint8_t address);
bool init();
float read(uint8_t channel);
private:
static constexpr uint32_t maxValue = 0x7fff;
struct Registers {
enum class Addresses : uint8_t {
Conversion = 0b00,
Config = 0b01,
LoThresh = 0b10,
HiThresh = 0b11,
};
struct Config {
uint8_t compQue : 2;
uint8_t compLat : 1;
uint8_t compPol : 1;
uint8_t compMode : 1;
uint8_t dr : 3;
uint8_t mode : 1;
uint8_t pga : 3;
uint8_t mux : 3;
uint8_t os : 1;
};
};
static_assert(sizeof(Registers::Config) == 2);
template <typename T>
bool writeRegister(Registers::Addresses reg, T value) {
static_assert(sizeof(T) == 2);
interface->swapIn();
Wire.beginTransmission(address);
Wire.write(static_cast<uint8_t>(reg));
auto* bytes = reinterpret_cast<uint8_t*>(&value);
Wire.write(bytes[1]);
Wire.write(bytes[0]);
auto result = Wire.endTransmission();
return result == 0;
}
void registerChannel(uint8_t channel);
uint16_t readRegister(Registers::Addresses red);
SensorInterface* interface;
PinInterface* drdy;
uint8_t address;
uint8_t counter = 0;
uint8_t usedPins = 0x0;
Logging::Logger logger = Logging::Logger("ADS111x");
friend class ADS111xPin;
};
} // namespace SlimeVR

View File

@@ -0,0 +1,43 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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 "ADS111xPin.h"
#include <cassert>
namespace SlimeVR {
ADS111xPin::ADS111xPin(ADS111xInterface* interface, uint8_t channel)
: ads111x{interface}
, channel{channel} {
assert(channel < 4);
interface->registerChannel(channel);
}
int ADS111xPin::digitalRead() { return analogRead() >= 0.5f; }
void ADS111xPin::pinMode(uint8_t mode) {}
void ADS111xPin::digitalWrite(uint8_t val) {}
float ADS111xPin::analogRead() { return ads111x->read(channel); }
}; // namespace SlimeVR

View File

@@ -0,0 +1,46 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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 <PinInterface.h>
#include "ADS111xInterface.h"
#pragma once
namespace SlimeVR {
class ADS111xPin : public PinInterface {
public:
ADS111xPin(ADS111xInterface* interface, uint8_t channel);
int digitalRead() override final;
void pinMode(uint8_t mode) override final;
void digitalWrite(uint8_t val);
float analogRead();
private:
ADS111xInterface* ads111x;
uint8_t channel;
};
}; // namespace SlimeVR

View File

@@ -22,8 +22,18 @@
*/
#include "DirectPinInterface.h"
#include "../consts.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); }
void DirectPinInterface::digitalWrite(uint8_t val) { ::digitalWrite(_pinNum, val); }
float DirectPinInterface::analogRead() {
#if ESP8266
return static_cast<float>(::analogRead(_pinNum)) / ADCResolution;
#elif ESP32
return static_cast<float>(::analogReadMilliVolts(_pinNum)) / 1000 / ADCVoltageMax;
#endif
}

View File

@@ -24,7 +24,8 @@
#define _H_DIRECT_PIN_INTERFACE_
#include <Arduino.h>
#include <PinInterface.h>
#include "PinInterface.h"
/**
* Pin interface using direct pins
@@ -32,12 +33,13 @@
*/
class DirectPinInterface : public PinInterface {
public:
DirectPinInterface(uint8_t pin)
explicit DirectPinInterface(uint8_t pin)
: _pinNum(pin){};
int digitalRead() override final;
void pinMode(uint8_t mode) override final;
void digitalWrite(uint8_t val) override final;
float analogRead() override final;
private:
uint8_t _pinNum;

View File

@@ -28,4 +28,6 @@ void MCP23X17PinInterface::pinMode(uint8_t mode) { _mcp23x17->pinMode(_pinNum, m
void MCP23X17PinInterface::digitalWrite(uint8_t val) {
_mcp23x17->digitalWrite(_pinNum, val);
}
}
float MCP23X17PinInterface::analogRead() { return digitalRead() ? 1.0f : 0.0f; }

View File

@@ -24,7 +24,8 @@
#define _H_MCP23X17PinInterface_
#include <Adafruit_MCP23X17.h>
#include <PinInterface.h>
#include "PinInterface.h"
#define MCP_GPA0 0
#define MCP_GPA1 1
@@ -55,6 +56,7 @@ public:
int digitalRead() override final;
void pinMode(uint8_t mode) override final;
void digitalWrite(uint8_t val) override final;
float analogRead() override final;
private:
Adafruit_MCP23X17* _mcp23x17;

View File

@@ -0,0 +1,101 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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 "ParallelMuxInterface.h"
#include <Arduino.h>
#include <cassert>
namespace SlimeVR {
ParallelMuxInterface::ParallelMuxInterface(
PinInterface* dataPin,
std::vector<PinInterface*>& addressPins,
PinInterface* enablePin,
bool enableActiveLevel,
bool addressActiveLevel
)
: dataPin{dataPin}
, addressPins{addressPins}
, enablePin{enablePin}
, enableActiveLevel{enableActiveLevel}
, addressActiveLevel{addressActiveLevel} {
assert(addressPins.size() <= 8);
}
bool ParallelMuxInterface::init() {
if (enablePin != nullptr) {
enablePin->pinMode(OUTPUT);
enablePin->digitalWrite(enableActiveLevel);
}
for (auto* pin : addressPins) {
pin->pinMode(OUTPUT);
pin->digitalWrite(false);
}
return true;
}
void ParallelMuxInterface::pinMode(uint8_t mode) { dataPin->pinMode(mode); }
void ParallelMuxInterface::digitalWrite(uint8_t address, uint8_t value) {
switchTo(address);
dataPin->digitalWrite(value);
}
int ParallelMuxInterface::digitalRead(uint8_t address) {
switchTo(address);
return dataPin->digitalRead();
}
float ParallelMuxInterface::analogRead(uint8_t address) {
switchTo(address);
return dataPin->analogRead();
}
void ParallelMuxInterface::switchTo(uint8_t address) {
assert(address < 1 << addressPins.size());
if (address == currentAddress) {
return;
}
if (enablePin != nullptr) {
enablePin->digitalWrite(!enableActiveLevel);
}
for (auto* addressPin : addressPins) {
bool value = address & 0x01;
address >>= 1;
addressPin->digitalWrite(value);
}
if (enablePin != nullptr) {
enablePin->digitalWrite(enableActiveLevel);
}
currentAddress = address;
delay(1);
}
} // namespace SlimeVR

View File

@@ -0,0 +1,57 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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.
*/
#pragma once
#include <PinInterface.h>
#include <cstdint>
#include <vector>
namespace SlimeVR {
class ParallelMuxInterface {
public:
ParallelMuxInterface(
PinInterface* dataPin,
std::vector<PinInterface*>& addressPins,
PinInterface* enablePin = nullptr,
bool enableActiveLevel = false,
bool addressActiveLevel = true
);
bool init();
void pinMode(uint8_t mode);
void digitalWrite(uint8_t address, uint8_t value);
int digitalRead(uint8_t address);
float analogRead(uint8_t address);
private:
void switchTo(uint8_t address);
PinInterface* const dataPin;
const std::vector<PinInterface*> addressPins;
PinInterface* const enablePin = nullptr;
const bool enableActiveLevel = false;
const bool addressActiveLevel = true;
uint8_t currentAddress = 0;
};
} // namespace SlimeVR

View File

@@ -0,0 +1,34 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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 "ParallelMuxPin.h"
namespace SlimeVR {
ParallelMuxPin::ParallelMuxPin(ParallelMuxInterface* mux, uint8_t address)
: mux{mux}
, address{address} {}
void ParallelMuxPin::pinMode(uint8_t mode) { mux->pinMode(mode); }
void ParallelMuxPin::digitalWrite(uint8_t value) { mux->digitalWrite(address, value); }
int ParallelMuxPin::digitalRead() { return mux->digitalRead(address); }
float ParallelMuxPin::analogRead() { return mux->analogRead(address); }
} // namespace SlimeVR

View File

@@ -0,0 +1,43 @@
/*
SlimeVR Code is placed under the MIT license
Copyright (c) 2025 Gorbit99 & 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.
*/
#pragma once
#include <cstdint>
#include "PinInterface.h"
#include "sensorinterface/ParallelMuxInterface.h"
namespace SlimeVR {
class ParallelMuxPin : public PinInterface {
public:
ParallelMuxPin(ParallelMuxInterface* mux, uint8_t address);
void pinMode(uint8_t mode) final;
void digitalWrite(uint8_t value) final;
int digitalRead() final;
float analogRead() final;
private:
ParallelMuxInterface* const mux;
uint8_t address;
};
} // namespace SlimeVR

View File

@@ -23,22 +23,44 @@
#pragma once
#include <PinInterface.h>
#include <functional>
#include <map>
#include <optional>
#include "ADS111xInterface.h"
#include "ADS111xPin.h"
#include "Adafruit_MCP23X17.h"
#include "DirectPinInterface.h"
#include "I2CPCAInterface.h"
#include "I2CWireSensorInterface.h"
#include "MCP23X17PinInterface.h"
#include "ParallelMuxInterface.h"
#include "SensorInterface.h"
#include "sensorinterface/ParallelMuxPin.h"
namespace SlimeVR {
template <typename T>
static bool operator<(std::initializer_list<T*>& lhs, std::initializer_list<T*>& rhs) {
size_t minLength = std::min(lhs.size(), rhs.size());
for (size_t i = 0; i < minLength; i++) {
if (lhs[i] < rhs[i]) {
return true;
}
if (lhs[i] > rhs[i]) {
return false;
}
}
return lhs.size() < rhs.size();
}
class SensorInterfaceManager {
private:
template <typename InterfaceClass, typename... Args>
struct SensorInterface {
explicit SensorInterface(
struct InterfaceCache {
explicit InterfaceCache(
std::function<bool(Args...)> validate = [](Args...) { return true; }
)
: validate{validate} {}
@@ -72,14 +94,31 @@ public:
inline auto& mcpPinInterface() { return mcpPinInterfaces; }
inline auto& i2cWireInterface() { return i2cWireInterfaces; }
inline auto& pcaWireInterface() { return pcaWireInterfaces; }
inline auto& adsInterface() { return adsInterfaces; }
inline auto& adsPinInterface() { return adsPinInterfaces; }
inline auto& parallelMuxInterface() { return parallelMuxInterfaces; }
inline auto& parallelMuxPinInterface() { return parallelMuxPinInterfaces; }
private:
SensorInterface<DirectPinInterface, int> directPinInterfaces{[](int pin) {
InterfaceCache<DirectPinInterface, int> directPinInterfaces{[](int pin) {
return pin != 255 && pin != -1;
}};
SensorInterface<MCP23X17PinInterface, int> mcpPinInterfaces;
SensorInterface<I2CWireSensorInterface, int, int> i2cWireInterfaces;
SensorInterface<I2CPCASensorInterface, int, int, int, int> pcaWireInterfaces;
InterfaceCache<MCP23X17PinInterface, Adafruit_MCP23X17*, int> mcpPinInterfaces;
InterfaceCache<I2CWireSensorInterface, int, int> i2cWireInterfaces;
InterfaceCache<I2CPCASensorInterface, int, int, int, int> pcaWireInterfaces;
InterfaceCache<ADS111xInterface, SensorInterface*, PinInterface*, int>
adsInterfaces;
InterfaceCache<ADS111xPin, ADS111xInterface*, int> adsPinInterfaces;
InterfaceCache<
ParallelMuxInterface,
PinInterface*,
std::vector<PinInterface*>,
PinInterface*,
bool,
bool>
parallelMuxInterfaces;
InterfaceCache<ParallelMuxPin, ParallelMuxInterface*, uint8_t>
parallelMuxPinInterfaces;
};
} // namespace SlimeVR

View File

@@ -24,18 +24,39 @@
#include "GlobalVars.h"
ADCResistanceSensor::ADCResistanceSensor(
uint8_t id,
float resistanceDivider,
PinInterface* pinInterface,
float smoothFactor
)
: Sensor("ADCResistanceSensor", SensorTypeID::ADC_RESISTANCE, id, 0, 0.0f, nullptr)
, m_PinInterface(pinInterface)
, m_ResistanceDivider(resistanceDivider)
, m_SmoothFactor(smoothFactor) {
working = true;
hadData = true;
lastSampleMicros = micros();
};
void ADCResistanceSensor::motionSetup() { m_PinInterface->pinMode(INPUT); }
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
#elif ESP32
float voltage = ((float)analogReadMilliVolts(m_Pin)) / 1000;
m_Data = m_ResistanceDivider
* (m_VCC / voltage - 1.0f); // Convert voltage to resistance
#endif
if (micros() - lastSampleMicros < samplingStepMicros) {
return;
}
float value = m_PinInterface->analogRead();
m_Data = m_ResistanceDivider * value;
lastSampleMicros += samplingStepMicros;
hasNewSample = true;
}
void ADCResistanceSensor::sendData() {
if (!hasNewSample) {
return;
}
networkConnection.sendFlexData(sensorId, m_Data);
hasNewSample = false;
}
bool ADCResistanceSensor::hasNewDataToSend() { return hasNewSample; }

View File

@@ -22,36 +22,27 @@
*/
#pragma once
#include "sensor.h"
#include "sensorinterface/SensorInterface.h"
#include <PinInterface.h>
class ADCResistanceSensor : Sensor {
#include "../sensorinterface/SensorInterface.h"
#include "sensor.h"
class ADCResistanceSensor : public Sensor {
public:
static constexpr auto TypeID = SensorTypeID::ADC_RESISTANCE;
ADCResistanceSensor(
uint8_t id,
uint8_t pin,
float VCC,
float resistanceDivider,
PinInterface* pinInterface = nullptr,
float smoothFactor = 0.1f
)
: Sensor(
"ADCResistanceSensor",
SensorTypeID::ADC_RESISTANCE,
id,
pin,
0.0f,
new SlimeVR::EmptySensorInterface
)
, m_Pin(pin)
, m_VCC(VCC)
, m_ResistanceDivider(resistanceDivider)
, m_SmoothFactor(smoothFactor){};
~ADCResistanceSensor();
);
~ADCResistanceSensor() = default;
void motionLoop() override final;
void sendData() override final;
void motionSetup() final;
void motionLoop() final;
void sendData() final;
bool hasNewDataToSend() final;
SensorStatus getSensorState() override final { return SensorStatus::SENSOR_OK; }
@@ -60,10 +51,14 @@ public:
};
private:
uint8_t m_Pin;
float m_VCC;
static constexpr uint32_t samplingRateHz = 120;
static constexpr uint64_t samplingStepMicros = 1000'000 / samplingRateHz;
PinInterface* m_PinInterface;
float m_ResistanceDivider;
float m_SmoothFactor;
uint64_t lastSampleMicros = 0;
bool hasNewSample = false;
float m_Data = 0.0f;
};

View File

@@ -23,15 +23,21 @@
#include "SensorManager.h"
#include <initializer_list>
#include <map>
#include <type_traits>
#include "../sensorinterface/ADS111xInterface.h"
#include "../sensorinterface/ADS111xPin.h"
#include "ADCResistanceSensor.h"
#include "PinInterface.h"
#include "bmi160sensor.h"
#include "bno055sensor.h"
#include "bno080sensor.h"
#include "icm20948sensor.h"
#include "mpu6050sensor.h"
#include "mpu9250sensor.h"
#include "sensorinterface/SensorInterface.h"
#include "sensorinterface/SensorInterfaceManager.h"
#include "sensors/softfusion/SoftfusionCalibration.h"
#include "sensors/softfusion/runtimecalibration/RuntimeCalibration.h"
@@ -95,12 +101,47 @@ void SensorManager::setup() {
m_Logger.info("MCP initialized");
}
#define NO_PIN nullptr
#define DIRECT_PIN(pin) interfaceManager.directPinInterface().get(pin)
#define DIRECT_WIRE(scl, sda) interfaceManager.i2cWireInterface().get(scl, sda)
#define MCP_PIN(pin) interfaceManager.mcpPinInterface().get(pin)
#define PCA_WIRE(scl, sda, addr, ch) \
interfaceManager.pcaWireInterface().get(scl, sda, addr, ch);
[[maybe_unused]] static constexpr auto* NO_PIN
= static_cast<PinInterface*>(nullptr);
[[maybe_unused]] static auto DIRECT_PIN = [&](uint8_t pin) constexpr {
return interfaceManager.directPinInterface().get(pin);
};
[[maybe_unused]] static auto DIRECT_WIRE = [&](uint8_t scl, uint8_t sda) constexpr {
return interfaceManager.i2cWireInterface().get(scl, sda);
};
[[maybe_unused]] static auto MCP_PIN = [&](uint8_t pin) constexpr {
return interfaceManager.mcpPinInterface().get(&m_MCP, pin);
};
[[maybe_unused]] static auto PCA_WIRE
= [&](uint8_t scl, uint8_t sda, uint8_t addr, uint8_t ch) constexpr {
return interfaceManager.pcaWireInterface().get(scl, sda, addr, ch);
};
[[maybe_unused]] static auto ADS_PIN
= [&](SensorInterface* interface, PinInterface* drdy, uint8_t addr, uint8_t ch
) constexpr {
return interfaceManager.adsPinInterface().get(
interfaceManager.adsInterface().get(interface, drdy, addr),
ch
);
};
[[maybe_unused]] static auto MUX_PIN
= [&](PinInterface* data,
std::vector<PinInterface*>&& addressPins,
uint8_t channel,
PinInterface* enablePin = nullptr,
bool enableActiveLevel = false,
bool addressActiveLevel = true) constexpr {
return interfaceManager.parallelMuxPinInterface().get(
interfaceManager.parallelMuxInterface().get(
data,
addressPins,
enablePin,
enableActiveLevel,
addressActiveLevel
),
channel
);
};
#define SENSOR_DESC_ENTRY(ImuType, ...) \
{ \
@@ -116,7 +157,7 @@ void SensorManager::setup() {
// Apply descriptor list and expand to entries
SENSOR_DESC_LIST;
#define SENSOR_INFO_ENTRY(ImuID, ...) \
#define SENSOR_INFO_ENTRY(SensorTypeID, ...) \
{ m_Sensors[SensorTypeID]->setSensorInfo(__VA_ARGS__); }
SENSOR_INFO_LIST;
@@ -124,6 +165,10 @@ void SensorManager::setup() {
#undef NO_PIN
#undef DIRECT_PIN
#undef DIRECT_WIRE
#undef MCP_PIN
#undef PCA_WIRE
#undef ADS_PIN
m_Logger.info("%d sensor(s) configured", activeSensorCount);
// Check and scan i2c if no sensors active
if (activeSensorCount == 0) {

View File

@@ -154,6 +154,35 @@ private:
return sensor;
}
// ADC Sensors
template <typename SensorType>
std::unique_ptr<::Sensor> buildSensor(
uint8_t sensorId,
float voltageDivider,
PinInterface* pinInterface,
float smoothingFactor
) {
m_Logger.trace(
"Building Sensor with: id=%d,\n\
voltage divider=%f, smoothing factor=%f\n\
interface=%s",
sensorId,
voltageDivider,
smoothingFactor,
pinInterface
);
std::unique_ptr<::Sensor> sensor = std::make_unique<SensorType>(
sensorId,
voltageDivider,
pinInterface,
smoothingFactor
);
sensor->motionSetup();
return sensor;
}
uint32_t m_LastBundleSentAtMicros = micros();
};
} // namespace Sensors

View File

@@ -25,6 +25,7 @@
#define SENSORS_BMI160SENSOR_H
#include <BMI160.h>
#include <PinInterface.h>
#include "../motionprocessing/GyroTemperatureCalibrator.h"
#include "../motionprocessing/RestDetection.h"

View File

@@ -25,6 +25,7 @@
#define SENSORS_BNO055SENSOR_H
#include <Adafruit_BNO055.h>
#include <PinInterface.h>
#include "sensor.h"

View File

@@ -24,6 +24,7 @@
#define SLIMEVR_ICM20948SENSOR_H_
#include <ICM_20948.h>
#include <PinInterface.h>
#include "SensorFusionDMP.h"
#include "sensor.h"

View File

@@ -25,6 +25,7 @@
#define SENSORS_MPU9250SENSOR_H
#include <MPU9250_6Axis_MotionApps_V6_12.h>
#include <PinInterface.h>
#include "logging/Logger.h"
#include "sensor.h"

View File

@@ -141,6 +141,8 @@ const char* getIMUNameByType(SensorTypeID imuType) {
return "ICM45686";
case SensorTypeID::ICM45605:
return "ICM45605";
case SensorTypeID::ADC_RESISTANCE:
return "Flex Sensor";
case SensorTypeID::Unknown:
case SensorTypeID::Empty:
return "UNKNOWN";

View File

@@ -89,7 +89,7 @@ public:
SensorTypeID getSensorType() { return sensorType; };
const Vector3& getAcceleration() { return acceleration; };
const Quat& getFusedRotation() { return fusedRotation; };
bool hasNewDataToSend() { return newFusedRotation || newAcceleration; };
virtual bool hasNewDataToSend() { return newFusedRotation || newAcceleration; };
inline bool hasCompletedRestCalibration() { return restCalibrationComplete; }
void setFlag(SensorToggles toggle, bool state);
[[nodiscard]] virtual bool isFlagSupported(SensorToggles toggle) const {