-
-
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 9 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> // for i2C | ||||||||
|
|
||||||||
| #include "MoonBase/Module.h" | ||||||||
| #include "driver/uart.h" | ||||||||
|
|
||||||||
|
|
@@ -216,6 +218,16 @@ class ModuleIO : public Module { | |||||||
| addControl(rows, "Level", "text", 0, 32, true); // ro | ||||||||
| addControl(rows, "DriveCap", "text", 0, 32, true); // ro | ||||||||
| } | ||||||||
|
|
||||||||
| control = addControl(controls, "i2cFreq", "number", 10, 1000, false, "kHz"); | ||||||||
| control["default"] = 100; // 100 kHz standard mode | ||||||||
|
|
||||||||
| control = addControl(controls, "i2cBus", "rows"); | ||||||||
| control["crud"] = "r"; | ||||||||
| rows = control["n"].to<JsonArray>(); | ||||||||
| { | ||||||||
| addControl(rows, "address", "number", 0, 255, true); // ro | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| class PinAssigner { | ||||||||
|
|
@@ -240,7 +252,7 @@ class ModuleIO : public Module { | |||||||
|
|
||||||||
| void setBoardPresetDefaults(uint8_t boardID) { | ||||||||
| JsonDocument doc; | ||||||||
| current_board_id = boardID; | ||||||||
| _current_board_id = boardID; | ||||||||
| JsonObject newState = doc.to<JsonObject>(); | ||||||||
| newState["modded"] = false; | ||||||||
|
|
||||||||
|
|
@@ -565,6 +577,23 @@ class ModuleIO : public Module { | |||||||
| pinAssigner.assignPin(16, pin_LED); | ||||||||
| #endif | ||||||||
|
|
||||||||
| #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; | ||||||||
| // pinAssigner.assignPin(5, pin_LED_03; | ||||||||
|
|
@@ -609,6 +638,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<uint32_t>() * 1000); // uint32_t instead of uint16_t to multiply in 32-bit arithmetic | ||||||||
| } | ||||||||
|
|
||||||||
| if (newState.size()) update(newState, ModuleState::update, _moduleName); // if changes made then update | ||||||||
|
|
@@ -701,17 +732,20 @@ 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<JsonArray>()) { | ||||||||
| uint8_t usage = pinObject["usage"]; | ||||||||
| if (usage == pin_Voltage) { | ||||||||
| pinVoltage = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinVoltage found %d", pinVoltage); | ||||||||
| _pinVoltage = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinVoltage found %d", _pinVoltage); | ||||||||
| } else if (usage == pin_Current) { | ||||||||
| pinCurrent = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinCurrent found %d", pinCurrent); | ||||||||
| _pinCurrent = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinCurrent found %d", _pinCurrent); | ||||||||
| } else if (usage == pin_Battery) { | ||||||||
| pinBattery = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinBattery found %d", pinBattery); | ||||||||
| _pinBattery = pinObject["GPIO"]; | ||||||||
| EXT_LOGD(ML_TAG, "pinBattery found %d", _pinBattery); | ||||||||
| } | ||||||||
| } | ||||||||
| #endif | ||||||||
|
|
@@ -790,12 +824,40 @@ class ModuleIO : public Module { | |||||||
| } | ||||||||
| #endif | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| uint8_t pinI2CSDA = UINT8_MAX; | ||||||||
| uint8_t pinI2CSCL = UINT8_MAX; | ||||||||
|
|
||||||||
| 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 | ||||||||
| delay(100); | ||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||
| uint32_t frequency = _state.data["i2cFreq"]; | ||||||||
| if (Wire.begin(pinI2CSDA, pinI2CSCL, frequency * 1000)) { | ||||||||
| 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 | ||||||||
| _state.data["I2CReady"] = true; | ||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||
| updateDevices(); | ||||||||
| } else { | ||||||||
| _state.data["I2CReady"] = false; | ||||||||
| EXT_LOGE(ML_TAG, "initI2C Wire failed"); | ||||||||
| } | ||||||||
| } | ||||||||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||
| } // readPins | ||||||||
|
|
||||||||
| #if FT_BATTERY | ||||||||
| uint8_t pinVoltage = UINT8_MAX; | ||||||||
| uint8_t pinCurrent = UINT8_MAX; | ||||||||
| uint8_t pinBattery = UINT8_MAX; | ||||||||
|
|
||||||||
| adc_attenuation_t adc_get_adjusted_gain(adc_attenuation_t current_gain, uint32_t adc_mv_readout) { | ||||||||
| if (current_gain == ADC_11db && adc_mv_readout < 1700) { | ||||||||
|
|
@@ -825,38 +887,38 @@ class ModuleIO : public Module { | |||||||
| void loop1s() { | ||||||||
| #if FT_BATTERY | ||||||||
| BatteryService* batteryService = _sveltekit->getBatteryService(); | ||||||||
| if (pinBattery != UINT8_MAX) { | ||||||||
| float mVB = analogReadMilliVolts(pinBattery) * 2.0; | ||||||||
| if (_pinBattery != UINT8_MAX) { | ||||||||
| float mVB = analogReadMilliVolts(_pinBattery) * 2.0; | ||||||||
| float perc = (mVB - BATTERY_MV * 0.65) / (BATTERY_MV * 0.35); // 65% of full battery is 0%, showing 0-100% | ||||||||
| // ESP_LOGD("", "bat mVB %f p:%f", mVB, perc); | ||||||||
| batteryService->updateSOC(perc * 100); | ||||||||
| } | ||||||||
| if (pinVoltage != UINT8_MAX) { | ||||||||
| if (_pinVoltage != UINT8_MAX) { | ||||||||
| analogSetAttenuation(voltage_readout_current_adc_attenuation); | ||||||||
| uint32_t adc_mv_vinput = analogReadMilliVolts(pinVoltage); | ||||||||
| uint32_t adc_mv_vinput = analogReadMilliVolts(_pinVoltage); | ||||||||
| analogSetAttenuation(ADC_11db); | ||||||||
| float volts = 0; | ||||||||
| if (current_board_id == board_SE16V1) { | ||||||||
| if (_current_board_id == board_SE16V1) { | ||||||||
| volts = ((float)adc_mv_vinput) * 2 / 1000; | ||||||||
| } // /2 resistor divider | ||||||||
| else if (current_board_id == board_LightCrafter16) { | ||||||||
| else if (_current_board_id == board_LightCrafter16) { | ||||||||
| volts = ((float)adc_mv_vinput) * 11.43 / (1.43 * 1000); | ||||||||
| } // 1k43/10k resistor divider | ||||||||
| batteryService->updateVoltage(volts); | ||||||||
| voltage_readout_current_adc_attenuation = adc_get_adjusted_gain(voltage_readout_current_adc_attenuation, adc_mv_vinput); | ||||||||
| } | ||||||||
| if (pinCurrent != UINT8_MAX) { | ||||||||
| if (_pinCurrent != UINT8_MAX) { | ||||||||
| analogSetAttenuation(current_readout_current_adc_attenuation); | ||||||||
| uint32_t adc_mv_cinput = analogReadMilliVolts(pinCurrent); | ||||||||
| uint32_t adc_mv_cinput = analogReadMilliVolts(_pinCurrent); | ||||||||
| analogSetAttenuation(ADC_11db); | ||||||||
| current_readout_current_adc_attenuation = adc_get_adjusted_gain(current_readout_current_adc_attenuation, adc_mv_cinput); | ||||||||
| if ((current_board_id == board_SE16V1) || (current_board_id == board_LightCrafter16)) { | ||||||||
| if ((_current_board_id == board_SE16V1) || (_current_board_id == board_LightCrafter16)) { | ||||||||
| if (adc_mv_cinput > 330) // datasheet quiescent output voltage of 0.5V, which is ~330mV after the 10k/5k1 voltage divider. Ideally, this value should be measured at boot when nothing is displayed on the LEDs | ||||||||
| { | ||||||||
| if (current_board_id == board_SE16V1) { | ||||||||
| if (_current_board_id == board_SE16V1) { | ||||||||
| batteryService->updateCurrent((((float)(adc_mv_cinput)-250) * 50.00) / 1000); | ||||||||
| } // 40mV / A with a /2 resistor divider, so a 50mA/mV | ||||||||
| else if (current_board_id == board_LightCrafter16) { | ||||||||
| else if (_current_board_id == board_LightCrafter16) { | ||||||||
| batteryService->updateCurrent((((float)(adc_mv_cinput)-330) * 37.75) / 1000); | ||||||||
| } // 40mV / A with a 10k/5k1 resistor divider, so a 37.75mA/mV | ||||||||
| } else { | ||||||||
|
|
@@ -867,9 +929,41 @@ 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; | ||||||||
| uint8_t _current_board_id = UINT8_MAX; | ||||||||
| #if FT_BATTERY | ||||||||
| // used in loop1s() | ||||||||
| uint8_t _pinVoltage = UINT8_MAX; | ||||||||
| uint8_t _pinCurrent = UINT8_MAX; | ||||||||
| uint8_t _pinBattery = UINT8_MAX; | ||||||||
| #endif | ||||||||
| }; | ||||||||
|
|
||||||||
| #endif | ||||||||
|
|
||||||||
| 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" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.