DryRun:
That's very useful! Thanks a lot for making this library.
I have some questions:
In your example code:
mpu.GetQuaternion(&q, quat);
mpu.GetGravity(&gravity, &q);
mpu.GetYawPitchRoll(ypr, &q, &gravity);
1. Is it necessary to have all 3 lines, if i only need the ypr values? That is, can't i just have the 3rd line only, instead of wasting CPU time on finding the quaternion and gravity?
The first function performs the math necessary to use the quaternion values
q -> w = (float)(qI[0] >> 16) / 16384.0f;
q -> x = (float)(qI[1] >> 16) / 16384.0f;
q -> y = (float)(qI[2] >> 16) / 16384.0f;
q -> z = (float)(qI[3] >> 16) / 16384.0f;
basically turning an integer into a floating-point value
as gravity is needed to get standard gravity-based yaw, pitch and roll values instead of euler values we must calculate this.
Try eular values to avoid gravity calculations
// Eular print code
#define printfloatx(Name,Variable,Spaces,Precision,EndTxt) print(Name); {char S[(Spaces + Precision + 3)];Serial.print(F(" ")); Serial.print(dtostrf((float)Variable,Spaces,Precision ,S));}Serial.print(EndTxt);//Name,Variable,Spaces,Precision,EndTxt
Quaternion q;
float euler[3]; // [psi, theta, phi] Euler angle container
mpu.GetQuaternion(&q, quat);
mpu.GetEuler(euler, &q);
Serial.printfloatx(F("euler ") , euler[0] * 180 / M_PI, 9, 4, F(", ")); //printfloatx is a Helper Macro that works with Serial.print that I created (See #define above)
Serial.printfloatx(F("") , euler[1] * 180 / M_PI, 9, 4, F(", "));
Serial.printfloatx(F("") , euler[2] * 180 / M_PI, 9, 4, F("\n"));
Quaternion and VectorFloat are deffinitionsthat allow us to handle the calculations easily
Quaternion q;
VectorFloat gravity;
The "q" variable is an instance of Quaternion .
Similarly the "gravity" variable is more than just a single value. the gravity is an instance of VectorFloat the VectorFloat is defined as follows
class VectorFloat {
public:
float x;
float y;
float z;
VectorFloat() {
x = 0;
y = 0;
z = 0;
}
VectorFloat(float nx, float ny, float nz) {
x = nx;
y = ny;
z = nz;
}
float getMagnitude() {
return sqrt(x*x + y*y + z*z);
}
VectorFloat :: normalize() {
float m = getMagnitude();
x /= m;
y /= m;
z /= m;
return this;
}
VectorFloat getNormalized() {
VectorFloat r(x, y, z);
r.normalize();
return r;
}
VectorFloat :: rotate(Quaternion *q) {
Quaternion p(0, x, y, z);
// quaternion multiplication: q * p, stored back in p
p = q -> getProduct(p);
// quaternion multiplication: p * conj(q), stored back in p
p = p.getProduct(q -> getConjugate());
// p quaternion is now [0, x', y', z']
x = p.x;
y = p.y;
z = p.z;
return this;
}
VectorFloat getRotated(Quaternion *q) {
VectorFloat r(x, y, z);
r.rotate(q);
return r;
}
};
This handles gravity in any vector as you rotate the MPU to other angles.
- If i use only the 3rd line, then will the data still be fused, meaning that to get the most accurate readings from the gyro, its data needs to be fused with the accelerometer, so will this still work or will i get drift in the gyro values? That's because there are the 3 arguments and the two others are pointers &q and &gravity.
The DMP "fusion" happens in the MPU6050 over a 10ms duration. Many readings are merged to provide you with a clean shift in orientation. This orientation is returned as four Quaternion values and 3 Accelerometer values and 3 gyro values While the gyro and accelerometer seem simple they are actually many readings over the 10ms period merged together. Accurate use of gyro and accelerometer readings would require you to use every 10ms instance luckily we have them already used in the Quaternion calculations so we can ignore them for the most part.
- Could you please add an example that showcases in detail how to use this library without needing interrupt pin connected to the IMU? I'm not sure how to implement the need to retrieve data at exactly 10ms and is the mpu.OverflowProtection(); necessary in this case?
If you are diligent in preventing long delays in your code, the mpu.OverflowProtection(); is not necessary.
mpu.OverflowProtection(); basically empties the FIFO buffer down to a reasonable level leaving about 10 reading in it.
If you overflow your next attempt to get any values will be rejected and nothing will occur.
The overflow protection has 2 parts
The delay(); part:
#define ENABLE_MPU_OVERFLOW_PROTECTION(...) void yield(void){mpu.OverflowProtection();} // yield is called from within the delay() function
which is basically a simple function for you to easily use
void yield(void){
mpu.OverflowProtection();
} // yield is called from within the delay() function
When you call delay(); the function yield(); is called normally noting happens because we don't know about it.
void delay( uint32_t ms )
{
if ( ms == 0 )
{
return ;
}
uint32_t start = _ulTickCount ;
do
{
yield() ; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
} while ( _ulTickCount - start <= (ms-1) ) ;
}
And, since we don't care what is happening during delay lets, do something important like preventing overflow from occurring.
now let's look at the OverflowProtection();
void Simple_MPU6050::OverflowProtection(void) {
uint32_t Timestamp ;
Timestamp = millis();
static uint32_t spamtimer;
if ((Timestamp - spamtimer) >= 30) { // Blink without delay timer 30ms or 3 readings are generated in the FIFO Buffer
spamtimer = Timestamp;
uint16_t fifo_count;
int8_t Packets;
FIFO_COUNTH_READ_FIFO_CNT(&fifo_count);
Packets = (fifo_count / packet_length) - (_maxPackets - 10); // Leaves room for more readings
if (Packets <= 0)return; // nothing is happening get out.
uint8_t trash[packet_length + 1] ;
while (0 < Packets--) {
FIFO_READ(packet_length, trash); // Trash all older packets down to about 10 readings.
}
}
}
Added a few notes in the code.
So there you have it.
Because we are using the MPU6050's processor to do all the Calculations leaving us with 10ms to do anything we want before the next reading is ready if we don't need the data we can use delay() and the most recent reading is preserved allowing us to instantly get it without flushing the FIFO buffer and waiting up to 10ms for a good reading.
Z