Posted 02 February 2019
After a vacation from my WallE2 autonomous wall-following robot code to recover from rotator cuff surgery (and creating/testing my new digital tensionometer), a week or so ago I decided it was time to get back into WallE2 mode. At the time I thought this would be a piece of cake, as I had left WallE2 in pretty good shape, code-wise back in September 2018 (at least that’s what I thought!).
Instead, For the past couple of weeks I have been enduring what can only be described as “#include file hell”. The first time I tried to compile my main program, I saw a couple of warnings in an i2cDev related library. The code still compiled, but I take all warnings very seriously, and these weren’t there the last time I compiled the code.
So, I started trying to figure out what, if anything, had changed, and how to go about fixing whatever problem had caused the warnings to pop up. Unfortunately, everything I did made things worse – and worse – and worse. Nothing made sense. Jeff Rowberg, the creator of the fine i2cDev collection of i2c device drivers was mystified, as was Tim Leek, the main guy on the Visual Micro forum. Arggghhhh!
So, Jeff Rowberg suggested I try compiling the project in the Arduino IDE rather than in VS2017/Visual Micro, to eliminate any issues caused by that environment. Up until this point I had actually never used the Arduino IDE, much preferring the more helpful and feature rich VS2017/Visual Micro IDE. But, what the heck – how hard could it be?
Well, the answer was – DAMNED HARD! Using the Arduino IDE after the VS/VM environment was like moving backwards in time from the 21st century to the stone age – having to rub sticks together to make a compile happen! Moreover, the Arduino IDE created even more (and different) problems than I had experienced so far, meaning that I not only wasn’t draining the swamp, but the alligators were getting even more numerous! Some of the ‘features’ of the Arduino IDE:
- When the IDE is first launched, it comes up with the last .INO file loaded. If you want a different file, it launches a new IDE instance with file->open; soon your desktop is littered with IDE instances.
- When it tries to find a library based on a ‘#include <libraryName.h>’ line, it can’t handle [library name]-master as is common with libraries downloaded from GitHub
- It requires exact name matching, including capitalization. So ‘#include <libraryName>’ will not match with the ‘Arduino\Libraries\Libraryname’ folder.
- Editing is clunky, and there’s no such thing as Intellisense.
After running around in circles with my hair on fire for the last week or so, making my wife miserable with my griping and inundating Tim Leek and Jeff Rowberg with ever-more-desperate cries for help, I finally decided that I was simply going to have to start over from scratch with my robot program (some 3000+ lines of code in the main program and over a dozen custom libraries), and just build it up piece by piece until everything works again – groan. It’s not like I don’t have backups and wasn’t using revision control – I do and I was; it’s just that the programs that compiled cleanly back in September are generating warnings and errors now, and everything I do makes the problem worse!
Since the original problem seemed to be related to the library that runs the DFRobots MPU6050 module, I decided to start there. After struggling up the learning curve on the Arduino IDE, I also decided I would make sure that each program step would compile cleanly in both the VS/VM and Arduino IDE’s before proceeding to the next step. I reasoned that since the Arduino IDE is much pickier about library locations and names, I could use it as sort of an editorial check on VS/VM; if it works in the Arduino IDE, the VS/VM setup will have no problem.
For the DFRobots MPU6050 6DOF IMU module, I started with Jeff Rowberg’s ‘MPU6050_DMP6.INO’ example program buried way down in the ‘i2cdevlib-master\Arduino\MPU6050\examples\MPU6050_DMP6’. According to the i2cDev ReadMe, I could either put the entire i2cDev-master folder in Arduino\Libraries and let the linker figure it out, or just put the required files in the project (solution folder for VS/VM, ‘sketchbook folder’ for Arduino IDE) folder. I elected for the latter (local files) option, as I was at least a little suspicious that part or all of my original problem was caused by the compiler/linker loading from the wrong library folder in the i2dDev folder tree. In addition, I completely removed the i2cDev folder tree from my PC and re-downloaded it from GitHub, placing it in a completely unrelated folder so that neither environment could possibly find it. Then I copied the required header/.cpp files from the hidden i2cDev folder tree into the project folder. In VS/VM I created a project called ‘MPU6050_DMP6_Example’ and copied the Arduino versions of I2Cdev.cpp/.h, MPU6050.cpp/.h, MPU6050_6Axis_Motion.h, and helper_3dmath.h into it. Then I started working to get this project to compile both in the VS/VM & Arduino IDE’s.
I’ve now gotten it to compile and link in the Arduino IDE (albeit with the same warnings I started with just before I went down the rabbit hole into header file hell). However, I can’t get it to compile in VS/VM – it blows a whole bunch of errors of the form – apparently one error for each MP6050 function)
|
cc51VoHg.ltrans0.ltrans.o*: In function main (.text.startup+0x1e4): undefined reference to MPU6050::initialize() (.text.startup+0x234): undefined reference to MPU6050::testConnection() (.text.startup+0x294): undefined reference to MPU6050::reset() (.text.startup+0x2a8): undefined reference to MPU6050::setSleepEnabled(bool) |
These errors proved impossible to correct, and nobody on either the Arduino or Visual Micro forums seemed to be able to help. Finally in desperation I uninstalled and re-installed the Visual Micro extension to VS2017, and that didn’t solve the problem either – exactly the same behavior.
So, last night I uninstalled VS2017 entirely from my system, and deleted the entire contents of the temp folder being used for temporary compile files. On my system this was C:\Users\Frank\AppData\Local\Temp.
03 February 2019
This morning I reinstalled VS2017CE and, using the Tools & Extensions menu, reinstalled Visual Micro. I left everything pretty much at the default settings (including the IDE selection and IDE location entries). The only thing non-standard with the setup was the inclusion of ‘https://raw.githubusercontent.com/sparkfun/Arduino_Boards/master/IDE_Board_Manager/package_sparkfun_index.json’ in the ‘Optional additional boards manager urls’ field. This was apparently left over from my previous installation. I’m not worried about this particular setting, but it does indicate that not everything about the previous incarnation of Visual Micro was actually removed from my system.
After installing VS/VM, I ran through a few of my simpler projects, and so far they have all compiled w/o problems (or had understandable and easily fixable problems). I also compiled each program in the Arduino IDE, taking care to follow the Arduino IDE restrictions (no “-master” in library folder names, exact capitalization, etc).
- BlinkTest.ino – very simple, no #includes
- ClassTest.ino – Very simple class construction project – no #includes
- DigitalScale.ino – Several #includes, including the HX-711 load cell library
- StepperSpeedCtrl – uses #include <Stepper.h>
- AdaFruit_BTLE_UART_Test.ino – uses 7 different library #includes
- Arduino_IMU6050_Test4.ino – uses i2cDev and MPU6050 libraries, along with SBWire, elapsedMillis, and PrintEx. this compiled OK, but with the same warnings (overrun & ‘one definition rule’) as when I first started this odyssey. Fortunately, that’s all that happened – I didn’t get the ‘(.text.startup+0x1e4): undefined reference to MPU6050::initialize()’ error – yay! This program also compiles in the Arduino IDE, with the same exact warnings. So, it appears I may be back where I started on this odyssey, with a program using the MPU6050 libraries that compiles OK but with one understandable/fixable warning (the overrun warning) and one mystery warning (the ‘one definition rule’ warning)
- MPU6050_DMP6_Example: spoke too soon! This program blows the same ‘(.text.startup+0x1e4): undefined reference to MPU6050::initialize()’ errors as before in VM, but compiles fine (albeit with the same two warnings as always) in the Arduino IDE.
- Arduino_IMU6050_Test4: This is a program I created some time ago, and I found that it compiles/links fine (stil with the overrun/ODR warnings), both VS/VM and Arduino IDE
In desperation, I decide to create a completely new Arduino project in VS/VM, copy the ‘known-good’ code from Arduino_IMU6050_Test4 into it, and then start hacking it down to the point where it fails. Surprise surprise, when I did this, the new project (UnDefTest2) failed right away in VS/VM, blowing LOTS of linker errors! Moreover, it compiled/linked fine in Arduino IDE – how could this be? There MUST be something different about the VS/VM environment between Arduino_IMU6050_Test4 and UnDefTest4 – but what? After putting the two projects up side-by-side (this is where a dual monitor setup comes in REAL handy), I finally twigged to the difference; in the ‘working’ version, the local header/cpp files had been ‘added’ to the project’s ‘Header Files’ and ‘Source Files’ folders via the Solution Explorer (right-click on the folder icon, select ‘Add Existing…’, select the desired files, click OK). As soon as I added the relevant files to the UnDefTest4 project, it compiled/linked fine – YAY!!
I could not believe what I was seeing! For some reason, VS/VM refused to process header/cpp files in the same folder as the .INO file, even though I had carefully checked the ‘Local Files Override Library Files’ option in the ‘Vmicro->Compiler’ menu. At the same time, the Arduino IDE always searches the local folder before anything else, so simply placing the relevant files in the local folder does the trick. The fact that Visual Micro requires an additional (and non-intuitive) step for this boggles the mind.
04 February 2019
OK, when I started all this foolishness I was trying to find out (and fix) whatever was causing the ‘One Definition Rule’ (ODR) violation warning I was getting on all my programs that used the MPU6050. I really really hate warnings, and I was determined to get to the bottom of this, and I finally did!
The ‘one definition rule’ (ODR) warning is caused when the compiler/linker sees code that can produce two different definitions for the same object. If that can happen, EVER, then an ODR violation warning is issued. Believe it or not, that is exactly what happens when the compiler processes MPU6050.H – it sees that there are some conditions for which two different descriptions of the MPU6050 class could exist – and says “no no”. The relevant portion of the class definition is shown below:
|
private: uint8_t devAddr; uint8_t buffer[14]; #if defined(MPU6050_INCLUDE_DMP_MOTIONAPPS20) or defined(MPU6050_INCLUDE_DMP_MOTIONAPPS41) uint8_t *dmpPacketBuffer; uint16_t dmpPacketSize; #endif |
When the compiler sees these lines, it says to itself; “Hmm, the way this is written, it is theoretically possible for there to exist two different versions of ‘Class MPU6050’, one with just two private member variables (devAddr & buffer) and one with four (with the addition of dmpPacketBuffer & dmpPacketSize), and this is a strict no-no; I’m going to whack that programmer across the head with an ODR violation!”
If the #ifdefined and #endif lines are commented out – the ODR warning goes away
Now, I suspect nobody has ever had a problem with this issue, as it would be very unlikely to have a project where BOTH versions of MPU6050 are in play, but of course the compiler doesn’t see it this way.
On a slightly different, but related subject, the OTHER warning was due to a potential integer overrun in the dmpGetGravity() function, as shown below:
|
uint8_t MPU6050::dmpGetGravity(int16_t *data, const uint8_t* packet) { /* +1g corresponds to +8192, sensitivity is 2g. */ int16_t qI[4]; uint8_t status = dmpGetQuaternion(qI, packet); data[0] = ((int32_t)qI[1] * qI[3] - (int32_t)qI[0] * qI[2]) / 16384; data[1] = ((int32_t)qI[0] * qI[1] + (int32_t)qI[2] * qI[3]) / 16384; data[2] = ((int32_t)qI[0] * qI[0] - (int32_t)qI[1] * qI[1] - (int32_t)qI[2] * qI[2] + (int32_t)qI[3] * qI[3]) / (2 * 16384); return status; } |
if the last line of the above calculation is changed to (note the addition of ‘UL’)
– (int32_t)qI[2] * qI[2] + (int32_t)qI[3] * qI[3]) / (2 * 16384UL);
then this warning goes away as well.
Mission accomplished! I now have MPU6050 code that compiles without errors (or warnings!!) in both the VS/VM and Arduino IDE environments. Along the way I learned more than I ever wanted to know about ‘One Definition Rule’ violations and the innards of both the VS/VM environment and the Arduino IDE.
to paraphrase a quote attributed to Abraham of Lincoln:
I feel like the man who was tarred and feathered and ridden out of town on a rail. To the man who asked him how he liked it, he said: “If it wasn’t for the honor of the thing, I’d rather walk.”
Stay tuned,
Frank