Issues interfacing Blynk App with MPU6050 using NodeMCU

Hi everyone, I am currently working on a hobby project to create a Wifi controlled drone using the Blynk App. I have stumbled upon a brick wall because the code here stopped working when I call the Blynk.syncVirtual(V20, V21, V22); line. Without initializing the MPU6050, the code worked perfectly fine.

Here are the causes and symptoms:

  1. When I call the Blynk.syncVirtual(V20, V21, V22) before initializing the MPU6050, the MPU6050 will always fail to initialize.
  2. When I initialize the MPU6050 first before calling the Blynk.syncVirtual(V20, V21, V22); then the nodeMCU goes into soft reset mode everytime.

Here is what you can safely assume:

  1. The MPU6050 was tested and working fine on Arduino Uno and NodeMCU (Without the Blynk App)
  2. the nodeMCU is not broken in any way
  3. I’ve used the DMP MPU6050 library provided by jrowberg/i2cdevlib

Any suggestion on this matter would be greatly appreciated.

Sketch code:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
#include <Wire.h>

// For MPU-6050
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"

#ifndef APSSID
#define APSSID "<USERNAME>"
#define APPSK  "<PASSWORD>"
#endif

const char *ssid = APSSID;
const char *password = APPSK;
char auth[] = "<AUTH_TOKEN>";

// Pin declaration
#define SCL D1  //D6
#define SDA D2  //D5
#define INTERRUPT_PIN D5  //D1

/*------------------------------------------------Global Var--------------------------------------------------------*/
ESP8266WebServer server(80);
MPU6050 mpu;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
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

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

int pitch_roll[] = { 0, 0}; int throttle_yaw[] = { 0, 0};
int pitchVal[] = {0, 0, 0, 0}; int rollVal[] = {0, 0, 0, 0}; int yawVal[] = {0, 0, 0, 0}; int throttleVal[] = {0, 0, 0, 0}; int calibVal[] = {0, 0, 0, 0};
int yawAngle = 0; int pitchAngle = 0; int rollAngle = 0;

void setup() {
  pinMode(INTERRUPT_PIN, INPUT);

  do {
    Wire.begin(SDA, SCL); //Wire.begin (<SDA>,<SCL>)
    //Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
    Serial.println(F("Initializing Gyroscope and Accelerometer..."));
    mpu.initialize();
    Serial.println(mpu.testConnection() ? F("\tMPU6050 connection successful") : F("\tMPU6050 connection failed"));
    Serial.println(F("\tInitializing DMP..."));
    devStatus = mpu.dmpInitialize();
    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(0);  // Default 220. Use 0 (value confirmed)
    mpu.setYGyroOffset(0 );   // Default 76. Use 0 (value confirmed)
    mpu.setZGyroOffset(0);  // Default -85. Use 0 (value confirmed)
    mpu.setZAccelOffset(1024); // Default 1788. Use 1024 (value confirmed)
    delay (1);
  }
  while (devStatus != 0);

  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // turn on the DMP, now that it's ready
    mpu.setDMPEnabled(true);

    // enable Arduino interrupt detection
    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
    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("\tDMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }
  Serial.println(F("Gyroscope and Accelerometer --------------------------- OK"));

  Blynk.begin(auth, ssid, password); // Connect to Blynk App
}

void loop() {
  Blynk.run();

  // Protection from unintetional disconnect from server. Attempt to reconnect after that.
  if (!Blynk.connected()) {
    Serial.println ("\nDisconnected from Blynk App");
    Serial.println ("Attempting to reconnect...");
    Blynk.begin(auth, ssid, password);
    delay (1);
  }

  Blynk.syncVirtual(V20, V21, V22);  // Read all widget values
  // Read some Gyroscope values here....

  delay (1);
}


/**********************************************************************************************INTERRUPT DETECTION ROUTINE*********************************
   Interrupt pin for MPU6050
*/
void dmpDataReady() {
  mpuInterrupt = true;
}

/**********************************************************************************************************PITCH/ROLL joystick*********************************
   Pitch/Roll Joystick
*/
BLYNK_WRITE(V20) {
  int x = param[0].asInt();
  int y = param[1].asInt();
  (y < 384 || y > 640) ? pitch_roll[1] = map(y, 0, 1023, -20, 20) : pitch_roll[1] = 0; // Pitch
  (x < 384 || x > 640) ? pitch_roll[2] = map(x, 0, 1023, -20, 20) : pitch_roll[2] = 0; //Roll
}

/**********************************************************************************************************Throttle/Yaw joystick*********************************
   Throttle/Yaw Joystick
*/
BLYNK_WRITE(V21) {
  int x = param[0].asInt();
  int y = param[1].asInt();
  (y > 100) ? throttle_yaw[1] = map(y, 0, 1023, 0, 70) : throttle_yaw[1] = 0; //Throttle. y > 100 to prevent motor burnout under for low PWM values. Default: map(y, 0, 1023, 0, 70)
  (x < 312 || x > 712) ? throttle_yaw[2] = map(x, 0, 1023, -35, 35) : throttle_yaw[2] = 0; //Yaw. Default: map(x, 0, 1023, -35, 35)
}

/**********************************************************************************************************MANUAL RESET*********************************
   Reset button on app
*/
BLYNK_WRITE(V22) {
  resetVal = param.asInt();
}

I’ve corrected the formatting of your code by adding triple backticks at the beginning and end, so that it displays correctly.
Triple backticks look like this:
```
Pleas add them yourself in future otherwise your incorrectly formatted code will be removed.

You’re calling Blynk.syncVirtual in your void loop, which is causing the problem. Blynk.syncVirtual(VPin) would normally be called once, in your void setup.
In addition, you don’t have any BLYNK_WRITE(VPin) functions in your code to receive the corresponding results from the Blynk server, so the Blynk.syncVirtual(VPin) commands aren’t achieving anything anyway.

BLYNK_WRITE(VPin) is a type of callback function that triggers automatically when the value of a virtual pin changes on the server. This change is normally a result of changing a widget in the app.

Blynk.syncVirtual(VPin) causes the corresponding BLYNK_WRITE(VPin) callback function to ‘fire’, allowing you to retrieve and process last value for the VPin that was stored on the server. As the name implies, it synchronises the hardware with the App/Server when the hardware device boots up.

Pete.

Hey Pete,

My apologies as this is my first time posting in the Blynk community. Will definitely add it in the next time. Also, I was simplifying the codes here while making sure it is still working. I may have missed out the BLYNK_WRITE functions. I’ve edited the codes as shown.

As for your suggestion on only one Blynk.syncVirtual, I’ll try to add it only once in the void setup(). Another question is that… what If I lose connection from the Blynk App, do I need to call Blynk.syncVirtual again after Blynk.begin() again? I’ll update here once I’ve tested out your solution. Thanks!

Just to be clear, with a Wi-Fi connection, the app is connected to the server, and the hardware running your code is also connected to the server, so the hardware would lose the connection to the server, not the app.
Instead of putting the Blynk.syncVirtual(VPin) command in void setup, you could put it in another one of Blynk’s callback functions called BLYNK_CONNECTED.
https://docs.blynk.cc/#blynk-firmware-blynktimer-blynk_connected

Pete.

I’ve modified the codes as below. What I have done here is that I’ve added a callback function BLYNK_CONNECTED() to sync the virtual pins once the hardware is connected to the server. This is the only place that the Blynk.syncVIrtual() is called. Also, in the setup() part, I am connecting the hardware to the blynk server first before initializing the MPU6050.

Here’s what I found out:

  1. Hardware connection to blynk cloud and MPU6050 was successful only if the Blynk.syncVirtual line of code in BLYNK_CONNECTED() is commented out.
  2. With Blynk.syncVirtual(), the MPU6050 cannot be initialized.

On a side note, it may be important for me to mention that the DMP MPU6050 library uses an interrupt routine for it to work. Could that be interfering with the Blynk server by any chance?

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
#include <Wire.h>

// For MPU-6050
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"

#ifndef APSSID
#define APSSID "<USERNAME>"
#define APPSK  "<PASSWORD>"
#endif

const char *ssid = APSSID;
const char *password = APPSK;
char auth[] = "<AUTH_TOKEN>";

// Pin declaration
#define SCL D1  //D6
#define SDA D2  //D5
#define INTERRUPT_PIN D5  //D1

/*------------------------------------------------Global Var--------------------------------------------------------*/
ESP8266WebServer server(80);
MPU6050 mpu;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
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

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

int pitch_roll[] = { 0, 0}; int throttle_yaw[] = { 0, 0};
int pitchVal[] = {0, 0, 0, 0}; int rollVal[] = {0, 0, 0, 0}; int yawVal[] = {0, 0, 0, 0}; int throttleVal[] = {0, 0, 0, 0}; int calibVal[] = {0, 0, 0, 0};
int yawAngle = 0; int pitchAngle = 0; int rollAngle = 0;


void setup() {
  pinMode(INTERRUPT_PIN, INPUT);

    Blynk.begin(auth, ssid, password); // Connect to Blynk App

  do {
    Wire.begin(SDA, SCL); //Wire.begin (<SDA>,<SCL>)
    //Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
    Serial.println(F("Initializing Gyroscope and Accelerometer..."));
    mpu.initialize();
    Serial.println(mpu.testConnection() ? F("\tMPU6050 connection successful") : F("\tMPU6050 connection failed"));
    Serial.println(F("\tInitializing DMP..."));
    devStatus = mpu.dmpInitialize();
    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(0);  // Default 220. Use 0 (value confirmed)
    mpu.setYGyroOffset(0 );   // Default 76. Use 0 (value confirmed)
    mpu.setZGyroOffset(0);  // Default -85. Use 0 (value confirmed)
    mpu.setZAccelOffset(1024); // Default 1788. Use 1024 (value confirmed)
    delay (1);
  }
  while (devStatus != 0);

  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // turn on the DMP, now that it's ready
    mpu.setDMPEnabled(true);

    // enable Arduino interrupt detection
    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
    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("\tDMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }
  Serial.println(F("Gyroscope and Accelerometer --------------------------- OK"));


}

void loop() {
  Blynk.run();

  // Protection from unintetional disconnect from server. Attempt to reconnect after that.
  if (!Blynk.connected()) {
    Serial.println ("\nDisconnected from Blynk App");
    Serial.println ("Attempting to reconnect...");
    Blynk.begin(auth, ssid, password);
    delay (1);
  }

  // Read some Gyroscope values here....

  delay (1);
}


/**********************************************************************************************INTERRUPT DETECTION ROUTINE*********************************
   Interrupt pin for MPU6050
*/
void dmpDataReady() {
  mpuInterrupt = true;
}


/**********************************************************************************************************PITCH/ROLL joystick*********************************
   Pitch/Roll Joystick
*/
BLYNK_WRITE(V20) {
  int x = param[0].asInt();
  int y = param[1].asInt();
  (y < 384 || y > 640) ? pitch_roll[1] = map(y, 0, 1023, -20, 20) : pitch_roll[1] = 0; // Pitch
  (x < 384 || x > 640) ? pitch_roll[2] = map(x, 0, 1023, -20, 20) : pitch_roll[2] = 0; //Roll
}

/**********************************************************************************************************Throttle/Yaw joystick*********************************
   Throttle/Yaw Joystick
*/
BLYNK_WRITE(V21) {
  int x = param[0].asInt();
  int y = param[1].asInt();
  (y > 100) ? throttle_yaw[1] = map(y, 0, 1023, 0, 70) : throttle_yaw[1] = 0; //Throttle. y > 100 to prevent motor burnout under for low PWM values. Default: map(y, 0, 1023, 0, 70)
  (x < 312 || x > 712) ? throttle_yaw[2] = map(x, 0, 1023, -35, 35) : throttle_yaw[2] = 0; //Yaw. Default: map(x, 0, 1023, -35, 35)
}

BLYNK_CONNECTED() {
  Blynk.syncVirtual(V20, V21, V22);  // Sync values
}

/**********************************************************************************************************MANUAL RESET*********************************
   Reset button on app
*/
BLYNK_WRITE(V22) {
  resetVal = param.asInt();
}

Just to update this section just in case it may help someone, someday. I found a workaround for the issue I’m facing, which is to calculate roll and pitch using raw gyro and accel values as shown in the following lines of code.

I did not use gyroscope values in the end as I noticed that they were too sensitive to vibrations for some reason. So I just used the accelerometer values and implemented the built-in digital low pass filters on the MPU 6050.

float CALC_YPR (String type) {
  float A = 0.98;  // Default 0.98. Ratio of how much the current gyro/accel values are kept. Only 2% of new values are added in this case
  float dt = 0.15;  // Default 0.15. dt = timestep. Should be approximaately equals to one program loop
  float tau = A / (1 - A) * dt;
  float rawGyro[3] = {gyroX, gyroY, gyroZ};
  float rawAccel[3] = {accelX, accelY, accelZ};
  float angle, filtered_gyro, filtered_acc;

  if (type == "pitch") {
    //filtered_gyro = A * filtered_gyro + (1 - A) * rawGyro[0];
    filtered_gyro = 0;                                              // Gyroscope values are too sensitive. Perhaps remove them?
    filtered_acc = A * filtered_acc + (1 - A) * rawAccel[0];
    angle = tau * filtered_gyro + filtered_acc;
  }
  else if (type == "roll") {
    // filtered_gyro = A * filtered_gyro + (1 - A) * rawGyro[1];
    filtered_gyro = 0;                                              // Gyroscope values are too sensitive. Perhaps remove them?
    filtered_acc = A * filtered_acc + (1 - A) * rawAccel[1];
    angle = tau * filtered_gyro + filtered_acc;
    angle *= -1;  // Roll right as positive value. Roll left as negative value
  }
  return angle;
}

Hi Justin,
I just went through the whole conversation and I have a few questions regarding the subject.
Can we please connect?

Thanks,
Deepak