Bluetooth hardware serial connection to Blynk

Hi

I have a MPU5060 and a HC-06 bluetooth module connected to an arduino nano which i’m using to communicate with the Blynk app.

The app connects nicely to the arduino via bluetooth and as a test I am writing the millis to the v5 pin and displaying that on the app. I am also displaying a stream of gyro data on the serial monitor.

My issue comes in with the fact that the application freezes from time to time for random intervals, anything from a second or two to 20 seconds. I have done a bit of research and it seems that the MPU code i’m using uses interrupts which doesn’t play nicely with SoftwareSerial.

As I understand there are two solutions, one being to change the MPU code to work without interrupts. The other is to connect the HC-05 to the hardware serial interface. I would like to go with this later solution. The demo version of the HC-05 code on the Bynk site requires SoftwareSerial however. Is there a way for me to connect the bluetooth module to Blynk using the hardware serial TX and RX pins on my arduino?

Please see my code below as reference.

// I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h files
// for both classes must be in the include path of your project
#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

// class default I2C address is 0x68
// specific I2C addresses may be passed as a parameter here
// AD0 low = 0x68 (default for SparkFun breakout and InvenSense evaluation board)
// AD0 high = 0x69
MPU6050 mpu;
//MPU6050 mpu(0x69); // <-- use for AD0 high

// uncomment "OUTPUT_READABLE_YAWPITCHROLL" if you want to see the yaw/
// pitch/roll angles (in degrees) calculated from the quaternions coming
// from the FIFO. Note this also requires gravity vector calculations.
// Also note that yaw/pitch/roll angles suffer from gimbal lock (for
// more info, see: http://en.wikipedia.org/wiki/Gimbal_lock)
#define OUTPUT_READABLE_YAWPITCHROLL

#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
bool blinkState = false;

// 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

// 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


//Calibration
float RollCalibrationReading;  // initial roll reading before calibration, this value is used for calibrtion
float RollValue;  // Roll angle
float CallibrationAdjustment; // Calibration adjustment
boolean RunCalibration = false;  // has calibration run yet

// Handstand
boolean standStatus;

// timing
unsigned long startMillis;
unsigned long currentMillis;
unsigned long strandStartTime;
unsigned long standDuration;

// packet structure for InvenSense teapot demo
uint8_t teapotPacket[14] = { '$', 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, '\r', '\n' };


// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
  mpuInterrupt = true;
}


// ================================================================
// ===               BLUETOOTH SETUP                            ===
// ================================================================

#define BLYNK_PRINT Serial


#include <SoftwareSerial.h>
//SoftwareSerial SwSerial(7, 8); // RX, TX
    
#include <BlynkSimpleSerialBLE.h>
//#include <SoftwareSerial.h>

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "8e9354f3c75148acb8eb2efb56e221c7";

SoftwareSerial SerialBLE(7, 8); // RX, TX

// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {

// bluetooth
  Serial.begin(9600);

  SerialBLE.begin(9600);
  Blynk.begin(SerialBLE, auth);

  Serial.println("Connected");
  
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  Serial.println("Start testing LED");
// bluetooth

  startMillis = millis();

  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  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(9600);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately

  // NOTE: 8MHz or slower host processors, like the Teensy @ 3.3v or Ardunio
  // 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

  mpu.initialize();

  devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(138);
  mpu.setYGyroOffset(-37);
  mpu.setZGyroOffset(41);
  mpu.setZAccelOffset(1338); // 1688 factory default for my test chip

  // make sure it worked (returns 0 if so)

  // turn on the DMP, now that it's ready
  mpu.setDMPEnabled(true);

  // enable Arduino interrupt detection
  attachInterrupt(0, 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();


}

// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {

  Blynk.run();

  currentMillis = millis();

  // if programming failed, don't try to do anything
  if (!dmpReady) return;

  // wait for MPU interrupt or extra packet(s) available
  while (!mpuInterrupt) {
    // other program behavior stuff here
    // .
    // .
    // .
    // if you are really paranoid you can frequently test in between other
    // stuff to see if mpuInterrupt is true, and if so, "break;" from the
    // while() loop to immediately process the MPU data
    // .
    // .
    // .
  }

  // 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;

    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);

    RollValue = (ypr[2] * 180 / M_PI);

    if ((RunCalibration == false) && (currentMillis - startMillis >= 2000)) //optimum millis 5000
    {
      Calibration();
    }
    else if (RunCalibration == true) {


      TestMeasurements();
      //MeasureHandstand();

    }
  }
}

// ================================================================
// ===                      CALIBRATION FUNCTION                ===
// ================================================================

void Calibration()
{
  //Calibration reading

  RollCalibrationReading = (ypr[2] * 180 / M_PI);
  if ((RollCalibrationReading >= 0)&&(RollCalibrationReading <= 180)){
    CallibrationAdjustment = RollCalibrationReading;
  }
  else if ((RollCalibrationReading > 180)&&(RollCalibrationReading <= 360)){
    CallibrationAdjustment = RollCalibrationReading - 360;
  }

  //Take calibration reading once
  Serial.print("Calibdarion value");
  Serial.println(CallibrationAdjustment);
  Serial.print("Time");
  Serial.println(currentMillis);
  Serial.println("");

  RunCalibration = true;
}

// ================================================================
// ===                      TEST MEAUSREMENTS                   ===
// ================================================================

void TestMeasurements() {
  //Serial.print("rool value before calibration\t");
  //Serial.println(ypr[2] * 180 / M_PI);
  //Serial.print("roll angle after calibration\t");
  Serial.println(RollValue);
  Blynk.virtualWrite(V5, millis()/1000);

  //Serial.print("\t");
  //Serial.println(currentMillis);
}

// ================================================================
// ===                      HANDSTAND MEASUREMENT               ===
// ================================================================

void MeasureHandstand()
{

  if (((RollValue > (100 + CallibrationAdjustment)) && (RollValue < (200 + CallibrationAdjustment))) && (standStatus == false)) {
    // Doing handstand

    Serial.println("upside down");
    standStatus = true;

    //start timer
    strandStartTime = currentMillis - startMillis;

  }
  else if (((RollValue < (20 + CallibrationAdjustment)) || (RollValue > (300 + CallibrationAdjustment))) && (standStatus == true)) {
    // Not doing handstand

    Serial.print("right way up");
    standStatus = false;

    //end timer && display results - also save results to table under the date it was performed (table includes date, amount of handstands and their average time)

    standDuration = currentMillis - strandStartTime;
    Serial.print("\t Stand duration: ");
    Serial.print(standDuration);
    Serial.print(" milliseconds");
  }
}

Possibly a library conflict… but there is also the fact that your void loop() is very IoT (including Blynk) unfriendly…

http://help.blynk.cc/getting-started-library-auth-token-code-examples/blynk-basics/keep-your-void-loop-clean

Yes, just simply remove the soft serial library and commands and program as direct to Serial instead, but then you cannot use ANY Serial.Print() or Serial.Println() command, including #define BLYNK_PRINT Serial, and be aware that you will need to unplug your BT device each and every time you want to upload a sketch.

Basically, doable, but not advisable.

I suppose you could also try different soft serial libraries, and/or change the pins used to see if that makes a difference.

Hi Gunner thanks for the informative reply. I will have a look at cleaning up the void loop.

If I remove software serial and #define BLYNK_PRINT Serial, the below section of code wont compile.

SoftwareSerial SerialBLE(7, 8); // RX, TX
SerialBLE.begin(9600);
Blynk.begin(SerialBLE, auth);

I’m assuming that this auth section is quite important for connecting to Blynk so what would the code be if i’m connecting the bluetooth module to the hardware serial instead of going the software serial route?

if you are changing to the built in Serial, then you can remove the 1st & 2nd and change the 3rd to…

Blynk.begin(Serial, auth);

Hi Gunner, thank you very much!

I’m going to give this a go and your help thus far is much appreciated.

HI Gunner

Just had to swap one thing around to get it partially working. Seems it was (auth, Serial) and not (Serial, auth), thanks for point me in the right direction though!

Currently i’m only getting one way communication. I can activate a LED with the Blynk app but I cant seem to display the milli on the app.

#define BLYNK_PRINT Serial

#include <BlynkSimpleSerialBLE.h>

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth = “8e9354f3c75148acb8eb2efb56e221c7”;

BlynkTimer timer;

// This function sends Arduino’s up time every second to Virtual Pin (5).
// In the app, Widget’s reading frequency should be set to PUSH. This means
// that you define how often to send data to Blynk App.
void myTimerEvent()
{
// You can send any value at any time.
// Please don’t send more that 10 values per second.
Blynk.virtualWrite(V5, millis() / 1000);
}

void setup()
{
// Debug console
Serial.begin(9600);
Blynk.begin(auth, Serial);

Serial.println(“Waiting for connections…”);

// Setup a function to be called every second
timer.setInterval(1000L, myTimerEvent);

pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
Blynk.virtualWrite(V5, 0);
}

void loop()
{
Blynk.run();
timer.run(); // Initiates BlynkTimer
}

Removing the below line solved my problem. Thank you very very much for all the help Gunner and sorry for being a help vampire.

#define BLYNK_PRINT Serial

1 Like