Arduino IDE and PlatformIO – Part 1
How to structure a project to be compilable by both
Table of Contents
Introduction
The goal of this post is to provide hints on how to structure an Arduino project to be able to compile successfully on both Arduino IDE and PlatformIO (through VSCode). Each compiler / IDE brings its own limitations causing little headaches to developers that want to apply simple software development practices on an Arduino project. That is, code separation in folders, third-party library management, common use of source code by multiple example projects etc.
The project and the initial structure
The project on which we are going to work on is a stripped down example of a device that reads a value from a sensor, gets internet connectivity and uploads the measurement. For better versatility, the project will support WiFi and NBIoT connectivity. This leads us to create two example sketches, one for each connectivity type. The initial set of header and sketch files are:
senseAndUpload/ ├── nbiot.h ├── wifi.h ├── sensor.h ├── exampleWifi.ino └── exampleNBIoT.ino
- nbiot.h: Supports function “NBIoT::connect”, “NBIoT::upload”
- wifi.h: Supports function “WiFi::connect”, “WiFi::upload”
- sensor.h: Supports function “Sensor::readSensorLevel”
- exampleWifi.ino: example sketch
#include "sensor.h" #include "wifi.h" void setup() {} void loop() { int val = Sensor::readSensorLevel(); WiFi::connect(); WiFi::upload; }
- exampleNBIoT.ino: example sketch
#include "sensor.h" #include "nbiot.h" void setup() {} void loop() { int val = Sensor::readSensorLevel(); NBIoT::connect(); NBIoT::upload; }
Setting up folder structure based on Arduino Documentation
Each folder must contain at most 1 .ino file
senseAndUpload/ ├── nbiot.h ├── wifi.h ├── sensor.h ├── exampleWifi/ │ └── exampleWifi.ino └── exampleNBIoT/ └── exampleNBIoT.ino
Arduino IDE does not handle relative paths to previous folders
senseAndUpload/ ├── examples/ | ├── exampleWifi/ | │ └── exampleWifi.ino | └── exampleNBIoT/ | └── exampleNBIoT.ino └── src/ ├── nbiot.h ├── wifi.h └── sensor.h
PlatformIO recognizes library headers only in subfolders
- senseAndUpload/examples/exampleWifi/platformio.ini
[env] lib_extra_dirs = ../../src [platformio] src_dir = . [env:mkrwifi1010] platform = atmelsam board = mkrwifi1010 framework = arduino
- senseAndUpload/examples/exampleNBIoT/platformio.ini
[env] lib_extra_dirs = ../../src [platformio] src_dir = . [env:mkrnb1500] platform = atmelsam board = mkrnb1500 framework = arduinoThese PlatformIO configuration files instruct that the “src” folder must be considered as a library folder (lets leave the board configuration for later) and this leads us to our 3rd issue. PlatformIO can only detect header files that reside in subfolders at the defined paths of “lib_extra_dirs” tag. This means that our example projects can not “see” our header files so lets comply to this new requirement. Create two new folders, “src/device” and “src/networking” and move the header files:
senseAndUpload/ ├── examples/ | ├── exampleWifi/ | │ └── exampleWifi.ino | └── exampleNBIoT/ | └── exampleNBIoT.ino └── src/ ├── device/ | └── sensor.h └── networking/ ├── nbiot.h └── wifi.h
Arduino IDE recognizes library headers only at the base folder of each library
Since Arduino IDE detects only header files at the “src” folder, lets create one helper header file that will be including all the headers needed from the subfolders.
- senseAndUpload/src/customLibs.h
#include <device/sensor.h> #include <networking/nbiot.h> #include <networking/wifi.h>Having two different behaviors from our compilers that can not be satisfied by one solution, this means that our code needs to be conditional. Using the “exampleWifi.ino” code as reference, here is an approach on how to handle this:
- exampleWifi.ino:
//#define ENABLE_ARDUINO_IDE_SUPPORT #ifdef ENABLE_ARDUINO_IDE_SUPPORT #include #else #include #include #endif void setup() {} void loop() { int val = Sensor::readSensorLevel(); WiFi::connect(); WiFi::upload; }When building through Arduino IDE, the ENABLE_ARDUINO_IDE_SUPPORT has to be uncommented and the “helper” header file will be included, handling all includes of the needed header files in subfolders. On the other hand, when building through PlatformIO, the macro remains commented and the header files are directly included. Sure, it is not the prettiest solution though it is viable.
Summary
The correct design of a maintainable and expandable project structure in the Arduino world is an ongoing trial & error based on the capabilities and limitations of the underlying tools. This task gets even trickier when combining the limitations of more than one tools, in our case Arduino IDE and PlatformIO.
Until now we have seen that:
- Generic
- Each folder must contain at most 1 .ino file
- Arduino IDE only
- inherently identifies “src” and “examples” folders.
- does not handle relative paths to previous folders
- recognizes library headers only at the base folder of each library
- PlatformIO only
- recognizes library headers only in subfolders
In the next post, we explain how cpp files affect the source code and/or the folder structure and will conclude with a fully working template code that will contain all the recommendations of this post series.