Working on my DateTime library has slowed to a crawl. I guess because I'm out of the habit (of doing things differently) I've been developing the DateTime class in the Arduino IDE. I remember now why that's a bad idea.
The Arduino process (you probably know so well) goes:
- change the code
- upload to Arduino
- run
- find errors
- repeat (ad nauseam)
This is good and normal and healthy development for the "usual" Arduino applications that are hardware dependent and relatively simple. But for a library like DateTime a few factors completely change the equation.
Get code here: https://github.com/prawnhead/ConsoleDateTime
First, the library is complex. I'm finding I can develop an algorithm for a particular calculation, desk-check it, implement the code, write testing code, have all the tests pass, but then I'll come back later and add a different test and find the code fails. When you add the time to download code to the microcontroller and the fact that I don't have on-board debugging, it's a very slow process to write Serial.print() messages to find errors and download over and over. Add to that the work required to determine test cases and write testing code, it's all too much.
Second, DateTime doesn't require the Arduino hardware. Well it does and it doesn't. The code needs to be compatible with Arduino; ideally you can just take the DateTime.h and DateTime.cpp files and drop them into your Arduino sketch and use them. But there's no reason the whole class has to be developed either in the Arduino IDE or running on an Arduino microcontroller.
So, if using the Arduino IDE isn't the smart option, what other options are there? Well, thankfully, most C or C++ code works in Arduino. So you really only need to develop in another C or C++ environment and then bring the files over to Arduino.
There are two other development environments I've used with good success for non-Arduino purposes. One is Code::Blocks, the other is Eclipse. Now, a word of warning. If you've used Arduino but never worked with an "enterprise class" IDE, you're in for a shock. They are so much more complex that it's not worth even looking at them unless you have time and patience to learn them. The Eclipse Preferences dialogue below has hundreds and hundreds of options.
Code::Blocks is still much more complex than Arduino, but also far simpler than Eclipse. So I'll set up my DateTime library in Code::Blocks and document the process. I'm working on Windows 7 64-bit so I'm downloading the codeblocks-12.11mingw-setup.exe file. I've also uninstalled an older version of Code::Blocks so that may affect how this goes.
Installing version 12.11 with the Full: All plugins, all tools, just everything option into the default location C:\Program Files (x86)\CodeBlocks. I'm now prompted to select a default compiler. I have a long list and some are red. I'll choose GNU GCC Compiler. The GNU GCC compiler is available since I installed it with Code::Blocks - the MinGW bit.
From the Start here page I'm going to Create a new project. Note that you can create AVR projects - you can code directly for the microcontroller you're using on your Arduino, without the Arduino environment. But I've never done that and I'm not working that out now. I'm selecting a Console application. Now we get the new console application wizard. On the second screen I'm selecting C++, on the next screen I've called my application ConsoleDateTime and named it's project folder accordingly.
On the compiler options dialog, I'm leaving everything as the default with the GNU GCC Compiler selected.
OK, so now I have my project. In the Management pane I opened up the Sources folder and double-clicked the main.cpp file which opens in the editor pane. It's pre-populated with basic "Hello world!" C++ code and can be run. But the debugging toolbar is missing. Actually it's just that the window is too small and the toolbar with the green "play" button is off to the right.
After rearranging my toolbars I can now hit the "play" button to run the generic code. I'm prompted, "Do you want to build it now?" Yes. Yes I do. The Build Log pane shows the compile process and then a terminal (DOS) window opens with "Hello world!" printed in it. So now we have the equivalent of an Arduino main sketch (.skp file) with the setup() and loop() functions in it. Note that setup() and loop() are an Arduino oddity it picked up from its Processing roots. If you want to feel more at home, make your main.cpp file look like this:
#include <iostream>
using namespace std;
void setup() {
cout << "Hello world!" << endl;
}void loop() {
}int main()
{
setup();
while(1) loop();
return 0;
}
Now you can ignore the main() function (you can't get rid of it) and carry on in Arduinoland. To compile the project I hit the Rebuild button on the toolbar; the equivalent of the Verify button in Arduino. The Rebuild project dialog appears, I select Don't annoy me again and hit Yes.
You'll know that without the Arduino you have no Serial object to print to. You can see though how to print using the standard C++ cout notation. Now, the bit that I wanted to get to. Adding a "library" as Arduino calls it (a class to anyone else) has a trick to it. Follow this in Code::Blocks:
- From the File menu, select New and then Class...
- I'm only going to fill in the Class name: DateTime and click Create.
- A dialog appears asking if I want to add it to the current project: Yes.
- The Multiple selection dialog appears: OK.
I now have three tabs in the code editor. The first thing I want is to ensure that I can compile the application including the new DateTime class. Under #include <iostream> at the top of main.cpp I add #include "DateTime.h" and Rebuild. Bummer. The Build messages pane shows fatal error: DateTime.h: No such file or directory.
Now, why this step is required I'll never know. This took me a while to work out and I had a bit of a rant on the Code::Blocks forum about it when I finally worked it out. But the issue here is that Code::Blocks looks at the #include directive in the main.cpp file and then doesn't know where to find the DateTime files. You'd think it should be set up by default, wouldn't you? Apparently not. Here's the fix:
- From the Project menu, select Build options ...
- Select the Search directories tab
- Click the Add button
- The Add directory dialog pops up. You can navigate around your file system with the ellipsis [...] button, but really it's not necessary. Just type include into the Directory: box and hit OK. It will appear on the list.
- Click Add again and this time type src and click OK.
- Your Search directories list should now contain two entries: include, src. Click OK.
- Hit Rebuild. If all is well the rebuild process will compete without warnings.
I quickly make a few changes so I can ensure the DateTime class is compiling and working before I get down to coding it. And yeah, I killed off setup() and loop() as I'm comfortable without them. Here's the code I created:
main.cpp
#include <iostream>
#include "DateTime.h"using namespace std;
int main()
{
DateTime test;
return 0;
}
DateTime.h
#ifndef DATETIME_H
#define DATETIME_H
#include <iostream>class DateTime
{
public:
DateTime();
virtual ~DateTime();
protected:
private:
};#endif // DATETIME_H
DateTime.cpp
#include "DateTime.h"
using namespace std;
DateTime::DateTime()
{
cout << "DateTime constructor" << endl;
}DateTime::~DateTime()
{
cout << "DateTime destructor" << endl;
}
OK, now I'm good to go. This compiles and runs and I see the constructor and destructor messages printed. Notice I've added #include <iostream> in DateTime.h so I can use cout. I've also added using namespace std; into DateTime.cpp for the same reason.
Remember the point of all this was to develop a DateTime class for Arduino? Let's check this works. I'm going to create a new Arduino sketch, add the files DateTime.h and DateTime.cpp to the Arduino IDE and then replace them with the same files from Code::Blocks.
Here's my Arduino project TestDateTime. The files DateTime.h and DateTime.cpp have been created but are empty. Now I'll close Arduino, copy DateTime.h and DateTime.cpp from the Code::Blocks project and replace the files in the Arduino project. In Code::Blocks you'll find the DateTime.h file in the 'header' folder, the DateTime.cpp file is in the 'src' folder.
Having replaced these two files, I now open my Arduino project and hit Verify. Crunch. 'cout' was not declared in this scope. I expected this. Maybe you didn't? What's going on here is that in Code::Blocks we're using "real" C++. Arduino is a variation on C++ (from Processing) and some things need to be different. We can easily account for the differences between the two systems by using compiler directives.
Here's a simple example. I've changed my DateTime files in Code::Blocks so now they work in both Code::Blocks ("real" C++) and in Arduino (Processing). Below are four files: the main.cpp in Code::Blocks, the two DateTime files that are only edited in Code::Blocks and copied to the Arduino project replacing the files in the Arduino project, and the main sketch for the Arduino project. Here they are:
main.cpp
#ifndef ARDUINO
#include <iostream>
#endif
#include "DateTime.h"using namespace std;
int main()
{
DateTime test;
return 0;
}
DateTime.h
#ifndef DATETIME_H
#define DATETIME_H
#ifndef ARDUINO
#include <iostream>
#endifclass DateTime
{
public:
DateTime();
virtual ~DateTime();
protected:
private:
};#endif // DATETIME_H
DateTime.cpp
#include "DateTime.h"
#ifndef ARDUINO
using namespace std;
#endif
DateTime::DateTime()
{
#ifndef ARDUINO
cout << "DateTime constructor" << endl;
#else
Serial.println("DateTime constructor");
#endif
}DateTime::~DateTime()
{
#ifndef ARDUINO
cout << "DateTime constructor" << endl;
#else
Serial.println("DateTime constructor");
#endif
}
TestDateTime.ino
#include "DateTime.h"
void setup() {
Serial.begin(115200);
DateTime test;
}void loop() {
}
So now we have this crazy compiler directive #ifndef handling things for us. Basically in the Arduino IDE, at some point along the way of building your sketch for the microcontroller, a compiler variable called ARDUINO gets declared. If this ARDUINO thing does not exists (ifndef means "if not defined") the first part of the #ifndef block is included in the code to be compiled. Otherwise the #else section is included. So you can see that, with the same code file, when it is compiled by Code::Blocks the ARDUINO compiler variable doesn't exist and the code using cout is compiled. When you take the same file into the Arduino environment, somehow the ARDUINO variable is defined, and the Serial.println() code is used instead. Code::Blocks doesn't complain about Serial.print() and Arduino doesn't complain about cout because those lines of code are completely ignored.
There are neater and better ways to do this. This guy has created an ArduinoWrapper.h file that seems to make a lot of the changes in bulk. I'd like to pursue this avenue too. But that's the basics of it. I can now develop my DateTime library in Code::Blocks, without a microcontroller slowing me down, with debugging, and with the ability to "drop-in test" the code in Arduino to make absolutely sure it's going to work on my Arduino when I get it finished.
The code repository is here: https://github.com/prawnhead/ConsoleDateTime. Note this is not an Arduino project although the DateTime library should be Arduino compatible.
As always, questions and comments appreciated.
That is all.
Comments
Post a Comment