From 9414f531dd80bdf57c77cac400061ba821fdcde1 Mon Sep 17 00:00:00 2001 From: Ewowi Date: Tue, 27 Sep 2022 15:19:58 +0200 Subject: [PATCH] IMO support in usermod games, add 3D IMO cube effect - platformio: add USERMOD_MPU6050_IMU and ElectronicCats/MPU6050 @ 0.6.0 to esp32mdevums - USERMOD_MPU6050_IMU: update readme.md and usermod_mpu650_imu.h - usermod_v2_games: support for USERMOD_MPU6050_IMU, add mode_IMUTest and class Frame3D and mode_3DIMUCube, remove old gyro handling - usermods_list.cpp: add USERMOD_MPU6050_IMU --- platformio.ini | 105 +++++++- usermods/mpu6050_imu/readme.md | 19 +- usermods/mpu6050_imu/usermod_mpu6050_imu.h | 238 +++++++++---------- usermods/usermod_v2_games/usermod_v2_games.h | 232 +++++++++++------- wled00/usermods_list.cpp | 8 + 5 files changed, 387 insertions(+), 215 deletions(-) diff --git a/platformio.ini b/platformio.ini index 9df10889..23a9d56e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ ; default_envs = travis_esp8266, travis_esp32 # Release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3 +; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3 # Build everything ; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips @@ -32,6 +32,9 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s ; default_envs = esp8285_4CH_MagicHome ; default_envs = esp8285_H801 ; default_envs = d1_mini_5CH_Shojo_PCB +; default_envs = esp32mdev +default_envs = esp32mdevums +; default_envs = esp8266mdev ; default_envs = wemos_shield_esp32 ; default_envs = m5atom ; default_envs = esp32_eth @@ -222,7 +225,9 @@ default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = ${env.lib_deps} - https://github.com/lorol/LITTLEFS.git + ; https://github.com/lorol/LITTLEFS.git + ; WLEDSR specific: use patched version of lorol LittleFS + https://github.com/softhack007/LITTLEFS-threadsafe.git#master makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 @@ -437,6 +442,82 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} +# ------------------------------------------------------------------------------ +# MoonModules configs +# ------------------------------------------------------------------------------ + +[env:esp32mdev] +board = esp32dev +platform = ${esp32.platform} +upload_speed = 460800 ; or 921600 +platform_packages = ${esp32.platform_packages} +build_unflags = ${common.build_unflags} + +build_flags = ${common.build_flags_esp32} + -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET + -D ABL_MILLIAMPS_DEFAULT=1500 ; 850 not enough for 1024 leds + -D UWLED_USE_MY_CONFIG + -D USERMOD_AUDIOREACTIVE + -D UM_AUDIOREACTIVE_USE_NEW_FFT + -D USERMOD_CUSTOMEFFECTS + ; -D WLED_DEBUG +lib_deps = ${esp32.lib_deps} + https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +; monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio + +[env:esp32mdevums] +board = esp32dev +platform = ${esp32.platform} +upload_speed = 460800 ; or 921600 +platform_packages = ${esp32.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} + -D WLED_RELEASE_NAME=ESP32 ; what does this do? + #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET ; which settings and when? + -D ABL_MILLIAMPS_DEFAULT=1500 ; 850 not enough for 1024 leds + -D WLED_MAX_USERMODS=9 ; default only 4-6 + -D UWLED_USE_MY_CONFIG ; what does this do? + -D USERMOD_AUDIOREACTIVE + -D UM_AUDIOREACTIVE_USE_NEW_FFT ; explain + ; -D USERMOD_DALLASTEMPERATURE + ; -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI + ; -D USERMOD_FOUR_LINE_DISPLAY + ; -D USERMOD_ROTARY_ENCODER_UI + -D USERMOD_AUTO_SAVE + -D USERMOD_CUSTOMEFFECTS ; WLEDSR usermod + -D USERMOD_WEATHER ; WLEDSR usermod + -D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG) + -D USERMOD_GAMES ; WLEDSR usermod + ; -D WLED_DEBUG +lib_deps = ${esp32.lib_deps} + ; OneWire@~2.3.5 ; used for USERMOD_FOUR_LINE_DISPLAY, USERMOD_DALLASTEMPERATURE + ; olikraus/U8g2 @ ^2.28.8 ; used for USERMOD_FOUR_LINE_DISPLAY + https://github.com/kosme/arduinoFFT#develop @ 1.9.2 ; used for USERMOD_AUDIOREACTIVE + ElectronicCats/MPU6050 @ 0.6.0 ; used for USERMOD_MPU6050_IMU +monitor_filters = esp32_exception_decoder ; used to show crash details +board_build.partitions = ${esp32.default_partitions} +; board_build.partitions = tools/WLED_ESP32_16MB.csv +; board_build.partitions = tools/WLED_ESP32-wrover_4MB.csv +board_build.f_flash = 80000000L ; explain +board_build.flash_mode = dio ; explain (dio vs qio?) + +[env:esp8266mdev] +extends = env:d1_mini +upload_speed = 460800 ;115200 +build_flags = ${common.build_flags_esp8266} + -D WLED_DEBUG + -D WLED_DISABLE_ALEXA + -D WLED_DISABLE_BLYNK + -D WLED_DISABLE_HUESYNC + ; -D WLED_DISABLE_2D + ; -D WLED_DISABLE_AUDIO + ; -D USERMOD_AUDIOREACTIVE + -UWLED_USE_MY_CONFIG +; monitor_filters = esp8266_exception_decoder + # ------------------------------------------------------------------------------ # custom board configurations # ------------------------------------------------------------------------------ @@ -444,21 +525,33 @@ lib_deps = ${esp8266.lib_deps} [env:wemos_shield_esp32] board = esp32dev platform = espressif32@3.2 -upload_speed = 460800 +upload_speed = 460800 ; or 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} + -D ABL_MILLIAMPS_DEFAULT=1500 ; 850 not enough for 1024 leds + -D WLED_MAX_USERMODS=8 -D LEDPIN=16 -D RLYPIN=19 -D BTNPIN=17 -D IRPIN=18 -D UWLED_USE_MY_CONFIG - -D USERMOD_DALLASTEMPERATURE - -D USERMOD_FOUR_LINE_DISPLAY - -D TEMPERATURE_PIN=23 + -D USERMOD_CUSTOMEFFECTS + -D USERMOD_AUDIOREACTIVE -D AUDIOPIN=-1 -D DMTYPE=1 -D I2S_SDPIN=32 -D I2S_WSPIN=15 -D I2S_CKPIN=14 + -D UM_AUDIOREACTIVE_USE_NEW_FFT + -D USERMOD_DALLASTEMPERATURE -D TEMPERATURE_PIN=23 + -D USE_ALT_DISPlAY + -D USERMOD_FOUR_LINE_DISPLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21 + -D USERMOD_ROTARY_ENCODER_UI ; -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 + -D USERMOD_AUTO_SAVE + -D USERMOD_WEATHER + -D USERMOD_GAMES + ; -D WLED_DEBUG lib_deps = ${esp32.lib_deps} OneWire@~2.3.5 olikraus/U8g2 @ ^2.28.8 + https://github.com/kosme/arduinoFFT#develop @ 1.9.2 board_build.partitions = ${esp32.default_partitions} +; board_build.partitions = tools/WLED_ESP32_16MB.csv [env:m5atom] board = esp32dev diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md index adb19ef8..d8df0adb 100644 --- a/usermods/mpu6050_imu/readme.md +++ b/usermods/mpu6050_imu/readme.md @@ -20,9 +20,9 @@ react to the globes orientation. See the blog post on building it For example: @@ -36,7 +36,7 @@ lib_deps = AsyncTCP@1.0.3 Esp Async WebServer@1.2.0 IRremoteESP8266@2.7.3 - I2Cdevlib-MPU6050@fbde122cc5 + ElectronicCats/MPU6050 @ 0.6.0 ``` ## Wiring @@ -77,18 +77,19 @@ to the info object ## Usermod installation -1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory. -2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`. + +2. Register the usermod by adding `#include "usermod_mpu6050_imu.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`. Example **usermods_list.cpp**: ```cpp #include "wled.h" -#include "usermod_mpu6050_imu.h" +#ifdef USERMOD_MPU6050_IMU +#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h" +#endif -void registerUsermods() -{ +#ifdef USERMOD_MPU6050_IMU usermods.add(new MPU6050Driver()); -} +#endif ``` diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index 4ce51c43..748824ee 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -2,6 +2,17 @@ #include "wled.h" +#ifdef WLED_DEBUG + #define DEBUG_PRINT_IMU(x) Serial.print(x) + #define DEBUG_PRINT_IMULN(x) Serial.println(x) + #define DEBUG_PRINT_IMUF(x...) Serial.printf(x) +#else + #define DEBUG_PRINT_IMU(x) + #define DEBUG_PRINT_IMULN(x) + #define DEBUG_PRINT_IMUF(x...) +#endif + + /* This driver reads quaternion data from the MPU6060 and adds it to the JSON This example is adapted from: https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050/examples/MPU6050_DMP6_ESPWiFi @@ -26,15 +37,14 @@ 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp 3. I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h file for both classes must be in the include path of your project. To install the - libraries add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file. - 4. You also need to change lib_compat_mode from strict to soft in platformio.ini (This ignores that I2Cdevlib-MPU6050 doesn't list platform compatibility) + libraries add ElectronicCats/MPU6050 @ 0.6.0 to lib_deps in the platformio.ini file. + // 4. You also need to change lib_compat_mode from strict to soft in platformio.ini (This ignores that I2Cdevlib-MPU6050 doesn't list platform compatibility) 5. Wire up the MPU6050 as detailed above. */ #include "I2Cdev.h" #include "MPU6050_6Axis_MotionApps20.h" -//#include "MPU6050.h" // not necessary if using MotionApps include file // Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation // is used in I2Cdev.h @@ -56,6 +66,7 @@ class MPU6050Driver : public Usermod { private: MPU6050 mpu; bool enabled = true; + unsigned long lastUMRun = millis(); // MPU control/status vars bool dmpReady = false; // set true if DMP init was successful @@ -65,47 +76,61 @@ class MPU6050Driver : public Usermod { uint16_t fifoCount; // count of all bytes currently in FIFO uint8_t fifoBuffer[64]; // FIFO storage buffer - //NOTE: some of these can be removed to save memory, processing time - // if the measurement isn't needed + public: + // orientation/motion vars Quaternion qat; // [w, x, y, z] quaternion container - float euler[3]; // [psi, theta, phi] Euler angle container - float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container VectorInt16 aa; // [x, y, z] accel sensor measurements VectorInt16 gy; // [x, y, z] gyro 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 static const int INTERRUPT_PIN = 15; // use pin 15 on ESP8266 - public: - //Functions called by WLED - - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - */ void setup() { + DEBUG_PRINT_IMULN("mpu setup"); PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } + // join I2C bus (I2Cdev library doesn't do this automatically) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE - Wire.begin(); - Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties + Wire.begin(); + Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE - Fastwire::setup(400, true); + Fastwire::setup(400, true); #endif + // // initialize serial communication + // // (115200 chosen because it is required for Teapot Demo output, but it's + // // really up to you depending on your project) + // Serial.begin(115200); + // while (!Serial); // wait for Leonardo enumeration, others continue immediately + + // NOTE: 8MHz or slower host processors, like the Teensy @ 3.3V or Arduino + // Pro Mini running at 3.3V, cannot handle this baud rate reliably due to + // the baud timing being too misaligned with processor ticks. You must use + // 38400 or slower in these cases, or use some kind of external separate + // crystal solution for the UART timer. + // initialize device - DEBUG_PRINTLN(F("Initializing I2C devices...")); + DEBUG_PRINT_IMULN(F("Initializing I2C devices...")); mpu.initialize(); pinMode(INTERRUPT_PIN, INPUT); // verify connection - DEBUG_PRINTLN(F("Testing device connections...")); - DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); + DEBUG_PRINT_IMULN(F("Testing device connections...")); + DEBUG_PRINT_IMULN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); + + // // wait for ready + // DEBUG_PRINT_IMULN(F("\nSend any character to begin DMP programming and demo: ")); + // while (Serial.available() && Serial.read()); // empty buffer + // while (!Serial.available()); // wait for data + // while (Serial.available() && Serial.read()); // empty buffer again // load and configure the DMP - DEBUG_PRINTLN(F("Initializing DMP...")); + DEBUG_PRINT_IMULN(F("Initializing DMP...")); devStatus = mpu.dmpInitialize(); // supply your own gyro offsets here, scaled for min sensitivity @@ -116,77 +141,52 @@ class MPU6050Driver : public Usermod { // make sure it worked (returns 0 if so) if (devStatus == 0) { - // turn on the DMP, now that it's ready - DEBUG_PRINTLN(F("Enabling DMP...")); - mpu.setDMPEnabled(true); + // Calibration Time: generate offsets and calibrate our MPU6050 + mpu.CalibrateAccel(6); + mpu.CalibrateGyro(6); + #ifdef WLED_DEBUG + mpu.PrintActiveOffsets(); + #endif + // turn on the DMP, now that it's ready + DEBUG_PRINT_IMULN(F("Enabling DMP...")); + mpu.setDMPEnabled(true); - // enable Arduino interrupt detection - DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); - attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); - mpuIntStatus = mpu.getIntStatus(); + // enable Arduino interrupt detection + DEBUG_PRINT_IMU(F("Enabling interrupt detection (Arduino external interrupt ")); + DEBUG_PRINT_IMU(digitalPinToInterrupt(INTERRUPT_PIN)); + DEBUG_PRINT_IMULN(F(")...")); + attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); + mpuIntStatus = mpu.getIntStatus(); - // set our DMP Ready flag so the main loop() function knows it's okay to use it - DEBUG_PRINTLN(F("DMP ready! Waiting for first interrupt...")); - dmpReady = true; + // set our DMP Ready flag so the main loop() function knows it's okay to use it + DEBUG_PRINT_IMULN(F("DMP ready! Waiting for first interrupt...")); + dmpReady = true; - // get expected DMP packet size for later comparison - packetSize = mpu.dmpGetFIFOPacketSize(); + // get expected DMP packet size for later comparison + packetSize = mpu.dmpGetFIFOPacketSize(); } else { - // ERROR! - // 1 = initial memory load failed - // 2 = DMP configuration updates failed - // (if it's going to break, usually the code will be 1) - DEBUG_PRINT(F("DMP Initialization failed (code ")); - DEBUG_PRINT(devStatus); - DEBUG_PRINTLN(F(")")); + // ERROR! + // 1 = initial memory load failed + // 2 = DMP configuration updates failed + // (if it's going to break, usually the code will be 1) + DEBUG_PRINT_IMU(F("DMP Initialization failed (code ")); + DEBUG_PRINT_IMU(devStatus); + DEBUG_PRINT_IMULN(F(")")); } } - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ void connected() { - //DEBUG_PRINTLN("Connected to WiFi!"); } - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - */ void loop() { // if programming failed, don't try to do anything - if (!enabled || !dmpReady || strip.isUpdating()) return; - - // wait for MPU interrupt or extra packet(s) available - if (!mpuInterrupt && fifoCount < packetSize) return; - - // reset interrupt flag and get INT_STATUS byte - mpuInterrupt = false; - mpuIntStatus = mpu.getIntStatus(); - - // get current FIFO count - fifoCount = mpu.getFIFOCount(); - - // check for overflow (this should never happen unless our code is too inefficient) - if ((mpuIntStatus & 0x10) || fifoCount == 1024) { - // reset so we can continue cleanly - mpu.resetFIFO(); - DEBUG_PRINTLN(F("FIFO overflow!")); - - // otherwise, check for DMP data ready interrupt (this should happen frequently) - } else if (mpuIntStatus & 0x02) { - // wait for correct available data length, should be a VERY short wait - while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount(); - - // read a packet from FIFO - mpu.getFIFOBytes(fifoBuffer, packetSize); - - // track FIFO count here in case there is > 1 packet available - // (this lets us immediately read more without waiting for an interrupt) - fifoCount -= packetSize; - + if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice + lastUMRun = millis(); // update time keeping + if (!dmpReady) return; + // read a packet from FIFO + if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet //NOTE: some of these can be removed to save memory, processing time // if the measurement isn't needed mpu.dmpGetQuaternion(&qat, fifoBuffer); @@ -200,25 +200,24 @@ class MPU6050Driver : public Usermod { } } - - void addToJsonInfo(JsonObject& root) { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray imu_meas = user.createNestedObject("IMU"); + StaticJsonDocument<600> doc; //measured 528 + + JsonObject imu_meas = doc.createNestedObject("IMU"); + #ifdef WLED_DEBUG JsonArray quat_json = imu_meas.createNestedArray("Quat"); quat_json.add(qat.w); quat_json.add(qat.x); quat_json.add(qat.y); quat_json.add(qat.z); JsonArray euler_json = imu_meas.createNestedArray("Euler"); - euler_json.add(euler[0]); - euler_json.add(euler[1]); - euler_json.add(euler[2]); + euler_json.add(euler[0] * 180/M_PI); + euler_json.add(euler[1] * 180/M_PI); + euler_json.add(euler[2] * 180/M_PI); JsonArray accel_json = imu_meas.createNestedArray("Accel"); accel_json.add(aa.x); accel_json.add(aa.y); @@ -227,10 +226,6 @@ class MPU6050Driver : public Usermod { gyro_json.add(gy.x); gyro_json.add(gy.y); gyro_json.add(gy.z); - JsonArray world_json = imu_meas.createNestedArray("WorldAccel"); - world_json.add(aaWorld.x); - world_json.add(aaWorld.y); - world_json.add(aaWorld.z); JsonArray real_json = imu_meas.createNestedArray("RealAccel"); real_json.add(aaReal.x); real_json.add(aaReal.y); @@ -239,49 +234,54 @@ class MPU6050Driver : public Usermod { grav_json.add(gravity.x); grav_json.add(gravity.y); grav_json.add(gravity.z); - JsonArray orient_json = imu_meas.createNestedArray("Orientation"); - orient_json.add(ypr[0]); - orient_json.add(ypr[1]); - orient_json.add(ypr[2]); + #endif + JsonArray world_json = imu_meas.createNestedArray("WorldAccel"); + world_json.add(aaWorld.x); + world_json.add(aaWorld.y); + world_json.add(aaWorld.z); + JsonArray orient_json = imu_meas.createNestedArray("YPR"); + orient_json.add(ypr[0] * 180/M_PI); + orient_json.add(ypr[1] * 180/M_PI); + orient_json.add(ypr[2] * 180/M_PI); + + char stringBuffer[300]; // measured 266 + serializeJson(imu_meas, stringBuffer); + JsonArray mainObject = user.createNestedArray("IMU"); + mainObject.add(stringBuffer); + + // Serial.printf("imu_meas %u (%u %u) stringBuffer %u\n", (unsigned int)imu_meas.memoryUsage(), (unsigned int)imu_meas.size(), (unsigned int)imu_meas.nesting(), strlen(stringBuffer)); + } - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ //void addToJsonState(JsonObject& root) //{ - //root["user0"] = userVar0; //} - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ //void readFromJsonState(JsonObject& root) //{ - //if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!")); //} + // void addToConfig(JsonObject& root) + // { + // JsonObject top = root.createNestedObject("MPU6050"); + // top[FPSTR("enabled")] = enabled; - /* - * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ -// void addToConfig(JsonObject& root) -// { -// JsonObject top = root.createNestedObject("MPU6050_IMU"); -// JsonArray pins = top.createNestedArray("pin"); -// pins.add(HW_PIN_SCL); -// pins.add(HW_PIN_SDA); -// } + // JsonObject interruptPin = top.createNestedObject(FPSTR("interruptPin")); + // interruptPin["pin"] = interruptPin; + // } + + // bool readFromConfig(JsonObject& root) + // { + // JsonObject top = root[FPSTR("MPU6050")]; + // bool configComplete = !top.isNull(); + + // configComplete &= getJsonValue(top[FPSTR("enabled")], enabled); + // configComplete &= getJsonValue(top[FPSTR("interruptPin")]["pin"], interruptPin); + + // return configComplete; + // } - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - */ uint16_t getId() { return USERMOD_ID_IMU; diff --git a/usermods/usermod_v2_games/usermod_v2_games.h b/usermods/usermod_v2_games/usermod_v2_games.h index 1132c046..832a4f96 100644 --- a/usermods/usermod_v2_games/usermod_v2_games.h +++ b/usermods/usermod_v2_games/usermod_v2_games.h @@ -1,25 +1,19 @@ +/* + Games usermod by ewowi, september 2022 + + Contains: + - mode_pongGame + - Depending on USERMOD_MPU6050_IMU + - mode_IMUTest (shows IMU values only if WLED_DEBUG) + - class Frame3D and struct Voxel + - mode_3DIMUCube (uses class Frame3D to show a rotating cube, if USERMOD_MPU6050_IMU then IMU used for rotation) + - class GamesUsermod (Add the modes/effects and initiates IMU) +*/ + #pragma once #include "wled.h" -/* - * Usermods allow you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * - * This is an example for a v2 usermod. - * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. - * Multiple v2 usermods can be added to one compilation easily. - * - * Creating a usermod: - * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. - * Please remember to rename the class and file to a descriptive name. - * You may also use multiple .h and .cpp files. - * - * Using a usermod: - * 1. Copy the usermod into the sketch folder (same folder as wled00.ino) - * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp - */ - //inspired by https://noobtuts.com/cpp/2d-pong-game typedef struct PongBall { float x;// = SEGMENT.virtualWidth() / 2; @@ -181,94 +175,170 @@ uint16_t mode_pongGame(void) { static const char _data_FX_MODE_PONGGAME[] PROGMEM = "🎮 Pong@!;!;!;2d"; //https://howtomechatronics.com/tutorials/arduino/arduino-and-mpu6050-accelerometer-and-gyroscope-tutorial/ -#define MPU_ADDR 0x68 // I2C address of the MPU-6050. If AD0 pin is set to HIGH, the I2C address will be 0x69. -int16_t accelerometer_x, accelerometer_y, accelerometer_z; // variables for accelerometer raw data -int16_t gyro_x, gyro_y, gyro_z; // variables for gyro raw data -int16_t temperature; // variables for temperature data -uint16_t mode_gyro(void) { +#ifdef USERMOD_MPU6050_IMU +MPU6050Driver *IMU = nullptr; +uint16_t mode_IMUTest(void) { SEGMENT.fill(BLACK); uint8_t y = 0; - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_y+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_z+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_y+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); - SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_z+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); + if (IMU != nullptr) { + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.x+1024)/(2*1024), y+=1, RED); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.y+1024)/(2*1024), y+=1, RED); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.z+1024)/(2*1024), y+=1, RED); + + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE); + + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[0]* 180/M_PI+180)/(2*180), y+=1, RED); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[1]* 180/M_PI+180)/(2*180), y+=1, RED); + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[2]* 180/M_PI+180)/(2*180), y+=1, RED); + } return FRAMETIME; } -static const char _data_FX_MODE_GYRO[] PROGMEM = "🎮 Gyro@!;!;!;2d"; +static const char _data_FX_MODE_IMUTest[] PROGMEM = "🎮 IMUTest@;;;2d"; -#ifndef FLD_PIN_SCL - #define FLD_PIN_SCL i2c_scl -#endif -#ifndef FLD_PIN_SDA - #define FLD_PIN_SDA i2c_sda #endif +//WLEDSR 3D to 2D mapping +struct Voxel { + float x; + float y; + float z; + uint32_t col; +}; + +class Frame3D { + private: + std::vector points; + void rotate(float a, float b, float angle, float &x, float &y) { + x = cosf(angle) * a - sinf(angle) * b; + y = sinf(angle) * a + cosf(angle) * b; + } + float yaw; + float pitch; + float roll; + public: + Frame3D(float yaw1, float pitch1, float roll1) { + points.clear(); + yaw = yaw1; + pitch = pitch1; + roll = roll1; + } + ~Frame3D() { + std::sort(points.begin(), points.end(), [](const Voxel &lhs, const Voxel &rhs) {return lhs.z > rhs.z;}); + for(size_t i = 0; i < points.size(); ++i) { + float w = 0.5;//SEGMENT.virtualWidth()/2; + float h = 0.5;//SEGMENT.virtualHeight()/2; + float perspective = SEGMENT.intensity / 64.0; + if(points[i].z > 0) { + float projX, projY; + projX = w+points[i].x/points[i].z*perspective; + projY = h+points[i].y/points[i].z*perspective; + SEGMENT.setPixelColorXY(projX, projY, points[i].col, false); //no aa + } + } + } + void setPixelColorXYZ(Voxel voxel) { + float camx = 0; + float camy = 0; + float camz = -6; + rotate(voxel.x,voxel.z,yaw, voxel.x, voxel.z); // Camera yaw + rotate(voxel.y,voxel.z,pitch, voxel.y, voxel.z); // Camera pitch + rotate(voxel.x,voxel.y,roll, voxel.x, voxel.y); // Camera roll + voxel.x -= camx; + voxel.y -= camy; + voxel.z -= camz; + points.push_back(voxel); + } + void drawLineXYZ(Voxel from, Voxel to, uint32_t col) { + for (float x=MIN(from.x, to.x); x<=MAX(from.x, to.x); x+=.05) + for (float y=MIN(from.y, to.y); y<=MAX(from.y, to.y); y+=.05) + for (float z=MIN(from.z, to.z); z<=MAX(from.z, to.z); z+=.05) + setPixelColorXYZ({x, y, z, col}); + } +}; + +uint16_t mode_3DIMUCube(void) { + SEGMENT.fill(BLACK); + + float yaw = 0; + float pitch = 0; + float roll = 0; + + #ifdef USERMOD_MPU6050_IMU + if (IMU != nullptr) { + yaw = -IMU->ypr[0]; + pitch = IMU->ypr[1]; + roll = IMU->ypr[2]; + } + #else + //simulate rotation + yaw = (fmod(SEGENV.call, 360)-180) / (180/M_PI); //-180 .. 180 + pitch = yaw; + roll = yaw; + #endif + + Frame3D frame3D = Frame3D(yaw, pitch, roll); + + Voxel leftbottomback = {-1,-1,-1}; + Voxel rightbottomback = {1,-1,-1}; + Voxel lefttopback = {-1,1,-1}; + Voxel righttopback = {1,1,-1}; + Voxel leftbottomfront = {-1,-1,1}; + Voxel rightbottomfront = {1,-1,1}; + Voxel lefttopfront = {-1,1,1}; + Voxel righttopfront = {1,1,1}; + frame3D.drawLineXYZ(leftbottomback, rightbottomback, SEGMENT.color_from_palette(255/12, false, true, 0)); + frame3D.drawLineXYZ(leftbottomback, lefttopback, SEGMENT.color_from_palette(255/12*2, false, true, 0)); + frame3D.drawLineXYZ(rightbottomback, righttopback, SEGMENT.color_from_palette(255/12*3, false, true, 0)); + frame3D.drawLineXYZ(lefttopback, righttopback, SEGMENT.color_from_palette(255/12*4, false, true, 0)); + + frame3D.drawLineXYZ(leftbottomfront, leftbottomback, SEGMENT.color_from_palette(255/12*9, false, true, 0)); + frame3D.drawLineXYZ(rightbottomfront, rightbottomback, SEGMENT.color_from_palette(255/12*10, false, true, 0)); + frame3D.drawLineXYZ(lefttopfront, lefttopback, SEGMENT.color_from_palette(255/12*11, false, true, 0)); + frame3D.drawLineXYZ(righttopfront, righttopback, SEGMENT.color_from_palette(255/12*12, false, true, 0)); + + frame3D.drawLineXYZ(leftbottomfront, rightbottomfront, SEGMENT.color_from_palette(255/12*5, false, true, 0)); + frame3D.drawLineXYZ(leftbottomfront, lefttopfront, SEGMENT.color_from_palette(255/12*6, false, true, 0)); + frame3D.drawLineXYZ(rightbottomfront, righttopfront, SEGMENT.color_from_palette(255/12*7, false, true, 0)); + frame3D.drawLineXYZ(lefttopfront, righttopfront, SEGMENT.color_from_palette(255/12*8, false, true, 0)); + + return FRAMETIME; +} +static const char _data_FX_MODE_3DIMUCube[] PROGMEM = "🎮 3DIMUCube@,Perspective;!;!;,pal=1,2d"; //random cycle class GamesUsermod : public Usermod { private: - bool enabled = true; - int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA - unsigned long lastUMRun = millis(); public: - //Functions called by WLED void setup() { - bool isHW; - PinOwner po = PinOwner::UM_Unspecified; - uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - if (ioPin[0] < 0 || ioPin[1] < 0) { - ioPin[0] = hw_scl; - ioPin[1] = hw_sda; - } - isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda); - if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { enabled = false; return; } - // PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; - // if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } - Wire.begin(); - Wire.beginTransmission(MPU_ADDR); // Begins a transmission to the I2C slave (GY-521 board) - Wire.write(0x6B); // PWR_MGMT_1 register - Wire.write(0); // set to zero (wakes up the MPU-6050) - Wire.endTransmission(true); - strip.addEffect(255, &mode_pongGame, _data_FX_MODE_PONGGAME); - strip.addEffect(255, &mode_gyro, _data_FX_MODE_GYRO); + #ifdef USERMOD_MPU6050_IMU + IMU = (MPU6050Driver *)usermods.lookup(USERMOD_ID_IMU); + #ifdef WLED_DEBUG + strip.addEffect(255, &mode_IMUTest, _data_FX_MODE_IMUTest); + #endif + #endif + strip.addEffect(255, &mode_3DIMUCube, _data_FX_MODE_3DIMUCube); //works also without IMU } - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ void connected() { - //Serial.println("Connected to WiFi!"); } void loop() { - if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice - lastUMRun = millis(); // update time keeping - - Wire.beginTransmission(MPU_ADDR); - Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40] - Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active. - Wire.requestFrom(MPU_ADDR, 7*2); // request a total of 7*2=14 registers - - // "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable - accelerometer_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L) - accelerometer_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L) - accelerometer_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L) - temperature = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L) - gyro_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L) - gyro_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L) - gyro_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L) } void addToJsonState(JsonObject& root) diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 70052cbc..de8e2a07 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -136,6 +136,9 @@ #include "../usermods/audioreactive/audio_reactive.h" #endif +#ifdef USERMOD_MPU6050_IMU +#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h" +#endif #ifdef USERMOD_GAMES #include "../usermods/usermod_v2_games/usermod_v2_games.h" #endif @@ -263,6 +266,11 @@ void registerUsermods() #ifdef USERMOD_AUDIOREACTIVE usermods.add(new AudioReactive()); #endif + + #ifdef USERMOD_MPU6050_IMU + usermods.add(new MPU6050Driver()); + #endif + #ifdef USERMOD_GAMES usermods.add(new GamesUsermod()); #endif