WallE, my autonomous wall-following robot had a nominal capability called “run to daylight”. The idea was to use this as a last resort when all the other ‘find a wall and follow it’ tricks failed. Unfortunately, I never could get this to work and never figured out why. Fast forward to the present, and Grok and I were trying to implement the same kind of capability into the new vision-enhanced version, to address the issue of what to do when the robot gets stuck.
Basically, the algorithm is the same as it was for the wall-following version; back up, look around and turn to the heading that provides the most freedom (‘largest depth’ in vision processing language). We (Grok and I) implemented the “back up and look around” operations using the new ‘L/Rxxx’ (where ‘xxx’ is in degrees), and then the ‘turn to the best heading’ utilizes a new ‘Txx’ command which is implemented via the already-existing ‘TurnToHdgDeg()’ function.
Unfortunately, although the ‘back up and look around’ part worked almost immediately, the ‘turn to the best heading’ part failed miserably, with the end result consistently going off in a completely different direction. I was somewhat mystified by this, as I was suffering from the (soon-to-be-debunked) belief that my MPU6050 IMU/DMP was actually delivering accurate heading measurements, and the problem must be in the ‘TurnToHdgDeg()’ function. However, data beats belief, so I decided to go back to basic ‘show me’ experiments.
To validate the MPU6050 IMU function operation, I set up an experiment where I manually rotated the robot on a printed compass rose, and measured the output of the IMU at each point, as shown in the following Excel table and resulting plot:

The above plot looks ugly, but a lot of the ugliness is due to the way IMU heading output jumps from +180 to -179; this is normal. What isn’t normal is the way the IMU reported heading diverges from the actual heading – the +180 to -179 transition occurs between 150 and 160 degrees rather than 180, and the -179 to +180 transition occurs between -140 and -150 degrees on the other side.
At first I thought this must be due to a bad IMU module, but changing out the module for a brand-new one produced the same results – so not that (further validation of my “hardware never fails – its ALWAYS the software” mantra).
So, I made another plot, this time manually changing the ‘opposite sign’ measurements back into the proper range by adding/subtracting 360 degrees. This produced the following plot:

Just looking at the ‘positive’ half from 0 to 180 deg, it is clear that the measured values are consistently higher than the actual physical headings. Looking at the first five values (before the first breakpoint in the plot) we see the differential goes from zero to about 156.8-120 = 36.8 deg over a 150 deg range, or about 36.8/150 = 0.25 deg/deg. Assuming the difference is linear, then the required multiplication factor should be 156.8X = 120 –> X = 120/156.8 = 0.769. For example, a multiplicative factor of 0.787 yielded the following plot:

Good enough for government work ;).
This factor can’t be applied directly to the IMU yaw output, as that value has already been corrected for the +/- 180 arrangement, so Grok suggested an algorithm to hold a ‘running total’ heading that uses IMU heading changes to produce a separate heading value that is corrected for the slope error and only afterwards applies the +/- 360 adjustment for the +/-180 scheme, as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
float UpdateIMUHdgValDeg() { //Purpose: Get latest yaw (heading) value from IMU //Inputs: None. This function should only be called after mpu.dmpPacketAvailable() returns TRUE //Outputs: // returns true if successful, otherwise false // IMUHdgValDeg updated on success //Plan: //Step1: check for overflow and reset the FIFO if it occurs. In this case, wait for new packet //Step2: read all available packets to get to latest data //Step3: 10/05/26: calculate delta from last raw heading value //Step4: 10/05/26: scale delta by correction factor (1/1.27) //Step5: 10/05/26: add scaled delta to last adjusted value to produce current corrected heading //Step6: update IMUHdgValDeg with latest corrected heading after adjusting for 180/-179 split //Notes: // 10/08/19 changed return type to boolean // 10/08/19 no longer need mpuIntStatus // 10/21/19 completely rewritten to use Homer's algorithm // 05/05/20 changed return type to float vs bool. // 06/13/23 added code to update gl_HdgHistoryArray for 'spinning' condx detection // 10/04/26 added Grok code to properly correct raw IMU headings //10/04/26 static makes them survive across calls for as long as the Teensy is powered static float prevRawDeg = 0; static float continuousDeg = 0; static bool haveYaw = false; int flag = GetCurrentFIFOPacket(fifoBuffer, packetSize, MAX_GETPACKET_LOOPS); //get the latest mpu packet if (flag != 0) //0 = error exit, 1 = normal exit, 2 = recovered from an overflow { // display Euler angles in degrees mpu.dmpGetQuaternion(&q, fifoBuffer); mpu.dmpGetGravity(&gravity, &q); mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); //compute the yaw value //10/03/26 added fudge factor to synch IMU output with physical reality float rawDeg = ypr[0] * 180.0f / M_PI; // still -180 to +180 if (!haveYaw) { prevRawDeg = rawDeg; continuousDeg = 0; // heading at power-up is 0 haveYaw = true; } else { float step = rawDeg - prevRawDeg; if (step > 180.0f) step -= 360.0f; // crossed the fold backward if (step < -180.0f) step += 360.0f; // crossed the fold forward continuousDeg += step * IMU_HDG_CORRECTION_FACTOR; //1.27 is the long scale from the protractor prevRawDeg = rawDeg; } IMUHdgValDeg = continuousDeg; while (IMUHdgValDeg > 180.0f) IMUHdgValDeg -= 360.0f; while (IMUHdgValDeg < -180.0f) IMUHdgValDeg += 360.0f; } //06/13/23 added to update gl_HdgHistoryArray for 'spinning' condx detection //all array entries bumped down one, with most recent value at i = HEADING_HISTORY_ARRAY_SIZE-1 for (uint16_t i = 1; i < HEADING_HISTORY_ARRAY_SIZE; i++) { gl_HdgHistoryArray[i - 1] = gl_HdgHistoryArray[i]; //gl_pSerPort->printf("gl_HdgHistoryArray[%d] = %2.2f\n", i, gl_HdgHistoryArray[i]); } gl_HdgHistoryArray[HEADING_HISTORY_ARRAY_SIZE - 1] = IMUHdgValDeg; return IMUHdgValDeg;//05/05/20 now returns updated value for use convenience } |























































