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
This commit is contained in:
Ewowi
2022-09-27 15:19:58 +02:00
parent 471ccf946b
commit 9414f531dd
5 changed files with 387 additions and 215 deletions

View File

@@ -12,7 +12,7 @@
; default_envs = travis_esp8266, travis_esp32 ; default_envs = travis_esp8266, travis_esp32
# Release binaries # 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 # 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 ; 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_4CH_MagicHome
; default_envs = esp8285_H801 ; default_envs = esp8285_H801
; default_envs = d1_mini_5CH_Shojo_PCB ; default_envs = d1_mini_5CH_Shojo_PCB
; default_envs = esp32mdev
default_envs = esp32mdevums
; default_envs = esp8266mdev
; default_envs = wemos_shield_esp32 ; default_envs = wemos_shield_esp32
; default_envs = m5atom ; default_envs = m5atom
; default_envs = esp32_eth ; default_envs = esp32_eth
@@ -222,7 +225,9 @@ default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps = lib_deps =
${env.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 makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 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 build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
lib_deps = ${esp8266.lib_deps} 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 # custom board configurations
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -444,21 +525,33 @@ lib_deps = ${esp8266.lib_deps}
[env:wemos_shield_esp32] [env:wemos_shield_esp32]
board = esp32dev board = esp32dev
platform = espressif32@3.2 platform = espressif32@3.2
upload_speed = 460800 upload_speed = 460800 ; or 921600
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} 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 LEDPIN=16
-D RLYPIN=19 -D RLYPIN=19
-D BTNPIN=17 -D BTNPIN=17
-D IRPIN=18 -D IRPIN=18
-D UWLED_USE_MY_CONFIG -D UWLED_USE_MY_CONFIG
-D USERMOD_DALLASTEMPERATURE -D USERMOD_CUSTOMEFFECTS
-D USERMOD_FOUR_LINE_DISPLAY -D USERMOD_AUDIOREACTIVE -D AUDIOPIN=-1 -D DMTYPE=1 -D I2S_SDPIN=32 -D I2S_WSPIN=15 -D I2S_CKPIN=14
-D TEMPERATURE_PIN=23 -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} lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5 OneWire@~2.3.5
olikraus/U8g2 @ ^2.28.8 olikraus/U8g2 @ ^2.28.8
https://github.com/kosme/arduinoFFT#develop @ 1.9.2
board_build.partitions = ${esp32.default_partitions} board_build.partitions = ${esp32.default_partitions}
; board_build.partitions = tools/WLED_ESP32_16MB.csv
[env:m5atom] [env:m5atom]
board = esp32dev board = esp32dev

View File

@@ -20,9 +20,9 @@ react to the globes orientation. See the blog post on building it <https://www.r
I2Cdev and MPU6050 must be installed. I2Cdev and MPU6050 must be installed.
To install them, add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file. To install them, add ElectronicCats/MPU6050 @ 0.6.0 to lib_deps in the platformio.ini file.
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) <!-- 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) -->
For example: For example:
@@ -36,7 +36,7 @@ lib_deps =
AsyncTCP@1.0.3 AsyncTCP@1.0.3
Esp Async WebServer@1.2.0 Esp Async WebServer@1.2.0
IRremoteESP8266@2.7.3 IRremoteESP8266@2.7.3
I2Cdevlib-MPU6050@fbde122cc5 ElectronicCats/MPU6050 @ 0.6.0
``` ```
## Wiring ## Wiring
@@ -77,18 +77,19 @@ to the info object
## Usermod installation ## Usermod installation
1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory. <!-- 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**: Example **usermods_list.cpp**:
```cpp ```cpp
#include "wled.h" #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()); usermods.add(new MPU6050Driver());
} #endif
``` ```

View File

@@ -2,6 +2,17 @@
#include "wled.h" #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 driver reads quaternion data from the MPU6060 and adds it to the JSON
This example is adapted from: This example is adapted from:
https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050/examples/MPU6050_DMP6_ESPWiFi 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 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 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 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. 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) // 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. 5. Wire up the MPU6050 as detailed above.
*/ */
#include "I2Cdev.h" #include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.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 // Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
// is used in I2Cdev.h // is used in I2Cdev.h
@@ -56,6 +66,7 @@ class MPU6050Driver : public Usermod {
private: private:
MPU6050 mpu; MPU6050 mpu;
bool enabled = true; bool enabled = true;
unsigned long lastUMRun = millis();
// MPU control/status vars // MPU control/status vars
bool dmpReady = false; // set true if DMP init was successful 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 uint16_t fifoCount; // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer uint8_t fifoBuffer[64]; // FIFO storage buffer
//NOTE: some of these can be removed to save memory, processing time public:
// if the measurement isn't needed // orientation/motion vars
Quaternion qat; // [w, x, y, z] quaternion container 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 aa; // [x, y, z] accel sensor measurements
VectorInt16 gy; // [x, y, z] gyro sensor measurements VectorInt16 gy; // [x, y, z] gyro sensor measurements
VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements
VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements
VectorFloat gravity; // [x, y, z] gravity vector 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 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() { void setup() {
DEBUG_PRINT_IMULN("mpu setup");
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
// join I2C bus (I2Cdev library doesn't do this automatically) // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin(); Wire.begin();
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true); Fastwire::setup(400, true);
#endif #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 // initialize device
DEBUG_PRINTLN(F("Initializing I2C devices...")); DEBUG_PRINT_IMULN(F("Initializing I2C devices..."));
mpu.initialize(); mpu.initialize();
pinMode(INTERRUPT_PIN, INPUT); pinMode(INTERRUPT_PIN, INPUT);
// verify connection // verify connection
DEBUG_PRINTLN(F("Testing device connections...")); DEBUG_PRINT_IMULN(F("Testing device connections..."));
DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); 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 // load and configure the DMP
DEBUG_PRINTLN(F("Initializing DMP...")); DEBUG_PRINT_IMULN(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize(); devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity // 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) // make sure it worked (returns 0 if so)
if (devStatus == 0) { if (devStatus == 0) {
// turn on the DMP, now that it's ready // Calibration Time: generate offsets and calibrate our MPU6050
DEBUG_PRINTLN(F("Enabling DMP...")); mpu.CalibrateAccel(6);
mpu.setDMPEnabled(true); 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 // enable Arduino interrupt detection
DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); DEBUG_PRINT_IMU(F("Enabling interrupt detection (Arduino external interrupt "));
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); DEBUG_PRINT_IMU(digitalPinToInterrupt(INTERRUPT_PIN));
mpuIntStatus = mpu.getIntStatus(); 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 // 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...")); DEBUG_PRINT_IMULN(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true; dmpReady = true;
// get expected DMP packet size for later comparison // get expected DMP packet size for later comparison
packetSize = mpu.dmpGetFIFOPacketSize(); packetSize = mpu.dmpGetFIFOPacketSize();
} else { } else {
// ERROR! // ERROR!
// 1 = initial memory load failed // 1 = initial memory load failed
// 2 = DMP configuration updates failed // 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1) // (if it's going to break, usually the code will be 1)
DEBUG_PRINT(F("DMP Initialization failed (code ")); DEBUG_PRINT_IMU(F("DMP Initialization failed (code "));
DEBUG_PRINT(devStatus); DEBUG_PRINT_IMU(devStatus);
DEBUG_PRINTLN(F(")")); DEBUG_PRINT_IMULN(F(")"));
} }
} }
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() { void connected() {
//DEBUG_PRINTLN("Connected to WiFi!");
} }
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() { void loop() {
// if programming failed, don't try to do anything // if programming failed, don't try to do anything
if (!enabled || !dmpReady || strip.isUpdating()) return; if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice
lastUMRun = millis(); // update time keeping
// 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 (!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 //NOTE: some of these can be removed to save memory, processing time
// if the measurement isn't needed // if the measurement isn't needed
mpu.dmpGetQuaternion(&qat, fifoBuffer); mpu.dmpGetQuaternion(&qat, fifoBuffer);
@@ -200,25 +200,24 @@ class MPU6050Driver : public Usermod {
} }
} }
void addToJsonInfo(JsonObject& root) void addToJsonInfo(JsonObject& root)
{ {
int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("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"); JsonArray quat_json = imu_meas.createNestedArray("Quat");
quat_json.add(qat.w); quat_json.add(qat.w);
quat_json.add(qat.x); quat_json.add(qat.x);
quat_json.add(qat.y); quat_json.add(qat.y);
quat_json.add(qat.z); quat_json.add(qat.z);
JsonArray euler_json = imu_meas.createNestedArray("Euler"); JsonArray euler_json = imu_meas.createNestedArray("Euler");
euler_json.add(euler[0]); euler_json.add(euler[0] * 180/M_PI);
euler_json.add(euler[1]); euler_json.add(euler[1] * 180/M_PI);
euler_json.add(euler[2]); euler_json.add(euler[2] * 180/M_PI);
JsonArray accel_json = imu_meas.createNestedArray("Accel"); JsonArray accel_json = imu_meas.createNestedArray("Accel");
accel_json.add(aa.x); accel_json.add(aa.x);
accel_json.add(aa.y); accel_json.add(aa.y);
@@ -227,10 +226,6 @@ class MPU6050Driver : public Usermod {
gyro_json.add(gy.x); gyro_json.add(gy.x);
gyro_json.add(gy.y); gyro_json.add(gy.y);
gyro_json.add(gy.z); 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"); JsonArray real_json = imu_meas.createNestedArray("RealAccel");
real_json.add(aaReal.x); real_json.add(aaReal.x);
real_json.add(aaReal.y); real_json.add(aaReal.y);
@@ -239,49 +234,54 @@ class MPU6050Driver : public Usermod {
grav_json.add(gravity.x); grav_json.add(gravity.x);
grav_json.add(gravity.y); grav_json.add(gravity.y);
grav_json.add(gravity.z); grav_json.add(gravity.z);
JsonArray orient_json = imu_meas.createNestedArray("Orientation"); #endif
orient_json.add(ypr[0]); JsonArray world_json = imu_meas.createNestedArray("WorldAccel");
orient_json.add(ypr[1]); world_json.add(aaWorld.x);
orient_json.add(ypr[2]); 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) //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) //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;
/* // JsonObject interruptPin = top.createNestedObject(FPSTR("interruptPin"));
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. // interruptPin["pin"] = interruptPin;
* 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!
*/ // bool readFromConfig(JsonObject& root)
// void addToConfig(JsonObject& root) // {
// { // JsonObject top = root[FPSTR("MPU6050")];
// JsonObject top = root.createNestedObject("MPU6050_IMU"); // bool configComplete = !top.isNull();
// JsonArray pins = top.createNestedArray("pin");
// pins.add(HW_PIN_SCL); // configComplete &= getJsonValue(top[FPSTR("enabled")], enabled);
// pins.add(HW_PIN_SDA); // 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() uint16_t getId()
{ {
return USERMOD_ID_IMU; return USERMOD_ID_IMU;

View File

@@ -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 #pragma once
#include "wled.h" #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 //inspired by https://noobtuts.com/cpp/2d-pong-game
typedef struct PongBall { typedef struct PongBall {
float x;// = SEGMENT.virtualWidth() / 2; float x;// = SEGMENT.virtualWidth() / 2;
@@ -181,94 +175,170 @@ uint16_t mode_pongGame(void) {
static const char _data_FX_MODE_PONGGAME[] PROGMEM = "🎮 Pong@!;!;!;2d"; static const char _data_FX_MODE_PONGGAME[] PROGMEM = "🎮 Pong@!;!;!;2d";
//https://howtomechatronics.com/tutorials/arduino/arduino-and-mpu6050-accelerometer-and-gyroscope-tutorial/ //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); SEGMENT.fill(BLACK);
uint8_t y = 0; uint8_t y = 0;
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); if (IMU != nullptr) {
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_y+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_z+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE); SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.z+INT16_MAX)/(2*INT16_MAX), y+=1, 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); 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; 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 #endif
//WLEDSR 3D to 2D mapping
struct Voxel {
float x;
float y;
float z;
uint32_t col;
};
class Frame3D {
private:
std::vector<Voxel> 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 { class GamesUsermod : public Usermod {
private: 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: public:
//Functions called by WLED
void setup() { 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_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() { void connected() {
//Serial.println("Connected to WiFi!");
} }
void loop() { 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) void addToJsonState(JsonObject& root)

View File

@@ -136,6 +136,9 @@
#include "../usermods/audioreactive/audio_reactive.h" #include "../usermods/audioreactive/audio_reactive.h"
#endif #endif
#ifdef USERMOD_MPU6050_IMU
#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h"
#endif
#ifdef USERMOD_GAMES #ifdef USERMOD_GAMES
#include "../usermods/usermod_v2_games/usermod_v2_games.h" #include "../usermods/usermod_v2_games/usermod_v2_games.h"
#endif #endif
@@ -263,6 +266,11 @@ void registerUsermods()
#ifdef USERMOD_AUDIOREACTIVE #ifdef USERMOD_AUDIOREACTIVE
usermods.add(new AudioReactive()); usermods.add(new AudioReactive());
#endif #endif
#ifdef USERMOD_MPU6050_IMU
usermods.add(new MPU6050Driver());
#endif
#ifdef USERMOD_GAMES #ifdef USERMOD_GAMES
usermods.add(new GamesUsermod()); usermods.add(new GamesUsermod());
#endif #endif