From ed5491a48013e97c438fc516542f5680583797c9 Mon Sep 17 00:00:00 2001 From: Axlan Date: Sat, 20 Jun 2020 19:44:29 -0700 Subject: [PATCH 1/3] Added usermod for connecting MPU6050 IMU --- usermods/mpu6050_imu/readme.md | 93 +++++++ usermods/mpu6050_imu/usermod_mpu6050_imu.h | 274 +++++++++++++++++++++ wled00/const.h | 1 + 3 files changed, 368 insertions(+) create mode 100644 usermods/mpu6050_imu/readme.md create mode 100644 usermods/mpu6050_imu/usermod_mpu6050_imu.h diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md new file mode 100644 index 000000000..0a96509e3 --- /dev/null +++ b/usermods/mpu6050_imu/readme.md @@ -0,0 +1,93 @@ +# MPU-6050 Six-Axis (Gyro + Accelerometer) Driver + +This usermod-v2 modification allows the connection of a MPU-6050 IMU sensor to +allow for effects that are controlled by the orientation or motion of the WLED Device. + +The MPU6050 has a built in "Digital Motion Processor" which does a lot of the heavy +lifting in integrating the gyro and accel measurements to get potentially more +useful gravity vector and orientation output. + +It is pretty straightforward to comment out some of the variables being read off the device if they're not needed to save CPU/Mem/Bandwidth. + +_Story:_ + +I built an icosahedron globe with lights inside to indicate cities I travelled to. I +wanted to integrate an IMU to allow either on-board, or off-board effects that would +react to the globes orientation. See TBD BLOG POST. + +## Adding Dependencies + +I2Cdev and MPU6050 must be installed. + +To install them, add I2Cdevlib-MPU6050@fbde122cc5 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) + +For example: + +``` +lib_compat_mode = soft +lib_deps = + FastLED@3.3.2 + NeoPixelBus@2.5.7 + ESPAsyncTCP@1.2.0 + ESPAsyncUDP@697c75a025 + AsyncTCP@1.0.3 + Esp Async WebServer@1.2.0 + IRremoteESP8266@2.7.3 + I2Cdevlib-MPU6050@fbde122cc5 +``` + +## Wiring + +The connections needed to the MPU6050 are as follows: +``` + VCC VU (5V USB) Not available on all boards so use 3.3V if needed. + GND G Ground + SCL D1 (GPIO05) I2C clock + SDA D2 (GPIO04) I2C data + XDA not connected + XCL not connected + AD0 not connected + INT D8 (GPIO15) Interrupt pin +``` + +You could probably modify the code not to need an interrupt, but I used the +setup directly from the example. + +## JSON API + +This code adds: +```json +"u":{ + "IMU":{ + "Quat": [w, x, y, z], + "Euler": [psi, theta, phi], + "Gyro": [x, y, z], + "Accel": [x, y, z], + "RealAccel": [x, y, z], + "WorldAccel": [x, y, z], + "Gravity": [x, y, z], + "Orientation": [yaw, pitch, roll] + } +} +``` +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`. + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" + +#include "usermod_mpu6050_imu.h" + +void registerUsermods() +{ + usermods.add(new MPU6050Driver()); +} +``` diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h new file mode 100644 index 000000000..965ab41b9 --- /dev/null +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -0,0 +1,274 @@ +#pragma once + +#include "wled.h" + +/* 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 + + Tested with a d1 mini esp-12f + + GY-521 NodeMCU + MPU6050 devkit 1.0 + board Lolin Description + ======= ========== ==================================================== + VCC VU (5V USB) Not available on all boards so use 3.3V if needed. + GND G Ground + SCL D1 (GPIO05) I2C clock + SDA D2 (GPIO04) I2C data + XDA not connected + XCL not connected + AD0 not connected + INT D8 (GPIO15) Interrupt pin + + Using 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 + 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) + 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 +#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE + #include "Wire.h" +#endif + +// ================================================================ +// === INTERRUPT DETECTION ROUTINE === +// ================================================================ + +volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high +void IRAM_ATTR dmpDataReady() { + mpuInterrupt = true; +} + + +class MPU6050Driver : public Usermod { + private: + MPU6050 mpu; + + // MPU control/status vars + bool dmpReady = false; // set true if DMP init was successful + uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU + uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) + uint16_t packetSize; // expected DMP packet size (default is 42 bytes) + 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 + 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 + + 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() { + // 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 + #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE + Fastwire::setup(400, true); + #endif + + // initialize device + Serial.println(F("Initializing I2C devices...")); + mpu.initialize(); + pinMode(INTERRUPT_PIN, INPUT); + + // verify connection + Serial.println(F("Testing device connections...")); + Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); + + // load and configure the DMP + Serial.println(F("Initializing DMP...")); + devStatus = mpu.dmpInitialize(); + + // supply your own gyro offsets here, scaled for min sensitivity + mpu.setXGyroOffset(220); + mpu.setYGyroOffset(76); + mpu.setZGyroOffset(-85); + mpu.setZAccelOffset(1788); // 1688 factory default for my test chip + + // make sure it worked (returns 0 if so) + if (devStatus == 0) { + // turn on the DMP, now that it's ready + Serial.println(F("Enabling DMP...")); + mpu.setDMPEnabled(true); + + // enable Arduino interrupt detection + Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)...")); + 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 + Serial.println(F("DMP ready! Waiting for first interrupt...")); + dmpReady = true; + + // 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) + Serial.print(F("DMP Initialization failed (code ")); + Serial.print(devStatus); + Serial.println(F(")")); + } + } + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() { + //Serial.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 (!dmpReady) 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(); + Serial.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; + + + //NOTE: some of these can be removed to save memory, processing time + // if the measurement isn't needed + mpu.dmpGetQuaternion(&qat, fifoBuffer); + mpu.dmpGetEuler(euler, &qat); + mpu.dmpGetGravity(&gravity, &qat); + mpu.dmpGetGyro(&gy, fifoBuffer); + mpu.dmpGetAccel(&aa, fifoBuffer); + mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); + mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &qat); + mpu.dmpGetYawPitchRoll(ypr, &qat, &gravity); + } + } + + + + 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"); + 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]); + JsonArray accel_json = imu_meas.createNestedArray("Accel"); + accel_json.add(aa.x); + accel_json.add(aa.y); + accel_json.add(aa.z); + JsonArray gyro_json = imu_meas.createNestedArray("Gyro"); + 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); + real_json.add(aaReal.z); + JsonArray grav_json = imu_meas.createNestedArray("Gravity"); + 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]); + } + + + /* + * 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) Serial.println(F("Don't burn down your garage!")); + } + + + /* + * 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; + } + +}; \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 34e42f8a1..e619c722a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -20,6 +20,7 @@ #define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h" #define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" #define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" +#define USERMOD_ID_IMU 5 //Usermod "usermod_PIR_sensor_switch.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot From b2c656940b9749652766f2fd2da27e5e64f4969f Mon Sep 17 00:00:00 2001 From: Jonathan Diamond Date: Sun, 21 Jun 2020 17:32:39 -0700 Subject: [PATCH 2/3] Added link for video and blog --- usermods/mpu6050_imu/readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md index 0a96509e3..adb19ef8e 100644 --- a/usermods/mpu6050_imu/readme.md +++ b/usermods/mpu6050_imu/readme.md @@ -11,9 +11,10 @@ It is pretty straightforward to comment out some of the variables being read off _Story:_ -I built an icosahedron globe with lights inside to indicate cities I travelled to. I -wanted to integrate an IMU to allow either on-board, or off-board effects that would -react to the globes orientation. See TBD BLOG POST. +As a memento to a long trip I was on, I built an icosahedron globe. I put lights inside to indicate cities I travelled to. + +I wanted to integrate an IMU to allow either on-board, or off-board effects that would +react to the globes orientation. See the blog post on building it or a video demo . ## Adding Dependencies From 652651668fdab2cdfc847f314131513bf33f379a Mon Sep 17 00:00:00 2001 From: Aircoookie Date: Mon, 22 Jun 2020 15:03:37 +0200 Subject: [PATCH 3/3] Update MPU usermod ID --- wled00/const.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/const.h b/wled00/const.h index e619c722a..152cbb52b 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -20,7 +20,7 @@ #define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h" #define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" #define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" -#define USERMOD_ID_IMU 5 //Usermod "usermod_PIR_sensor_switch.h" +#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot