BMI160 IMU - Read full 360 angle on axis

Hi All,
I have a problem managing the absolute angles on a BMI160 IMU sensor , the sensor is connected in i2C to an Atmega2560.
Phisically the board is mounted on an arm that can rotate 360° around IMU X axis and tilt around Y axeis +/- 80°.
The problem I have is that "Roll" angle ( arm rotation) start from 0 and go to +/-90° depending on rotation side and then start decreasing again to 0 , for example a real 100° return 80°, at the same time Pitch angle jump from 0 to something close to 180° when roll pass 90°.
I tried this code that use different type of filters but all the outputs have the same behaviour exept the gyro_roll that measure a full 360 angle but is obviously subject to a big drift overtime.

Someone that face the same problem can help me ?

Thanks in advanced

Flavio

#include <Wire.h>
#include <math.h>
#include <BMI160Gen.h>
#include <Kalman.h>
#include <MadgwickAHRS.h>

#define PRINT_PROCESSING

#define BAUDRATE  115200
#define SENSE_RATE   100
#define GYRO_RANGE   250
#define ACCL_RANGE     2

#define deg_to_rad(a) (a/180*M_PI)
#define rad_to_deg(a) (a/M_PI*180)

Kalman kalmanRoll;
Kalman kalmanPitch;
Madgwick madgwick;

float convertRawGyro(int gRaw) {
 // ex) if the range is +/-500 deg/s: +/-32768/500 = +/-65.536 LSB/(deg/s)
 float lsb_omega = float(0x7FFF) / GYRO_RANGE;
 return gRaw / lsb_omega;  // deg/sec
}

float convertRawAccel(int aRaw) {
 // ex) if the range is +/-2g ; +/-32768/2 = +/-16384 LSB/g
 float lsb_g = float(0x7FFF) / ACCL_RANGE;
 return aRaw / lsb_g;
}

static float gyro_roll = 0, gyro_pitch = 0, gyro_yaw = 0;
static float comp_roll = 0, comp_pitch = 0;
static unsigned long last_mills = 0; 
void print_roll_pitch() 
{
    
 // read raw accl measurements from device
 int rawXAcc, rawYAcc, rawZAcc; // x, y, z
 BMI160.readAccelerometer(rawXAcc, rawYAcc, rawZAcc);
 float accX = convertRawAccel(rawXAcc);
 float accY = convertRawAccel(rawYAcc);
 float accZ = convertRawAccel(rawZAcc);

 float rad_a_roll = atan2(accY, accZ);
 float rad_a_pitch = atan2(-accX, sqrt(accY*accY + accZ*accZ));
 float accl_roll = rad_to_deg(rad_a_roll);
 float accl_pitch = rad_to_deg(rad_a_pitch);

 // read raw gyro measurements from device
 int rawRoll, rawPitch, rawYaw; // roll, pitch, yaw
 BMI160.readGyro(rawRoll, rawPitch, rawYaw);
 float omega_roll  = convertRawGyro(rawRoll);
 float omega_pitch = convertRawGyro(rawPitch);
 float omega_yaw   = convertRawGyro(rawYaw);
 
 unsigned long cur_mills = micros();
 unsigned long duration = cur_mills - last_mills;
 last_mills = cur_mills;
 double dt = duration / 1000000.0; // us->s  
 if (dt > 0.1) return;

 // Gyro data
 gyro_roll  += omega_roll  * dt; // (ms->s) omega x time = degree
 gyro_pitch += omega_pitch * dt;
 gyro_yaw   += omega_yaw   * dt;
 
 // Complementary filter data
 comp_roll  = 0.93 * (comp_roll  + omega_roll  * dt) + 0.07 * accl_roll; 
 comp_pitch = 0.93 * (comp_pitch + omega_pitch * dt) + 0.07 * accl_pitch; 

 // Kalman filter data
 float kalm_roll  = kalmanRoll.getAngle(accl_roll, omega_roll, dt);
 float kalm_pitch = kalmanPitch.getAngle(accl_pitch, omega_pitch, dt); 

 // Madgwick filter data
 madgwick.updateIMU2(omega_roll, omega_pitch, omega_yaw, accX, accY, accZ, dt);
 // madgwick.updateIMU(omega_roll, omega_pitch, omega_yaw, accX, accY, accZ);
 float madw_roll  = madgwick.getRoll();
 float madw_pitch = madgwick.getPitch();
 float madw_yaw   = madgwick.getYaw();

#ifdef PRINT_PROCESSING
 static int n = 0;
 if (n != 50) {
   ++n; return; 
 }
 n = 0;

 Serial.print(accl_roll);
 Serial.print(",");
 Serial.print(accl_pitch);
 Serial.print(",");
 Serial.print(madw_yaw);
 Serial.print(",");
 Serial.print(gyro_roll);
 Serial.print(",");
 Serial.print(gyro_pitch);
 Serial.print(",");
 Serial.print(madw_yaw);
 Serial.print(",");
 Serial.print(comp_roll);
 Serial.print(",");
 Serial.print(comp_pitch);
 Serial.print(",");
 Serial.print(madw_yaw);
 Serial.print(",");
 Serial.print(kalm_roll);
 Serial.print(",");
 Serial.print(kalm_pitch);
 Serial.print(",");
 Serial.print(madw_yaw);
 Serial.print(",");
 Serial.print(madw_roll);
 Serial.print(",");
 Serial.print(madw_pitch);
 Serial.print(",");
 Serial.print(madw_yaw);
 Serial.print(",");
#endif
 Serial.println();
}


void setup() {
 Serial.begin(115200);
 BMI160.begin(BMI160GenClass::I2C_MODE);
 madgwick.begin(SENSE_RATE);

 BMI160.setGyroRate(SENSE_RATE);
 BMI160.setAccelerometerRate(SENSE_RATE);
 BMI160.setGyroRange(GYRO_RANGE);
 BMI160.setAccelerometerRange(ACCL_RANGE);

 BMI160.autoCalibrateGyroOffset();
 BMI160.autoCalibrateAccelerometerOffset(X_AXIS, 0);
 BMI160.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
 BMI160.autoCalibrateAccelerometerOffset(Z_AXIS, 1);

 delay(100);

 /* Set kalman and gyro starting angle */
 int rawXAcc, rawYAcc, rawZAcc;
 BMI160.readAccelerometer(rawXAcc, rawYAcc, rawZAcc);
 float accX = convertRawAccel(rawXAcc);
 float accY = convertRawAccel(rawYAcc);
 float accZ = convertRawAccel(rawZAcc);
 
 float roll  = rad_to_deg(atan(accY / accZ));
 float pitch = rad_to_deg(atan(-accX / sqrt(accY * accY + accZ * accZ)));

 // Set starting angle
 kalmanRoll.setAngle(roll); 
 kalmanPitch.setAngle(pitch);
 gyro_roll  = comp_roll  = roll;
 gyro_pitch = gyro_pitch = pitch;
}

void loop() {
 print_roll_pitch();
}

It is impossible to avoid problems with pitch and roll angles when you get to 90 degrees. This is called gimbal lock, and is built in to the angular coordinate system.

The solution that everyone uses these days is to avoid these angular conversion operations entirely, and use quaternions to represent orientations.

Thanks jremington for the replay, I understand gimbal lock problem but I'm not sure how to approach quaternions , the final goal is to have two "gauge" that represent absolute roll and pitch angle of the arm relative to earth, something like drone GUI view. Any suggestion on how to achive it ?

Start by defining "absolute roll and pitch". The angles describe transformations from one frame of reference to another.

A drone view would be the Earth, viewed from the drone frame of reference. I don't see how that helps with the discussion.

Sorry my mistake describing the frame of reference, for drone GUI i mean something like artificial horizon in the control software where zero roll and zero pitch are both referred to earth gravity vector, rotation around X axis describe roll and rotation around Y axis describe pitch.

Again, you run into the gimbal lock problem, when either roll or pitch are 90 degrees, because the other axis is aligned with the vertical axis Z.

I suggest that to get on with this project, drop the notion of angular coordinate systems entirely, and describe what you actually want to do with the robot arm.

The arm is part of a robot that travel inside a pipe, the arm can rotate at the "shoulder" 360° continuosly , and the arm can be lifted from the shoulder +80° and -20° , the IMU is mounted on the ARM top side, combining this motion I can "touch" the pipe internal surface at any of the 360 position. The purpose of the IMU is know at wich rotation angle and lift angle is the arm in a given moment, I need this data to visualize it and for future automation, like "move the arm horizontally": arm direction along (pitch 0) the pipe and rotation on horizon (roll 0). I attach a simple scheme of visualization result.

The accelerometer part of the IMU reports the direction of gravity in its own frame of reference, which depends on the end effector (arm) position and orientation. The arm position and orientation in turn depends on its mounting arrangement.

The arm position/orientation problem is straightforward and has been worked out in many different ways for the general case of robot arms. Your case is simple, with two angular degrees of freedom, and presumably one or more fixed lengths.

From the pictures you have posted, I can't visualize the arm construction.

Search terms are "forward robot arm kinematics" or perhaps the inverse kinematic problem. Here are a couple of overviews:
http://faculty.salina.k-state.edu/tim/robotics_sg/Arm_robots/ArmKinematics.html

https://motion.cs.illinois.edu/RoboticSystems/Kinematics.html

You haven't said whether the pipe is always horizontal, and if not, that adds another degree of freedom.

The pipe could be consider orizzontally and the arm rotation center can be considered coincident to pipe section center, the arm construction is almost identical to the following pictures (the "wrist" movement is not part of the measure needed)
imu

I tried to understand the link u provide and the concept of "gimbal lock" but probably my math/geometry background is not enought to understand why as my Y axis rotation never reach 90° (+80/-20) and the only rotation over 90 is around X axis I will face gimbal lock problem with euler angles and how I can user quaternion directly to graphycally represent the arm position :frowning:

If the two rotation axes are controlled by stepper motors, you can control and calculate the end effector position very accurately.

The IMU can report only two angles, and won't be nearly as accurate, so what is its purpose?

It could used on the robot body, to report whether the robot is level.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.