From 5e3d35b0b887604c1c4e64344f54c7a7ac20d4f5 Mon Sep 17 00:00:00 2001 From: ewowi Date: Tue, 10 Feb 2026 21:30:42 +0100 Subject: [PATCH 1/5] add MPU6050 driver pio.ini: add MPU6050 driver backend ======= - NodeManager: send onUpdate to node when on/off changed - Nodes: add sharedData.gravity, add D_MPU6050.h - Module Drivers: add D_MPU6050.h - D_MPU6050.h: New - Effects: particles: add gyro --- platformio.ini | 3 +- src/MoonBase/NodeManager.h | 1 + src/MoonBase/Nodes.h | 3 + src/MoonLight/Modules/ModuleDrivers.h | 2 + src/MoonLight/Nodes/Drivers/D_MPU6050.h | 140 ++++++++++++++++++++++ src/MoonLight/Nodes/Effects/E_MoonLight.h | 14 +-- 6 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 src/MoonLight/Nodes/Drivers/D_MPU6050.h diff --git a/platformio.ini b/platformio.ini index d4e05a8b..d85ef069 100644 --- a/platformio.ini +++ b/platformio.ini @@ -141,7 +141,8 @@ extra_scripts = scripts/save_elf.py lib_deps = ArduinoJson@>=7.0.0 - elims/PsychicMqttClient@^0.2.4 + elims/PsychicMqttClient@^0.2.4 + ElectronicCats/MPU6050 @ 1.3.0 ; for D_MPU6050 driver ;πŸ’« [moonlight] diff --git a/src/MoonBase/NodeManager.h b/src/MoonBase/NodeManager.h index 57e70068..9208e00b 100644 --- a/src/MoonBase/NodeManager.h +++ b/src/MoonBase/NodeManager.h @@ -264,6 +264,7 @@ class NodeManager : public Module { if (nodeClass != nullptr) { nodeClass->on = updatedItem.value.as(); // set nodeclass on/off // EXT_LOGD(ML_TAG, " nodeclass πŸ”˜:%d πŸš₯:%d πŸ’Ž:%d", nodeClass->on, nodeClass->hasOnLayout(), nodeClass->hasModifier()); + nodeClass->onUpdate(updatedItem.oldValue, nodeState); // custom onUpdate for the node nodeClass->requestMappings(); } else EXT_LOGW(ML_TAG, "Nodeclass %s not found", nodeState["name"].as()); diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index 97fc6c22..38540877 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -344,6 +344,8 @@ static struct SharedData { size_t connectedClients; size_t activeClients; size_t clientListSize; + + Coord3D gravity; } sharedData; /** @@ -360,6 +362,7 @@ static struct SharedData { #include "MoonLight/Nodes/Drivers/D_FastLED.h" #include "MoonLight/Nodes/Drivers/D_Hub75.h" #include "MoonLight/Nodes/Drivers/D_Infrared.h" + #include "MoonLight/Nodes/Drivers/D_MPU6050.h" #include "MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h" #include "MoonLight/Nodes/Drivers/D__Sandbox.h" #include "MoonLight/Nodes/Effects/E_FastLED.h" diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 40dfeb3e..18664b93 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -106,6 +106,7 @@ class ModuleDrivers : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); // board preset specific @@ -143,6 +144,7 @@ class ModuleDrivers : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); // board preset specific diff --git a/src/MoonLight/Nodes/Drivers/D_MPU6050.h b/src/MoonLight/Nodes/Drivers/D_MPU6050.h new file mode 100644 index 00000000..c38bfd68 --- /dev/null +++ b/src/MoonLight/Nodes/Drivers/D_MPU6050.h @@ -0,0 +1,140 @@ +/** + @title MoonLight + @file D_MPU6050.h + @repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs + @Authors https://github.com/MoonModules/MoonLight/commits/main + @Doc https://moonmodules.org/MoonLight/moonlight/overview/ + @Copyright Β© 2026 Github MoonLight Commit Authors + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license For non GPL-v3 usage, commercial licenses must be purchased. Contact us for more information. +**/ + +#if FT_MOONLIGHT + + #include + +class MPU6050Driver : public Node { + public: + static const char* name() { return "MPU6050 driver"; } + static uint8_t dim() { return _NoD; } + static const char* tags() { return "☸️"; } + + bool motionTrackingReady = false; // set true if DMP init was successful + + Coord3D gyro; // in degrees (not radians) + Coord3D accell; + VectorFloat gravityVector; + + void setup() override { + // controls will show in the UI + // for different type of controls see other Nodes + // addControl(pin, "pin", "slider", 1, SOC_GPIO_PIN_COUNT - 1); + addControl(gyro, "gyro", "coord3D"); + addControl(accell, "accell", "coord3D"); + // isEnabled = false; // need to enable after fresh setup + } + + bool initI2S() { + // tbd: set pins in ui!! + // allocatePin(21, "Pins", "I2S SDA"); + // allocatePin(22, "Pins", "I2S SCL"); + bool success = Wire.begin(5, 6); + EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure"); + return success; + } + + void onUpdate(const Char<20>& oldValue, const JsonObject& control) { + // add your custom onUpdate code here + if (!control["on"].isNull()) { // control is the node n case of on! + if (control["on"] == true) { + if (initI2S()) { + mpu.initialize(); + + // verify connection + if (mpu.testConnection()) { + EXT_LOGI(ML_TAG, "MPU6050 connection successful Initializing DMP..."); + uint8_t devStatus = mpu.dmpInitialize(); + + if (devStatus == 0) { + // // Calibration Time: generate offsets and calibrate our MPU6050 + mpu.CalibrateAccel(6); + mpu.CalibrateGyro(6); + // mpu.PrintActiveOffsets(); + + mpu.setDMPEnabled(true); // mandatory + + // mpuIntStatus = mpu.getIntStatus(); + + motionTrackingReady = true; + } else { + // ERROR! + // 1 = initial memory load failed + // 2 = DMP configuration updates failed + // (if it's going to break, usually the code will be 1) + EXT_LOGI(ML_TAG, "DMP Initialization failed (code %d)", devStatus); + } + } else + EXT_LOGI(ML_TAG, "Testing device connections MPU6050 connection failed"); + } + } + } + } + + bool hasOnLayout() const override { return true; } // so the mapping system knows this node has onLayout, eg each time a modifier changes + void onLayout() override {}; // onLayout for drivers is used to init or update the driver based on the layouts, a driver will use the layout nodes which are defined before the driver node, so order matters + + // use for continuous actions, e.g. reading data from sensors or sending data to lights (e.g. LED drivers or Art-Net) + void loop20ms() override { + // mpu.getMotion6(&accell.x, &accell.y, &accell.z, &gyro.x, &gyro.y, &gyro.z); + // // display tab-separated accel/gyro x/y/z values + // EXT_LOGI(ML_TAG, "mpu6050 %d,%d,%d %d,%d,%d", accell.x, accell.y, accell.z, gyro.x, gyro.y, gyro.z); + + // if programming failed, don't try to do anything + if (!motionTrackingReady) return; + // read a packet from FIFO + if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet + mpu.dmpGetQuaternion(&q, fifoBuffer); + mpu.dmpGetGravity(&gravity, &q); + mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); + gyro.y = ypr[0] * 180 / M_PI; // pan = yaw ! + gyro.x = ypr[1] * 180 / M_PI; // tilt = pitch ! + gyro.z = ypr[2] * 180 / M_PI; // roll = roll + sharedData.gravity.x = gravity.x; + sharedData.gravity.y = gravity.y; + sharedData.gravity.z = gravity.z; + // display real acceleration, adjusted to remove gravity + + // needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0) + mpu.dmpGetQuaternion(&q, fifoBuffer); + mpu.dmpGetAccel(&aa, fifoBuffer); + mpu.dmpGetGravity(&gravity, &q); + + mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); + // mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q); //worked in 0.6.0, not in 1.3.0 anymore + + accell.x = aaReal.x; + accell.y = aaReal.y; + accell.z = aaReal.z; + } + }; + + ~MPU6050Driver() override {}; // e.g. to free allocated memory + + private: + MPU6050 mpu; + + // MPU control/status vars + uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) + uint8_t fifoBuffer[64]; // FIFO storage buffer + + // orientation/motion vars + Quaternion q; // [w, x, y, z] quaternion container + VectorInt16 aa; // [x, y, z] accel sensor measurements + VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements + // VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements + VectorFloat gravity; // [x, y, z] gravity vector + // float euler[3]; // [psi, theta, phi] Euler angle container + float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container and gravity vector +}; + +#endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_MoonLight.h b/src/MoonLight/Nodes/Effects/E_MoonLight.h index 577500c2..7e9beb1b 100644 --- a/src/MoonLight/Nodes/Effects/E_MoonLight.h +++ b/src/MoonLight/Nodes/Effects/E_MoonLight.h @@ -1177,11 +1177,7 @@ class ParticlesEffect : public Node { uint8_t speed = 15; uint8_t numParticles = 10; bool barriers = false; - #ifdef STARBASE_USERMOD_MPU6050 - bool gyro = true; - #else bool gyro = false; - #endif bool randomGravity = true; uint8_t gravityChangeInterval = 5; // bool debugPrint = layer->effectData.read(); @@ -1191,9 +1187,7 @@ class ParticlesEffect : public Node { addControl(speed, "speed", "slider", 0, 30); addControl(numParticles, "number of Particles", "slider", 1, 255); addControl(barriers, "barriers", "checkbox"); - #ifdef STARBASE_USERMOD_MPU6050 addControl(gyro, "gyro", "checkbox"); - #endif addControl(randomGravity, "randomGravity", "checkbox"); addControl(gravityChangeInterval, "gravityChangeInterval", "slider", 1, 10); // addControl(bool, "Debug Print", layer->effectData.write(0)); @@ -1256,18 +1250,16 @@ class ParticlesEffect : public Node { float gravityX, gravityY, gravityZ; // Gravity if using gyro or random gravity - #ifdef STARBASE_USERMOD_MPU6050 if (gyro) { - gravity[0] = -mpu6050->gravityVector.x; - gravity[1] = mpu6050->gravityVector.z; // Swap Y and Z axis - gravity[2] = -mpu6050->gravityVector.y; + gravity[0] = -sharedData.gravity.x; + gravity[1] = sharedData.gravity.z; // Swap Y and Z axis + gravity[2] = -sharedData.gravity.y; if (layer->layerDimension == _2D) { // Swap back Y and Z axis set Z to 0 gravity[1] = -gravity[2]; gravity[2] = 0; } } - #endif if (randomGravity) { if (pal::millis() - gravUpdate > gravityChangeInterval * 1000) { From 66f1d6de28f2237ad79df7d1263d7cb1d557f853 Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 11 Feb 2026 11:17:16 +0100 Subject: [PATCH 2/5] MPU6050 driver to IMU driver Change MPU6050 driver to IMU driver and add board control (MPU6050 and BMI160) I2S -< I2C, including safety delays - WIP add scanI2C --- platformio.ini | 2 +- src/MoonBase/Nodes.h | 2 +- src/MoonLight/Modules/ModuleDrivers.h | 4 +- .../Nodes/Drivers/{D_MPU6050.h => D_IMU.h} | 147 +++++++++++------- src/MoonLight/Nodes/Drivers/D__Sandbox.h | 2 +- src/MoonLight/Nodes/Effects/E__Sandbox.h | 2 +- 6 files changed, 97 insertions(+), 62 deletions(-) rename src/MoonLight/Nodes/Drivers/{D_MPU6050.h => D_IMU.h} (50%) diff --git a/platformio.ini b/platformio.ini index d85ef069..ab943936 100644 --- a/platformio.ini +++ b/platformio.ini @@ -142,7 +142,7 @@ extra_scripts = lib_deps = ArduinoJson@>=7.0.0 elims/PsychicMqttClient@^0.2.4 - ElectronicCats/MPU6050 @ 1.3.0 ; for D_MPU6050 driver + ElectronicCats/MPU6050 @ 1.3.0 ; for D_IMU.h driver ;πŸ’« [moonlight] diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index 38540877..f13272bd 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -362,7 +362,7 @@ static struct SharedData { #include "MoonLight/Nodes/Drivers/D_FastLED.h" #include "MoonLight/Nodes/Drivers/D_Hub75.h" #include "MoonLight/Nodes/Drivers/D_Infrared.h" - #include "MoonLight/Nodes/Drivers/D_MPU6050.h" + #include "MoonLight/Nodes/Drivers/D_IMU.h" #include "MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h" #include "MoonLight/Nodes/Drivers/D__Sandbox.h" #include "MoonLight/Nodes/Effects/E_FastLED.h" diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 18664b93..d574aa7b 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -106,7 +106,7 @@ class ModuleDrivers : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); - addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); // board preset specific @@ -144,7 +144,7 @@ class ModuleDrivers : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); - if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); // board preset specific diff --git a/src/MoonLight/Nodes/Drivers/D_MPU6050.h b/src/MoonLight/Nodes/Drivers/D_IMU.h similarity index 50% rename from src/MoonLight/Nodes/Drivers/D_MPU6050.h rename to src/MoonLight/Nodes/Drivers/D_IMU.h index c38bfd68..f08973a9 100644 --- a/src/MoonLight/Nodes/Drivers/D_MPU6050.h +++ b/src/MoonLight/Nodes/Drivers/D_IMU.h @@ -15,7 +15,7 @@ class MPU6050Driver : public Node { public: - static const char* name() { return "MPU6050 driver"; } + static const char* name() { return "IMU driver"; } // Inertial Measurement Unit static uint8_t dim() { return _NoD; } static const char* tags() { return "☸️"; } @@ -24,6 +24,7 @@ class MPU6050Driver : public Node { Coord3D gyro; // in degrees (not radians) Coord3D accell; VectorFloat gravityVector; + uint8_t board = 0; void setup() override { // controls will show in the UI @@ -32,49 +33,79 @@ class MPU6050Driver : public Node { addControl(gyro, "gyro", "coord3D"); addControl(accell, "accell", "coord3D"); // isEnabled = false; // need to enable after fresh setup + addControl(board, "board", "select"); + addControlValue("MPU6050"); + addControlValue("BMI160"); // not supported yet } - bool initI2S() { + bool initI2C() { // tbd: set pins in ui!! // allocatePin(21, "Pins", "I2S SDA"); // allocatePin(22, "Pins", "I2S SCL"); + Wire.end(); + delay(10); bool success = Wire.begin(5, 6); - EXT_LOGI(ML_TAG, "initI2S Wire begin %s", success ? "success" : "failure"); + EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure"); + + if (success) { + delay(200); // Give I2C bus time to stabilize + Wire.setClock(50000); // Explicitly set to 100kHz + } + return success; } - void onUpdate(const Char<20>& oldValue, const JsonObject& control) { + void scanI2C() { + EXT_LOGI(ML_TAG, "Scanning I2C bus..."); + byte count = 0; + for (byte i = 1; i < 127; i++) { + Wire.beginTransmission(i); + if (Wire.endTransmission() == 0) { + EXT_LOGI(ML_TAG, "Found I2C device at address 0x%02X", i); + count++; + } + } + EXT_LOGI(ML_TAG, "Found %d device(s)", count); + } + + void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { // add your custom onUpdate code here - if (!control["on"].isNull()) { // control is the node n case of on! + if (!control["on"].isNull()) { // control is the node n case of on! if (control["on"] == true) { - if (initI2S()) { - mpu.initialize(); - - // verify connection - if (mpu.testConnection()) { - EXT_LOGI(ML_TAG, "MPU6050 connection successful Initializing DMP..."); - uint8_t devStatus = mpu.dmpInitialize(); - - if (devStatus == 0) { - // // Calibration Time: generate offsets and calibrate our MPU6050 - mpu.CalibrateAccel(6); - mpu.CalibrateGyro(6); - // mpu.PrintActiveOffsets(); - - mpu.setDMPEnabled(true); // mandatory - - // mpuIntStatus = mpu.getIntStatus(); - - motionTrackingReady = true; - } else { - // ERROR! - // 1 = initial memory load failed - // 2 = DMP configuration updates failed - // (if it's going to break, usually the code will be 1) - EXT_LOGI(ML_TAG, "DMP Initialization failed (code %d)", devStatus); - } - } else - EXT_LOGI(ML_TAG, "Testing device connections MPU6050 connection failed"); + if (initI2C()) { + scanI2C(); // Diagnostic - remove after testing + + if (board == 0) { // MPU6050 + mpu.initialize(); + + delay(100); + + // verify connection + if (mpu.testConnection()) { + EXT_LOGI(ML_TAG, "MPU6050 connection successful Initializing DMP..."); + uint8_t devStatus = mpu.dmpInitialize(); + + if (devStatus == 0) { + // // Calibration Time: generate offsets and calibrate our MPU6050 + mpu.CalibrateAccel(6); + mpu.CalibrateGyro(6); + // mpu.PrintActiveOffsets(); + + mpu.setDMPEnabled(true); // mandatory + + // mpuIntStatus = mpu.getIntStatus(); + + motionTrackingReady = true; + } else { + // ERROR! + // 1 = initial memory load failed + // 2 = DMP configuration updates failed + // (if it's going to break, usually the code will be 1) + EXT_LOGI(ML_TAG, "DMP Initialization failed (code %d)", devStatus); + } + } else + EXT_LOGI(ML_TAG, "Testing device connections MPU6050 connection failed"); + } } } } @@ -92,29 +123,33 @@ class MPU6050Driver : public Node { // if programming failed, don't try to do anything if (!motionTrackingReady) return; // read a packet from FIFO - if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet - mpu.dmpGetQuaternion(&q, fifoBuffer); - mpu.dmpGetGravity(&gravity, &q); - mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); - gyro.y = ypr[0] * 180 / M_PI; // pan = yaw ! - gyro.x = ypr[1] * 180 / M_PI; // tilt = pitch ! - gyro.z = ypr[2] * 180 / M_PI; // roll = roll - sharedData.gravity.x = gravity.x; - sharedData.gravity.y = gravity.y; - sharedData.gravity.z = gravity.z; - // display real acceleration, adjusted to remove gravity - - // needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0) - mpu.dmpGetQuaternion(&q, fifoBuffer); - mpu.dmpGetAccel(&aa, fifoBuffer); - mpu.dmpGetGravity(&gravity, &q); - - mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); - // mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q); //worked in 0.6.0, not in 1.3.0 anymore - - accell.x = aaReal.x; - accell.y = aaReal.y; - accell.z = aaReal.z; + if (board == 0) { // MPU6050 + if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet + mpu.dmpGetQuaternion(&q, fifoBuffer); + mpu.dmpGetGravity(&gravity, &q); + mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); + gyro.y = ypr[0] * 180 / M_PI; // pan = yaw ! + gyro.x = ypr[1] * 180 / M_PI; // tilt = pitch ! + gyro.z = ypr[2] * 180 / M_PI; // roll = roll + sharedData.gravity.x = gravity.x; + sharedData.gravity.y = gravity.y; + sharedData.gravity.z = gravity.z; + // display real acceleration, adjusted to remove gravity + + EXT_LOGD(ML_TAG, "%f %f %f", gravity.x, gravity.y, gravity.z); + + // needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0) + mpu.dmpGetQuaternion(&q, fifoBuffer); + mpu.dmpGetAccel(&aa, fifoBuffer); + mpu.dmpGetGravity(&gravity, &q); + + mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); + // mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q); //worked in 0.6.0, not in 1.3.0 anymore + + accell.x = aaReal.x; + accell.y = aaReal.y; + accell.z = aaReal.z; + } } }; diff --git a/src/MoonLight/Nodes/Drivers/D__Sandbox.h b/src/MoonLight/Nodes/Drivers/D__Sandbox.h index 94a803ee..a7c5b956 100644 --- a/src/MoonLight/Nodes/Drivers/D__Sandbox.h +++ b/src/MoonLight/Nodes/Drivers/D__Sandbox.h @@ -28,7 +28,7 @@ class ExampleDriver : public Node { addControl(pin, "pin", "slider", 1, SOC_GPIO_PIN_COUNT - 1); } - void onUpdate(const Char<20>& oldValue, const JsonObject& control) { + void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { // add your custom onUpdate code here if (control["name"] == "pin") { if (control["value"] == 0) { diff --git a/src/MoonLight/Nodes/Effects/E__Sandbox.h b/src/MoonLight/Nodes/Effects/E__Sandbox.h index 56721fd7..3c87ed26 100644 --- a/src/MoonLight/Nodes/Effects/E__Sandbox.h +++ b/src/MoonLight/Nodes/Effects/E__Sandbox.h @@ -32,7 +32,7 @@ class ExampleEffect : public Node { void onSizeChanged(const Coord3D& prevSize) override {} // e.g. realloc variables - void onUpdate(const Char<20>& oldValue, const JsonObject& control) { + void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { // add your custom onUpdate code here if (control["name"] == "bpm") { if (control["value"] == 0) { From 48e3647f97d2d5f8473bf507f2571a90811700a8 Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 11 Feb 2026 13:16:04 +0100 Subject: [PATCH 3/5] Move I2C mgmt to Module IO (from IMU driver) ModuleIO - Include Wire - Add controls i2cFreq and i2cBus - default assign sda to pin 21 and scl to pin 22 - readPins: add I2C pin assignment - add updateDevices: Scanning I2C bus and send frequency and addresses to UI IMU driver - remove initI2C and scan I2C - gravity xyz float to int correction - WIP --- src/MoonBase/Modules/ModuleIO.h | 68 ++++++++++++++++++++++++++++- src/MoonLight/Nodes/Drivers/D_IMU.h | 49 ++++----------------- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 93bf2335..37b3c927 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -14,6 +14,8 @@ #if FT_MOONBASE == 1 + #include + #include "MoonBase/Module.h" #include "driver/uart.h" @@ -216,6 +218,15 @@ class ModuleIO : public Module { addControl(rows, "Level", "text", 0, 32, true); // ro addControl(rows, "DriveCap", "text", 0, 32, true); // ro } + + addControl(controls, "i2cFreq", "number", 0, 65534, true); + + control = addControl(controls, "i2cBus", "rows"); + control["crud"] = "r"; + rows = control["n"].to(); + { + addControl(rows, "address", "number", 0, 255, true); // ro + } } class PinAssigner { @@ -564,6 +575,8 @@ class ModuleIO : public Module { #else pinAssigner.assignPin(16, pin_LED); #endif + pinAssigner.assignPin(21, pin_I2C_SDA); + pinAssigner.assignPin(22, pin_I2C_SCL); // trying to add more pins, but these pins not liked by esp32-d0-16MB ... 🚧 // pinAssigner.assignPin(4, pin_LED_02; @@ -790,7 +803,31 @@ class ModuleIO : public Module { } #endif } - } + + for (JsonObject pinObject : _state.data["pins"].as()) { + uint8_t usage = pinObject["usage"]; + if (usage == pin_I2C_SDA) { + pinI2CSDA = pinObject["GPIO"]; + EXT_LOGD(ML_TAG, "I2CSDA found %d", pinI2CSDA); + } + if (usage == pin_I2C_SCL) { + pinI2CSCL = pinObject["GPIO"]; + EXT_LOGD(ML_TAG, "I2CSCL found %d", pinI2CSCL); + } + } + + if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { + if (Wire.begin(pinI2CSDA, pinI2CSCL)) { + EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d", pinI2CSDA, pinI2CSCL); + // delay(200); // Give I2C bus time to stabilize + // Wire.setClock(50000); // Explicitly set to 100kHz + } else + EXT_LOGE(ML_TAG, "initI2C Wire failed"); + } + } // readPins + + uint8_t pinI2CSDA = UINT8_MAX; + uint8_t pinI2CSCL = UINT8_MAX; #if FT_BATTERY uint8_t pinVoltage = UINT8_MAX; @@ -865,6 +902,35 @@ class ModuleIO : public Module { } } #endif + if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { + updateDevices(); + } + } + + void updateDevices() { + JsonDocument doc; + doc["i2cBus"].to(); + JsonObject newState = doc.as(); + + EXT_LOGI(ML_TAG, "Scanning I2C bus..."); + byte count = 0; + for (byte i = 1; i < 127; i++) { + Wire.beginTransmission(i); + if (Wire.endTransmission() == 0) { + JsonObject i2cDevice = newState["i2cBus"].as().add(); + i2cDevice["address"] = i; + + EXT_LOGI(ML_TAG, "Found I2C device at address 0x%02X", i); + count++; + } + } + EXT_LOGI(ML_TAG, "Found %d device(s)", count); + JsonObject i2cDevice = newState["i2cBus"].as().add(); + i2cDevice["address"] = 255; + + doc["i2cFreq"] = Wire.getClock(); + + update(newState, ModuleState::update, _moduleName); } private: diff --git a/src/MoonLight/Nodes/Drivers/D_IMU.h b/src/MoonLight/Nodes/Drivers/D_IMU.h index f08973a9..699927f4 100644 --- a/src/MoonLight/Nodes/Drivers/D_IMU.h +++ b/src/MoonLight/Nodes/Drivers/D_IMU.h @@ -13,9 +13,9 @@ #include -class MPU6050Driver : public Node { +class IMUDriver : public Node { public: - static const char* name() { return "IMU driver"; } // Inertial Measurement Unit + static const char* name() { return "IMU driver"; } // Inertial Measurement Unit static uint8_t dim() { return _NoD; } static const char* tags() { return "☸️"; } @@ -35,46 +35,15 @@ class MPU6050Driver : public Node { // isEnabled = false; // need to enable after fresh setup addControl(board, "board", "select"); addControlValue("MPU6050"); - addControlValue("BMI160"); // not supported yet - } - - bool initI2C() { - // tbd: set pins in ui!! - // allocatePin(21, "Pins", "I2S SDA"); - // allocatePin(22, "Pins", "I2S SCL"); - Wire.end(); - delay(10); - bool success = Wire.begin(5, 6); - EXT_LOGI(ML_TAG, "initI2C Wire begin %s", success ? "success" : "failure"); - - if (success) { - delay(200); // Give I2C bus time to stabilize - Wire.setClock(50000); // Explicitly set to 100kHz - } - - return success; - } - - void scanI2C() { - EXT_LOGI(ML_TAG, "Scanning I2C bus..."); - byte count = 0; - for (byte i = 1; i < 127; i++) { - Wire.beginTransmission(i); - if (Wire.endTransmission() == 0) { - EXT_LOGI(ML_TAG, "Found I2C device at address 0x%02X", i); - count++; - } - } - EXT_LOGI(ML_TAG, "Found %d device(s)", count); + addControlValue("BMI160"); // not supported yet } void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { // add your custom onUpdate code here if (!control["on"].isNull()) { // control is the node n case of on! if (control["on"] == true) { - if (initI2C()) { - scanI2C(); // Diagnostic - remove after testing - + bool i2cInited = true; // todo: check in moduleIO if successfull + if (i2cInited) { if (board == 0) { // MPU6050 mpu.initialize(); @@ -131,9 +100,9 @@ class MPU6050Driver : public Node { gyro.y = ypr[0] * 180 / M_PI; // pan = yaw ! gyro.x = ypr[1] * 180 / M_PI; // tilt = pitch ! gyro.z = ypr[2] * 180 / M_PI; // roll = roll - sharedData.gravity.x = gravity.x; - sharedData.gravity.y = gravity.y; - sharedData.gravity.z = gravity.z; + sharedData.gravity.x = gravity.x * INT16_MAX; + sharedData.gravity.y = gravity.y * INT16_MAX; + sharedData.gravity.z = gravity.z * INT16_MAX; // display real acceleration, adjusted to remove gravity EXT_LOGD(ML_TAG, "%f %f %f", gravity.x, gravity.y, gravity.z); @@ -153,7 +122,7 @@ class MPU6050Driver : public Node { } }; - ~MPU6050Driver() override {}; // e.g. to free allocated memory + ~IMUDriver() override {}; // e.g. to free allocated memory private: MPU6050 mpu; From 77d11ecfb8e2b78e800bc82e5fb0ec31adcd3427 Mon Sep 17 00:00:00 2001 From: ewowi Date: Wed, 11 Feb 2026 14:09:15 +0100 Subject: [PATCH 4/5] Module IO and IMU driver tweaks Module IO - setup, onUpdate: i2cFreq is modifyable (in kHz) - setBoardPresetDefaults: i2c default 21,22 on esp32d0 and 8, 9 on others - wip - readPins: reinit Wire after i2c pins changed, using freq, and updateDevices(not every s) IMU module - remove gravityVector --- src/MoonBase/Modules/ModuleIO.h | 21 ++++++++++++++------- src/MoonBase/NodeManager.h | 2 ++ src/MoonLight/Nodes/Drivers/D_IMU.h | 20 +++++++------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 37b3c927..9f6b4ae7 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -219,7 +219,7 @@ class ModuleIO : public Module { addControl(rows, "DriveCap", "text", 0, 32, true); // ro } - addControl(controls, "i2cFreq", "number", 0, 65534, true); + addControl(controls, "i2cFreq", "number", 0, 65534, false, "kHz"); control = addControl(controls, "i2cBus", "rows"); control["crud"] = "r"; @@ -575,8 +575,13 @@ class ModuleIO : public Module { #else pinAssigner.assignPin(16, pin_LED); #endif + #ifdef CONFIG_IDF_TARGET_ESP32 pinAssigner.assignPin(21, pin_I2C_SDA); pinAssigner.assignPin(22, pin_I2C_SCL); + #else + pinAssigner.assignPin(8, pin_I2C_SDA); + pinAssigner.assignPin(9, pin_I2C_SCL); + #endif // trying to add more pins, but these pins not liked by esp32-d0-16MB ... 🚧 // pinAssigner.assignPin(4, pin_LED_02; @@ -622,6 +627,8 @@ class ModuleIO : public Module { newState["modded"] = true; } else if (updatedItem.name == "usage") { newState["modded"] = true; + } else if (updatedItem.name == "i2cFreq") { + Wire.setClock(updatedItem.value.as() * 1000); } if (newState.size()) update(newState, ModuleState::update, _moduleName); // if changes made then update @@ -817,10 +824,13 @@ class ModuleIO : public Module { } if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { - if (Wire.begin(pinI2CSDA, pinI2CSCL)) { - EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d", pinI2CSDA, pinI2CSCL); + Wire.end(); // Clean up any previous I2C initialization + uint16_t frequency = _state.data["i2cFreq"]; + if (Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000)) { + EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d freq:%d", pinI2CSDA, pinI2CSCL, frequency); // delay(200); // Give I2C bus time to stabilize // Wire.setClock(50000); // Explicitly set to 100kHz + updateDevices(); } else EXT_LOGE(ML_TAG, "initI2C Wire failed"); } @@ -902,9 +912,6 @@ class ModuleIO : public Module { } } #endif - if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { - updateDevices(); - } } void updateDevices() { @@ -928,7 +935,7 @@ class ModuleIO : public Module { JsonObject i2cDevice = newState["i2cBus"].as().add(); i2cDevice["address"] = 255; - doc["i2cFreq"] = Wire.getClock(); + doc["i2cFreq"] = Wire.getClock() / 1000; update(newState, ModuleState::update, _moduleName); } diff --git a/src/MoonBase/NodeManager.h b/src/MoonBase/NodeManager.h index 9208e00b..6a6098b7 100644 --- a/src/MoonBase/NodeManager.h +++ b/src/MoonBase/NodeManager.h @@ -264,7 +264,9 @@ class NodeManager : public Module { if (nodeClass != nullptr) { nodeClass->on = updatedItem.value.as(); // set nodeclass on/off // EXT_LOGD(ML_TAG, " nodeclass πŸ”˜:%d πŸš₯:%d πŸ’Ž:%d", nodeClass->on, nodeClass->hasOnLayout(), nodeClass->hasModifier()); + xSemaphoreTake(*nodeClass->layerMutex, portMAX_DELAY); nodeClass->onUpdate(updatedItem.oldValue, nodeState); // custom onUpdate for the node + xSemaphoreGive(*nodeClass->layerMutex); nodeClass->requestMappings(); } else EXT_LOGW(ML_TAG, "Nodeclass %s not found", nodeState["name"].as()); diff --git a/src/MoonLight/Nodes/Drivers/D_IMU.h b/src/MoonLight/Nodes/Drivers/D_IMU.h index 699927f4..df398253 100644 --- a/src/MoonLight/Nodes/Drivers/D_IMU.h +++ b/src/MoonLight/Nodes/Drivers/D_IMU.h @@ -23,7 +23,6 @@ class IMUDriver : public Node { Coord3D gyro; // in degrees (not radians) Coord3D accell; - VectorFloat gravityVector; uint8_t board = 0; void setup() override { @@ -47,7 +46,7 @@ class IMUDriver : public Node { if (board == 0) { // MPU6050 mpu.initialize(); - delay(100); + // delay(100); // verify connection if (mpu.testConnection()) { @@ -70,20 +69,16 @@ class IMUDriver : public Node { // 1 = initial memory load failed // 2 = DMP configuration updates failed // (if it's going to break, usually the code will be 1) - EXT_LOGI(ML_TAG, "DMP Initialization failed (code %d)", devStatus); + EXT_LOGW(ML_TAG, "DMP Initialization failed (code %d)", devStatus); } } else - EXT_LOGI(ML_TAG, "Testing device connections MPU6050 connection failed"); + EXT_LOGW(ML_TAG, "Testing device connections MPU6050 connection failed"); } } } } } - bool hasOnLayout() const override { return true; } // so the mapping system knows this node has onLayout, eg each time a modifier changes - void onLayout() override {}; // onLayout for drivers is used to init or update the driver based on the layouts, a driver will use the layout nodes which are defined before the driver node, so order matters - - // use for continuous actions, e.g. reading data from sensors or sending data to lights (e.g. LED drivers or Art-Net) void loop20ms() override { // mpu.getMotion6(&accell.x, &accell.y, &accell.z, &gyro.x, &gyro.y, &gyro.z); // // display tab-separated accel/gyro x/y/z values @@ -108,9 +103,9 @@ class IMUDriver : public Node { EXT_LOGD(ML_TAG, "%f %f %f", gravity.x, gravity.y, gravity.z); // needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0) - mpu.dmpGetQuaternion(&q, fifoBuffer); - mpu.dmpGetAccel(&aa, fifoBuffer); - mpu.dmpGetGravity(&gravity, &q); + // mpu.dmpGetQuaternion(&q, fifoBuffer); + // mpu.dmpGetAccel(&aa, fifoBuffer); + // mpu.dmpGetGravity(&gravity, &q); mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); // mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q); //worked in 0.6.0, not in 1.3.0 anymore @@ -122,13 +117,12 @@ class IMUDriver : public Node { } }; - ~IMUDriver() override {}; // e.g. to free allocated memory + ~IMUDriver() override {}; private: MPU6050 mpu; // MPU control/status vars - uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint8_t fifoBuffer[64]; // FIFO storage buffer // orientation/motion vars From c73ab8690f310cc0921bf0d60183ebd35949677a Mon Sep 17 00:00:00 2001 From: ewowi Date: Thu, 12 Feb 2026 09:54:04 +0100 Subject: [PATCH 5/5] Module IO I2S tweaks, IMU fix accell calculation ModuleIO: add I2C pins for default boards per MCU type, reset pins before update IMU Driver: fix accell calculation, BMI160 code (commented ATM) --- platformio.ini | 1 + src/MoonBase/Modules/ModuleIO.h | 27 ++++++++++++++++++-- src/MoonLight/Nodes/Drivers/D_IMU.h | 38 ++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index ab943936..93a5b463 100644 --- a/platformio.ini +++ b/platformio.ini @@ -143,6 +143,7 @@ lib_deps = ArduinoJson@>=7.0.0 elims/PsychicMqttClient@^0.2.4 ElectronicCats/MPU6050 @ 1.3.0 ; for D_IMU.h driver + ; https://github.com/hanyazou/BMI160-Arduino.git ; hanyazou/BMI160-Arduino#057f36e002bee0473a54fcedf41b93acad059568 ; @ ^1.0.0 ; for BMI160 ;πŸ’« [moonlight] diff --git a/src/MoonBase/Modules/ModuleIO.h b/src/MoonBase/Modules/ModuleIO.h index 9f6b4ae7..5392efbf 100644 --- a/src/MoonBase/Modules/ModuleIO.h +++ b/src/MoonBase/Modules/ModuleIO.h @@ -14,7 +14,7 @@ #if FT_MOONBASE == 1 - #include + #include // for i2C #include "MoonBase/Module.h" #include "driver/uart.h" @@ -582,6 +582,23 @@ class ModuleIO : public Module { pinAssigner.assignPin(8, pin_I2C_SDA); pinAssigner.assignPin(9, pin_I2C_SCL); #endif + // In setBoardPresetDefaults() for board_none (default case): + #ifdef CONFIG_IDF_TARGET_ESP32 + pinAssigner.assignPin(21, pin_I2C_SDA); // ESP32 classic + pinAssigner.assignPin(22, pin_I2C_SCL); + #elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) + pinAssigner.assignPin(8, pin_I2C_SDA); // ESP32-C3 + pinAssigner.assignPin(9, pin_I2C_SCL); + #elif defined(CONFIG_IDF_TARGET_ESP32C6) + pinAssigner.assignPin(23, pin_I2C_SDA); // ESP32-C6 + pinAssigner.assignPin(22, pin_I2C_SCL); + #elif defined(CONFIG_IDF_TARGET_ESP32P4) + pinAssigner.assignPin(7, pin_I2C_SDA); // ESP32-P4 (common board default) + pinAssigner.assignPin(8, pin_I2C_SCL); + #else + pinAssigner.assignPin(21, pin_I2C_SDA); // Fallback + pinAssigner.assignPin(22, pin_I2C_SCL); + #endif // trying to add more pins, but these pins not liked by esp32-d0-16MB ... 🚧 // pinAssigner.assignPin(4, pin_LED_02; @@ -721,6 +738,9 @@ class ModuleIO : public Module { #endif // ethernet #if FT_BATTERY + pinVoltage = UINT8_MAX; + pinCurrent = UINT8_MAX; + pinBattery = UINT8_MAX; for (JsonObject pinObject : _state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; if (usage == pin_Voltage) { @@ -811,6 +831,8 @@ class ModuleIO : public Module { #endif } + pinI2CSDA = UINT8_MAX; + pinI2CSCL = UINT8_MAX; for (JsonObject pinObject : _state.data["pins"].as()) { uint8_t usage = pinObject["usage"]; if (usage == pin_I2C_SDA) { @@ -825,9 +847,10 @@ class ModuleIO : public Module { if (pinI2CSCL != UINT8_MAX && pinI2CSDA != UINT8_MAX) { Wire.end(); // Clean up any previous I2C initialization + delay(100); uint16_t frequency = _state.data["i2cFreq"]; if (Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000)) { - EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d freq:%d", pinI2CSDA, pinI2CSCL, frequency); + EXT_LOGI(ML_TAG, "initI2C Wire sda:%d scl:%d freq:%d kHz", pinI2CSDA, pinI2CSCL, frequency); // delay(200); // Give I2C bus time to stabilize // Wire.setClock(50000); // Explicitly set to 100kHz updateDevices(); diff --git a/src/MoonLight/Nodes/Drivers/D_IMU.h b/src/MoonLight/Nodes/Drivers/D_IMU.h index df398253..e9d92f20 100644 --- a/src/MoonLight/Nodes/Drivers/D_IMU.h +++ b/src/MoonLight/Nodes/Drivers/D_IMU.h @@ -11,6 +11,7 @@ #if FT_MOONLIGHT + // #include #include class IMUDriver : public Node { @@ -26,9 +27,6 @@ class IMUDriver : public Node { uint8_t board = 0; void setup() override { - // controls will show in the UI - // for different type of controls see other Nodes - // addControl(pin, "pin", "slider", 1, SOC_GPIO_PIN_COUNT - 1); addControl(gyro, "gyro", "coord3D"); addControl(accell, "accell", "coord3D"); // isEnabled = false; // need to enable after fresh setup @@ -73,6 +71,16 @@ class IMUDriver : public Node { } } else EXT_LOGW(ML_TAG, "Testing device connections MPU6050 connection failed"); + } else if (board == 1) { // BMI160 - NEW + + // BMI160.begin(BMI160GenClass::I2C_MODE, 0x68); + + // if (BMI160.getDeviceID() == 0xD1) { // BMI160 device ID + // EXT_LOGI(ML_TAG, "BMI160 connection successful"); + // motionTrackingReady = true; + // } else { + // EXT_LOGW(ML_TAG, "BMI160 connection failed"); + // } } } } @@ -104,7 +112,7 @@ class IMUDriver : public Node { // needed to repeat the following 3 lines (yes if you look at the output: otherwise not 0) // mpu.dmpGetQuaternion(&q, fifoBuffer); - // mpu.dmpGetAccel(&aa, fifoBuffer); + mpu.dmpGetAccel(&aa, fifoBuffer); // mpu.dmpGetGravity(&gravity, &q); mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); @@ -114,6 +122,28 @@ class IMUDriver : public Node { accell.y = aaReal.y; accell.z = aaReal.z; } + } else if (board == 1) { // BMI160 - NEW + // int gx, gy, gz, ax, ay, az; + // BMI160.readGyro(gx, gy, gz); + // BMI160.readAccelerometer(ax, ay, az); + + // // Convert raw values to degrees (BMI160 gyro: 16.4 LSB/Β°/s at Β±2000Β°/s range) + // gyro.x = gx / 16.4f; + // gyro.y = gy / 16.4f; + // gyro.z = gz / 16.4f; + + // // Convert raw accel values (BMI160 accel: 16384 LSB/g at Β±2g range) + // accell.x = ax; + // accell.y = ay; + // accell.z = az; + + // // Calculate gravity vector from accelerometer + // float norm = sqrt(ax*ax + ay*ay + az*az); + // if (norm > 0) { + // sharedData.gravity.x = (ax / norm) * INT16_MAX; + // sharedData.gravity.y = (ay / norm) * INT16_MAX; + // sharedData.gravity.z = (az / norm) * INT16_MAX; + // } } };