-
-
Notifications
You must be signed in to change notification settings - Fork 15
I2C support and IMU driver #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
5e3d35b
66f1d6d
48e3647
77d11ec
c73ab86
62de264
001daad
0c8be14
bc015c2
13dd504
5d4ce24
6604c2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,8 @@ | |||||||
|
|
||||||||
| #if FT_MOONBASE == 1 | ||||||||
|
|
||||||||
| #include <Wire.h> | ||||||||
|
|
||||||||
| #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, false, "kHz"); | ||||||||
|
|
||||||||
| control = addControl(controls, "i2cBus", "rows"); | ||||||||
| control["crud"] = "r"; | ||||||||
| rows = control["n"].to<JsonArray>(); | ||||||||
| { | ||||||||
| addControl(rows, "address", "number", 0, 255, true); // ro | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| class PinAssigner { | ||||||||
|
|
@@ -564,6 +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; | ||||||||
|
|
@@ -609,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<uint16_t>() * 1000); | ||||||||
| } | ||||||||
|
|
||||||||
| if (newState.size()) update(newState, ModuleState::update, _moduleName); // if changes made then update | ||||||||
|
|
@@ -790,7 +810,34 @@ class ModuleIO : public Module { | |||||||
| } | ||||||||
| #endif | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| for (JsonObject pinObject : _state.data["pins"].as<JsonArray>()) { | ||||||||
| 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) { | ||||||||
| 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"); | ||||||||
| } | ||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||
| } // readPins | ||||||||
|
|
||||||||
| uint8_t pinI2CSDA = UINT8_MAX; | ||||||||
| uint8_t pinI2CSCL = UINT8_MAX; | ||||||||
|
|
||||||||
| #if FT_BATTERY | ||||||||
| uint8_t pinVoltage = UINT8_MAX; | ||||||||
|
|
@@ -867,6 +914,32 @@ class ModuleIO : public Module { | |||||||
| #endif | ||||||||
| } | ||||||||
|
|
||||||||
| void updateDevices() { | ||||||||
| JsonDocument doc; | ||||||||
| doc["i2cBus"].to<JsonArray>(); | ||||||||
| JsonObject newState = doc.as<JsonObject>(); | ||||||||
|
|
||||||||
| 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<JsonArray>().add<JsonObject>(); | ||||||||
| 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<JsonArray>().add<JsonObject>(); | ||||||||
| i2cDevice["address"] = 255; | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentinel entry with address 255 pollutes the device list. A fake device at address 255 is appended after the real scan results. This is not a valid I2C address (7-bit range is 0–127) and will appear as a spurious device in the UI. If this is meant to ensure the Proposed fix: remove the sentinel EXT_LOGI(ML_TAG, "Found %d device(s)", count);
- JsonObject i2cDevice = newState["i2cBus"].as<JsonArray>().add<JsonObject>();
- i2cDevice["address"] = 255;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| doc["i2cFreq"] = Wire.getClock() / 1000; | ||||||||
|
|
||||||||
| update(newState, ModuleState::update, _moduleName); | ||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||
| } | ||||||||
|
|
||||||||
| private: | ||||||||
| ESP32SvelteKit* _sveltekit; | ||||||||
| uint8_t current_board_id = UINT8_MAX; | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -264,6 +264,9 @@ class NodeManager : public Module { | |
| if (nodeClass != nullptr) { | ||
| nodeClass->on = updatedItem.value.as<bool>(); // 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent mutex handling and argument semantics for Two observations:
+ xSemaphoreTake(*nodeClass->layerMutex, portMAX_DELAY);
nodeClass->onUpdate(updatedItem.oldValue, nodeState); // custom onUpdate for the node
+ xSemaphoreGive(*nodeClass->layerMutex);
🤖 Prompt for AI Agents |
||
| xSemaphoreGive(*nodeClass->layerMutex); | ||
| nodeClass->requestMappings(); | ||
| } else | ||
| EXT_LOGW(ML_TAG, "Nodeclass %s not found", nodeState["name"].as<const char*>()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -344,6 +344,8 @@ static struct SharedData { | |
| size_t connectedClients; | ||
| size_t activeClients; | ||
| size_t clientListSize; | ||
|
|
||
| Coord3D gravity; | ||
|
Comment on lines
+347
to
+348
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As noted in the D_IMU.h review, the MPU6050 gravity vector contains float values in the range [–1.0, 1.0]. If 🤖 Prompt for AI Agents |
||
| } 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_IMU.h" | ||
| #include "MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h" | ||
| #include "MoonLight/Nodes/Drivers/D__Sandbox.h" | ||
| #include "MoonLight/Nodes/Effects/E_FastLED.h" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| /** | ||
| @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 <MPU6050_6Axis_MotionApps20.h> | ||
|
|
||
| class IMUDriver : public Node { | ||
| public: | ||
| static const char* name() { return "IMU driver"; } // Inertial Measurement Unit | ||
| static uint8_t dim() { return _NoD; } | ||
| static const char* tags() { return "☸️"; } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| bool motionTrackingReady = false; // set true if DMP init was successful | ||
|
|
||
| Coord3D gyro; // in degrees (not radians) | ||
| Coord3D accell; | ||
| 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 | ||
| addControl(board, "board", "select"); | ||
| addControlValue("MPU6050"); | ||
| addControlValue("BMI160"); // not supported yet | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| 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) { | ||
| bool i2cInited = true; // todo: check in moduleIO if successfull | ||
| if (i2cInited) { | ||
| 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_LOGW(ML_TAG, "DMP Initialization failed (code %d)", devStatus); | ||
| } | ||
| } else | ||
| EXT_LOGW(ML_TAG, "Testing device connections MPU6050 connection failed"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| 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 (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 * 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); | ||
|
|
||
| // 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; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }; | ||
|
|
||
| ~IMUDriver() override {}; | ||
|
|
||
| private: | ||
| MPU6050 mpu; | ||
|
|
||
| // MPU control/status vars | ||
| 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 | ||
Uh oh!
There was an error while loading. Please reload this page.