Get from CurieIMU the rotation angle in degrees (without and with madgwick lib)

The first project with Arduino101 I wanted to do was a pan/tilt head that would compensate the Arduino movement.

But to do that I needed the IMU returns the degrees of rotation that arduino101 has performed on each axis... I searched without finding anything similar on the internet especially without using external libraries (apart CurieImu).

This is the shortest code that I could do to get degrees from curieImu, I hope it can be helpful, feedback are much appreciated + feel free to use / fork this code!

/*
* The code is released under the GNU General Public License.
* Developed by www.codekraft.it 
*/

#include "CurieImu.h"

// IMU
float FS_SEL = 131;                                  // IMU gyro values to degrees/sec
unsigned long last_read_time;
float angle_x, angle_y, angle_z;                     // These are the result angles
float last_x_angle, last_y_angle, last_z_angle;      // These are the filtered angles
float lGXA, lGYA, lGZA;                              // Store the gyro angles to compare drift

// FUNCTIONS
//Math
//Convert radians to degrees
double rtod(double fradians) {return(fradians * 180.0 / PI);}

void set_last_read_angle_data(unsigned long time, float x, float y, float z, float x_gyro, float y_gyro, float z_gyro) {
  last_read_time = time;
  last_x_angle = x; last_y_angle = y; last_z_angle = z;
  lGXA = x_gyro; lGYA = y_gyro; lGZA = z_gyro;
}

void setup() {
  
  Serial.begin(9600);
  while (!Serial);

  // init CurieImu
  CurieImu.initialize();
  // use the code below to calibrate accel/gyro offset values
  Serial.println("Internal sensor offsets BEFORE calibration...");
  Serial.print(CurieImu.getXAccelOffset()); 
  Serial.print("\t"); // -76
  Serial.print(CurieImu.getYAccelOffset()); 
  Serial.print("\t"); // -235
  Serial.print(CurieImu.getZAccelOffset()); 
  Serial.print("\t"); // 168
  Serial.print(CurieImu.getXGyroOffset()); 
  Serial.print("\t"); // 0
  Serial.print(CurieImu.getYGyroOffset()); 
  Serial.print("\t"); // 0
  Serial.println(CurieImu.getZGyroOffset());
  Serial.println("About to calibrate. Make sure your board is stable and upright");
  delay(1000);
  // The board must be resting in a horizontal position for 
  // the following calibration procedure to work correctly!
  Serial.print("Starting Gyroscope calibration...");
  CurieImu.autoCalibrateGyroOffset();
  Serial.println(" Done");
  Serial.print("Starting Acceleration calibration...");
  CurieImu.autoCalibrateXAccelOffset(0);
  CurieImu.autoCalibrateYAccelOffset(0);
  CurieImu.autoCalibrateZAccelOffset(1);
  Serial.println(" Done");
  Serial.println("Enabling Gyroscope/Acceleration offset compensation");
  CurieImu.setGyroOffsetEnabled(true);
  CurieImu.setAccelOffsetEnabled(true);

  set_last_read_angle_data(millis(), 0, 0, 0, 0, 0, 0);
}
 
void loop() {
  unsigned long t_now = millis();
  int ax = CurieImu.getAccelerationX();
  int ay = CurieImu.getAccelerationY();
  int az = CurieImu.getAccelerationZ();
  int gx = CurieImu.getRotationX();
  int gy = CurieImu.getRotationY();
  int gz = CurieImu.getRotationZ();


  // Convert gyro values to degrees/sec
  float gyro_x = gx/FS_SEL;
  float gyro_y = gy/FS_SEL;
  float gyro_z = gz/FS_SEL;

   // Compute the accel angles
  float accelX = rtod(atan(ay / sqrt( pow(ax, 2) + pow(az, 2))));
  float accelY = rtod(-1 * atan(ax/sqrt(pow(ay,2) + pow(az,2))));
  
  // Compute the (filtered) gyro angles
  float dt =(t_now - last_read_time)/1000.0;
  float gyroX = gyro_x*dt + last_x_angle;
  float gyroY = gyro_y*dt + last_y_angle;
  float gyroZ = gyro_z*dt + last_z_angle;  

  // Compute the drifting gyro angles
  float unfiltered_gyro_angle_x = gyro_x*dt + lGXA;
  float unfiltered_gyro_angle_y = gyro_y*dt + lGYA;
  float unfiltered_gyro_angle_z = gyro_z*dt + lGZA;
  
  // Apply the complementary filter to figure out the change in angle 
  // Alpha depends on the sampling rate...
  float alpha = 0.96;
  angle_x = alpha*gyroX + (1.0 - alpha)*accelX;
  angle_y = alpha*gyroY + (1.0 - alpha)*accelY;
  angle_z = gyroZ;  //Accelerometer doesn't give z-angle

  // Update the saved data with the latest values
  set_last_read_angle_data(t_now, angle_x, angle_y, angle_z, unfiltered_gyro_angle_x, unfiltered_gyro_angle_y, unfiltered_gyro_angle_z);

  Serial.print("Y:" );
  Serial.print(angle_y);
  Serial.print("  \t Z:" );
  Serial.print(angle_z);
  Serial.print("  \t X:" );
  Serial.println(angle_x); 

}

Hi erikyo

Just did a cursory look and looks like it should work. Here a couple of links for you to take a look at:

http://forum.arduino.cc/index.php?PHPSESSID=68on5l0ghnibm68lgmb90pdtr0&action=printpage;topic=58048.0

http://www.den-uijl.nl/electronics/gyro.html

The links take about exactly what you doing and provide some additional suggestions that you might find interesting. Especially, filtering using different type of averages on the accelerometer and gyro data since they tend to be noisy.

Have you done any testing with it yet and looked at the angles it generates? Just curious.

Mike

Merlin 513 thanks to the links, find clear information on the topic is not easy...

To filter the results I used the complementary filter, I found it easier to implement.

I tested the results and IHMO are good, but I would to know from others what they think. The only problem I encountered on some occasion is a rotation about the Z axis about 1 degree per minute.

Erik

Hi Erik

You are experiencing what they call yaw drift. One of errors that inherent with gyros over time is called yaw drift, if you do a google search for imu yaw drift you will find all kinds of info. 1 degree per minute drift is pretty good for a mems sensor from what i read - there are ways you can reduce it especially if it stays constant over a long period of time. If it stays constant you can always get rid of it by subtracting the drift rate. You should check out DIYDRONES forum. Has a lot of good information on the site.

From what I found is that pitch and roll tend to be very stable while yaw is always a problem. Some of the things that I use is my only calculation for determining the gyro offsets that is slightly modified from the what they do for quad copters. It also allows me to reset the gyros if they start drifting to much or the offsets change overtime and they do.

As a note you can change the ranges for both the accelerometer and gyro as well as adding a DLPF which i do in my efforts for the BMI160 - take a look at the BMI160.cpp file. It is pretty well documented. If you what just this info let me know and I will post.

A couple of gotchas you should check is if you rapidly rotate the sensor and put it done at rest does it the return to close to original position and the yaw drift comes back down into reasonable drift rate.

You may what to check this out:

The Gyro Sensor

Cheers
Mike

I recently discovered a lot about the IMU, and I decided to rewrite the same code though using Madgwick libraries.

This is because it seems to me much more reliable (no drifting in the z axis for example) and the code is much more compact. Also this code is the one that gave me back for now the most accurate results

/*
* The code is released under the GNU General Public License.
* Developed by www.codekraft.it 
*/

#include <BMI160.h>
#include <CurieImu.h>

#include <MadgwickAHRS.h>
Madgwick madG;

#define M_PI   3.14159265358979323846264338327950288

// Repeat part of the code every X miliseconds
#define runEvery(t) for (static long _lasttime;\
                         (uint16_t)((uint16_t)millis() - _lasttime) >= (t);\
                         _lasttime += (t))

//Set up a timer Variable
uint32_t timer;

int ax, ay, az;         // accelerometer values
int gx, gy, gz;         // gyrometer values
float yaw, pitch, roll;

int factor = 200; // variable by which to divide gyroscope values, used to control sensitivity
// note that an increased baud rate requires an increase in value of factor

const int ledPin = 13;      // activity LED pin
boolean blinkState = false; // state of the LED

void setup() {
  Serial.begin(9600); // initialize Serial communication
  while (!Serial);    // wait for the serial port to open

  CurieImu.initialize();

  CurieImu.setFullScaleGyroRange(BMI160_GYRO_RANGE_500);
  CurieImu.setGyroRate(BMI160_GYRO_RATE_100HZ);
  CurieImu.setFullScaleAccelRange(BMI160_ACCEL_RANGE_2G);


    //IMU device must be resting in a horizontal position for the following calibration procedure to work correctly!

    Serial.print("Starting Gyroscope calibration...");
    CurieImu.autoCalibrateGyroOffset();
    Serial.println(" Done");
    Serial.print("Starting Acceleration calibration...");
    CurieImu.autoCalibrateXAccelOffset(0);
    CurieImu.autoCalibrateYAccelOffset(0);
    CurieImu.autoCalibrateZAccelOffset(1);
    Serial.println(" Done");

    Serial.println("Enabling Gyroscope/Acceleration offset compensation");
    CurieImu.setGyroOffsetEnabled(true);
    CurieImu.setAccelOffsetEnabled(true);

    timer = micros();       // Initialize timer
}

void loop() {
  runEvery(10){         // Exetutes this part of the code every 10 miliseconds -> 100Hz 
    timer = micros();    // Reset the timer  
    // read raw accel/gyro measurements from device
    ax = CurieImu.getAccelerationX();
    ay = CurieImu.getAccelerationY();
    az = CurieImu.getAccelerationZ();
    gx = CurieImu.getRotationX();
    gy = CurieImu.getRotationY();
    gz = CurieImu.getRotationZ();
  
    // use function from MagdwickAHRS.h to return quaternions
    madG.updateIMU(gx / factor, gy / factor, gz / factor, ax, ay, az);
      
    yaw = madG.getYaw();
    roll = madG.getRoll();
    pitch = madG.getPitch();
  
    Serial.print("yaw:\t");
    Serial.print(yaw * 180.0 / M_PI);
    Serial.print("\t roll:\t");
    Serial.print(roll * 180.0 / M_PI);
    Serial.print("\t pitch:\t");
    Serial.println(pitch * 180.0 / M_PI);
    
    runEvery(1000){
      // blink LED to indicate activity
      blinkState = !blinkState;
      digitalWrite(ledPin, blinkState);
    }
  }
}

Hi Erik

Nice job. I just ran it and it performs well. I made one modification to your sketch so it works with a Processing 3.0 Visualizer I use.

The mod I made to your sketch was to enable to send floats in an accurate way over to processing, you can see the changes below. This is based on Fabio Verasano's work.

I will attach the processing sketch if anyone is interested as well as a screenshot.

Arduino Sketch Mods:

/*
* The code is released under the GNU General Public License.
* Developed by www.codekraft.it 
*/

#include <BMI160.h>
#include <CurieImu.h>

#include <MadgwickAHRS.h>
Madgwick madG;

#define M_PI   3.14159265358979323846264338327950288

// Repeat part of the code every X miliseconds
#define runEvery(t) for (static long _lasttime;\
                         (uint16_t)((uint16_t)millis() - _lasttime) >= (t);\
                         _lasttime += (t))

//Set up a timer Variable
uint32_t timer;

int ax, ay, az;         // accelerometer values
int gx, gy, gz;         // gyrometer values
float yaw, pitch, roll;
float ypr[3];

int factor = 200; // variable by which to divide gyroscope values, used to control sensitivity
// note that an increased baud rate requires an increase in value of factor

const int ledPin = 13;      // activity LED pin
boolean blinkState = false; // state of the LED

void setup() {
  Serial.begin(9600); // initialize Serial communication
  while (!Serial);    // wait for the serial port to open

  CurieImu.initialize();

  CurieImu.setFullScaleGyroRange(BMI160_GYRO_RANGE_500);
  CurieImu.setGyroRate(BMI160_GYRO_RATE_100HZ);
  CurieImu.setFullScaleAccelRange(BMI160_ACCEL_RANGE_2G);


    //IMU device must be resting in a horizontal position for the following calibration procedure to work correctly!

    //Serial.print("Starting Gyroscope calibration...");
    CurieImu.autoCalibrateGyroOffset();
    //Serial.println(" Done");
    //Serial.print("Starting Acceleration calibration...");
    CurieImu.autoCalibrateXAccelOffset(0);
    CurieImu.autoCalibrateYAccelOffset(0);
    CurieImu.autoCalibrateZAccelOffset(1);
    //Serial.println(" Done");

    //Serial.println("Enabling Gyroscope/Acceleration offset compensation");
    CurieImu.setGyroOffsetEnabled(true);
    CurieImu.setAccelOffsetEnabled(true);

    timer = micros();       // Initialize timer
}

void loop() {
  runEvery(10){         // Exetutes this part of the code every 10 miliseconds -> 100Hz 
    timer = micros();    // Reset the timer  
    // read raw accel/gyro measurements from device
    ax = CurieImu.getAccelerationX();
    ay = CurieImu.getAccelerationY();
    az = CurieImu.getAccelerationZ();
    gx = CurieImu.getRotationX();
    gy = CurieImu.getRotationY();
    gz = CurieImu.getRotationZ();
  
    // use function from MagdwickAHRS.h to return quaternions
    madG.updateIMU(gx / factor, gy / factor, gz / factor, ax, ay, az);
      
    ypr[0] = madG.getYaw()* 180.0 / M_PI;
    ypr[2] = madG.getRoll()* 180.0 / M_PI;
    ypr[1] = madG.getPitch()* 180.0 / M_PI;
    
    serialPrintFloatArr(ypr,3);
    Serial.print('\n');
/*    Serial.print("yaw:\t");
    Serial.print(ypr[0]);
    Serial.print("\t roll:\t");
    Serial.print(ypr[2]);
    Serial.print("\t pitch:\t");
    Serial.println(ypr[1]); 
*/    
    runEvery(1000){
      // blink LED to indicate activity
      blinkState = !blinkState;
      digitalWrite(ledPin, blinkState);
    }
  }
}


void serialPrintFloatArr(float * arr, int length) {
  for(int i=0; i<length; i++) {
    serialFloatPrint(arr[i]);
    Serial.print(",");
  }
}


void serialFloatPrint(float f) {
  byte * b = (byte *) &f;
  for(int i=0; i<4; i++) {
    
    byte b1 = (b[i] >> 4) & 0x0f;
    byte b2 = (b[i] & 0x0f);
    
    char c1 = (b1 < 10) ? ('0' + b1) : 'A' + b1 - 10;
    char c2 = (b2 < 10) ? ('0' + b2) : 'A' + b2 - 10;
    
    Serial.print(c1);
    Serial.print(c2);
  }
}


void writeArr(void * varr, uint8_t arr_length, uint8_t type_bytes) {
  byte * arr = (byte*) varr;
  for(uint8_t i=0; i<arr_length; i++) {
    writeVar(&arr[i * type_bytes], type_bytes);
  }
}


// thanks to Francesco Ferrara and the Simplo project for the following code!
void writeVar(void * val, uint8_t type_bytes) {
  byte * addr=(byte *)(val);
  for(uint8_t i=0; i<type_bytes; i++) { 
    Serial.write(addr[i]);
  }
}

FreeIMU_yaw_pitch_roll_curie_pde.pde (5.35 KB)

Hi :slight_smile:

I am doing a project where I want the outputs of the arduino as degrees. If on of the values/axis are above 10 degrees the arduino sends a bluetooth signal to an android app.

I have some problems with the pitch/z value... The pitch value seems to be normal, but when converting it to degrees (z_value) something weird happens. The calculated z_value simply get enlarge for every loop, even though the arduino is not moving. The pitch seems to stay as wanted (around zero, when the board is initialized and not moved).

The Serial Monitor looks like this after some loops:
x_value: -0.00 y_value: 0.00 z_value: -0.01..
x_value: -0.00 y_value: 0.00 z_value: -0.02..
x_value: -0.00 y_value: 0.00 z_value: -0.03..
x_value: -0.00 y_value: 0.00 z_value: -0.04..
x_value: -0.00 y_value: 0.00 z_value: -0.06..
x_value: -0.00 y_value: 0.00 z_value: -0.07..
x_value: -0.00 y_value: 0.00 z_value: -0.08..
x_value: -0.00 y_value: 0.00 z_value: -0.09..
x_value: -0.00 y_value: 0.00 z_value: -0.10..
x_value: -0.00 y_value: 0.00 z_value: -0.11..
x_value: -0.00 y_value: 0.00 z_value: -0.12..
x_value: -0.00 y_value: 0.00 z_value: -0.13..

Can some of you please help me - have been strugling with this for several days now. :confused:

UPDATE:
I found out the it is the pitch value that is "wrong". It does enlarge for every loop. Somebody knows what I am doing wrong? I start the board standing on the longest side - can this be the issue? (I have tested with the board laying flat, but seems like I got the same issue.... :frowning: )

  • Tina

My code:

#include <BMI160.h>
#include <CurieIMU.h>
#include <CurieBLE.h>
#include <MadgwickAHRS.h>

#define M_PI   3.14159265358979323846264338327950288

#define runEvery(t) for (static long _lasttime;\
                         (uint16_t)((uint16_t)millis() - _lasttime) >= (t);\
                         _lasttime += (t))

//Set up a timer Variable
uint32_t timer;

BLEPeripheral blePeripheral;
BLEService positionService("180F");
BLEService calibrateService("180E");

BLEIntCharacteristic positionChar("2A19", BLERead | BLENotify);
BLEIntCharacteristic calibrateChar("2A18", BLERead | BLEWrite | BLENotify);

Madgwick filter; // initialise Madgwick object
int ax, ay, az;
int gx, gy, gz;
int false_value = 0;
int true_value = 1;
float yaw, pitch, roll;
float x_value, y_value, z_value;
int factor = 200; // variable by which to divide gyroscope values, used to control sensitivity
// note that an increased baud rate requires an increase in value of factor

int calibrateOffsets = 1; // int to determine whether calibration takes place or not
long previousMillis = 0;

void setup() {
  // initialize Serial communication
  Serial.begin(9600);

  // initialize device
  CurieIMU.begin();

  CurieIMU.setGyroRate(BMI160_GYRO_RATE_100HZ);
  
  blePeripheral.setLocalName("Gyroskop");
  blePeripheral.setAdvertisedServiceUuid(positionService.uuid());
  blePeripheral.setAdvertisedServiceUuid(calibrateService.uuid());
  blePeripheral.addAttribute(positionService);
  blePeripheral.addAttribute(positionChar);
  blePeripheral.addAttribute(calibrateService);
  blePeripheral.addAttribute(calibrateChar);
  calibrateChar.setValue(10);
  positionChar.setValue(9);
  blePeripheral.begin();
  
  if (calibrateOffsets == 1) {
    // use the code below to calibrate accel/gyro offset values
    Serial.println("Internal sensor offsets BEFORE calibration...");
    Serial.print(CurieIMU.getAccelerometerOffset(X_AXIS)); Serial.print("\t");
    Serial.print(CurieIMU.getAccelerometerOffset(Y_AXIS)); Serial.print("\t");
    Serial.print(CurieIMU.getAccelerometerOffset(Z_AXIS)); Serial.print("\t");
    Serial.print(CurieIMU.getGyroOffset(X_AXIS)); Serial.print("\t");
    Serial.print(CurieIMU.getGyroOffset(Y_AXIS)); Serial.print("\t");
    Serial.print(CurieIMU.getGyroOffset(Z_AXIS)); Serial.print("\t");
    Serial.println("");

    Serial.print("Starting Gyroscope calibration...");
    CurieIMU.autoCalibrateGyroOffset();
    Serial.println(" Done");
    Serial.print("Starting Acceleration calibration...");
    CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
    CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 1);
    CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 0);
    Serial.println(" Done");
  }
}

void loop() {
  BLECentral central = blePeripheral.central();
  if (central) {
    Serial.println(central.address());
  }
  while (central.connected()) {
    long currentMillis = millis();
    if (currentMillis - previousMillis >= 200) {
      previousMillis = currentMillis;
        //if (calibrateChar.value() == 1) {
        //calibrate();
        //calibrateChar.setValue(0);
    //}
      checkPosition();
    }
  }
  
}

void checkPosition() {
  runEvery(10) {
    timer = micros();
  // read raw accel/gyro measurements from device
  //CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); 
    ax = CurieIMU.getAccelerationX();
    ay = CurieIMU.getAccelerationY();
    az = CurieIMU.getAccelerationZ();
    gx = CurieIMU.getRotationX();
    gy = CurieIMU.getRotationY();
    gz = CurieIMU.getRotationZ();

  // use function from MagdwickAHRS.h to return quaternions
  filter.updateIMU(gx / factor, gy / factor, gz / factor, ax, ay, az);

  // functions to find yaw roll and pitch from quaternions
  yaw = filter.getYaw();
  roll = filter.getRoll();
  pitch = filter.getPitch();

    x_value = ((yaw * 180.0 / M_PI)/2);
    y_value = ((roll * 180.0 / M_PI)/2);
    z_value = ((pitch * 180L / M_PI)/2);
    
    Serial.print("x_value:\t");
    Serial.print(x_value);
    Serial.print("\t y_value:\t");
    Serial.print(y_value);
    Serial.print("\t z_value:\t");
    Serial.print(z_value);
    Serial.println("..");

  // if z_value is higher than 10 degrees send notification signal to mobile phone 
  int false_value = 0;
  int true_value = 1;
  int result = false_value;
  if (abs(z_value) > 10) {
    result = true_value;
  }
  positionChar.setValue(result);
  }
}

CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 1);
CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 0);

I think you are telling arduino that the Y axis pointing up. Is it? Normally it would be Z, if I'm not mistaken.