Tutorial for creating multi cpp file arduino project

[General Discussion, Idea, Platformio improvement, Mass adaptation]

Dear Platformio Community.

First of all it is great a pleasure for me to finally join in this illustrious community. I have found out about Platformio project on January of this year and ever since have been using it for many of my arduino projects. I use the Atom Text editor which I found to be extremely easy and intuitive to learn and use.

A bit about myself, I am a mechanical engineer from Bangladesh with a passion for robotics and have been using arduino development platform for the past 4 years. I have adequate knowledge in Embedded C but don’t have a lot of experience with C++ or Python.

As great as platformio is, you are all aware of the fact that platformio does not compile multiple cpp files in the same way as arduino concatenates multiple ino files. To illustrate my point,

When I write codes in Arduino IDE, I can split the function bodies from the main ino files into discrete files following a sequential alphabetical naming scheme viz. A.main , B.pid, C.motor etc etc. When I hit the compile button, arduino compiles all these files and their associated header files without any error. Also the other ino files are capable of recognizing public variables, function calls written in libraries by other users installed into the Arduino IDE.

But in platformio, say I make the above files as such, main.cpp, pid.cpp, motor.cpp numerous errors are thrown including missing variable declarations from other libraries, for instance tft declaration of Adafruit GFX library is not recognized in other cpp files EXPECT in the main.cpp file.

Thus every time the project gets complex I am forced to use Arduino IDE in spite of the robustness and ease of coding platformio provides for its users.

Hence though I am not an expert in this subject matter, I strongly believe the platformio community/team should make ONE DEFINITIVE TUTORIAL to teach us less experienced programmers on how to create a multifile arduino project which will do the following

** Split the function bodies into multiple files

** The cpp files, other than main.cpp, will be able to recognize functions installed in external libraries in platformio.

** The tutorial will be comprehensive enough to teach the basics of object oriented programming and will be less ambiguity or and not be too complex to follow.

** The files should be such that if the main.cpp is imported as a project in Arduino, say new_project.ino into Arduino Ide, then that Ide will have no problem in recompiling them

This is the best paraphrasing I could come up with for the above ideas with my limited programming knowledge.

Thank you all for reading this long post and any help/ tips and tricks will be most appreciated.

With regards
Azmyin

8 Likes

Well to just give you a quick example

main.cpp:

#include <Arduino.h>
#include "myhelperfunctions.h"

void setup() {
    Serial.begin(9600);
    person me = {
        .first_name = "Dummy",
        .last_name = "Man",
        .age = add(2018, -1800),
    };
    Serial.print(me.first_name + " " + me.last_name + " is ");
    Serial.print(me.age);
    Serial.println(" years old.");
}

void loop() {
    ;
}

With another .cpp-file defining the body of the function.
myhelpferfunctions.cpp:

long add(int a, int b) {
    return a + b;
}

And a header-file (.h) with the function prototype and the struct definition. Arduino.h has to be included so you can use the variable type String. To clean up your project directory you can put the header-file in the include-folder while the source-file (.cpp) should be in the src-folder.
myhelperfunctions.h:

#include <Arduino.h>

typedef struct {
    String first_name;
    String last_name;
    long age;
} person;

long add(int a, int b);
5 Likes

Dear Mr. Thomseeen

Thank you very much for looking into my problem.

However I was proposing a comprehensive tutorial involving an actual project which would guide newcomers like me from first to last without any compilation error in between.

2 Likes

Well, create a new project in PIO and add these three files and you have a project that will compile without any errors and that shows you how it’s done. This is the “default way” of splitting C/C++ code into multiple files. For anything more detailed there should be plenty other, non PIO-specific, tutorials out there
 I don’t think it would make a lot of sense to include another such a tutorial in the PIO docs.

On the contrary adding a tutorial project as Azmyin describes would be an excellent addition to the documents section. I too have been in exactly the same situation as Azmyin and spent hours converting a multi-file Arduino project to platformIO. I am aware that the Arduino IDE removes a lot of the need for a more formal approach to make life easier for beginners. Whilst your example is very helpful it doesn’t actually show multiple .h /.cpp files. Of course I now know how to do multiple files in platformIO now but a good well written tutorial would have been a great help.
Just the thoughts of another platformIO recruit.

4 Likes

Personally I found this tutorial useful and well written: http://cse230.artifice.cc/lecture/splitting-code.html

1 Like

I totally agree. My programming discipline is questionable. I have a lot of multiple .ino projects and I am having a real struggle with getting a clean and manageable structure in Platformio.
At the moment, the Arduino IDE makes them far more readable.
For native C++ programmers, I guess the structure is second nature. For migrants from Arduino, it needs a better explanation - please?

1 Like

I still don’t really see the point of a dedicated tutorial. Read a tutorial on how to properly split code in C/C++ using headers and source files, there are a ton out there and you will quickly find one suiting your learning-style. As soon as you grasp the idea of it (it really isn’t that difficult
) you can easily use that knowledge to convert your *.inos in a proper multi-file C/C++ project for PIO.
Writing a dedicated tutorial on converting multi-ino-file-projects to proper-c/c+±projects would be like writing a tutorial on "How to create a loop() in setup()". The answer would be a tutorial on while and for-loops and not something any more specific, because why?! - I hope that analogy somehow works for the point I’m trying to make
 :smiley:

2 Likes

I think the issue is that the posters who are asking for, or would like, a dedicated tutorial envisage something along these lines. A tutorial writer taking an existing multi tab Arduino project, say 8 tabs, and then walking through all the steps to convert it to platformIO, or conventional C/C++ coding. Ideally providing some explanation as to why things are done. The example you provided is fine and helpful but it, as do many other on line tutorials, provides only a basic example.
Using multiple tabs in the Arduino IDE is an easy way to create a reasonably structured ‘sketch’. It is unfortunate that the Arduino abstracts away much of what might be termed proper coding and allows bad habits.
I have spent a considerable amount of time searching for good tutorials / information on this topic. My conclusion is that it is probably quicker to do the project again in platformIO, starting with an example such as yours and expand it gradually learning as you go.

3 Likes

I found that once I had a project working in platformIO that it was easy to navigate to, say a function, because they are all listed. However, I went back to the Arduino IDE for now because I can be get the job done much easier and quicker.
When I next try platformIO I plan to move the code from multiple tabs into one main tab, import that to platform.IO and get it running then maybe break it down into separate .ccp and .h files.

3 Likes

I don’t think you got why I think such a tutorial would be a problem. Someone could write such a tutorial and maybe it would provide enough information for some users to convert their projects.
BUT in most cases, that would cause errors as such a tutorial might not explain some more advanced things like: why a function prototype might be placed in a header and why in a source file, how to prevent double definitions, where to put preprocessor directives, how to do global variables, etc. Now you will still have to read a “normal” tutorial on the topic.
If such a ino-to-C/C++ tutorial would provide the additional, more advanced, information, then there wouldn’t be much of a difference to a “normal” tutorial and there won’t be a point to even do a specific one.
Again, in my opinion you should learn how to split C/C++ source code using one of the thousands of tutorials (real tutorials, not just a quick example like I did; still shouldn’t be to difficult or to time consuming to work through it) available for this topic. If you learned that you will have no problem to use that knowledge to convert your project. Also I think it is a nice way to quickly learn something more about the technologies behind your code
 learn how a toolchain with a preprocessor, compiler and linker turn your code into something binary for your Arduino.

I can see where you are both coming from, but I tend to agree more with @bobcroft is saying
 solely because of the “Why does it work on the Arduino IDE but doesn’t work with PlatformIO” question that gets thrown around, with the viewpoint of the poster that there must be something wrong with PlatformIO (understandable, considering their level of knowledge).

But the real question then is what to do about it, because something does need to be done
 even if it is a page/post saying do this (read this link for more detail), do that (read that link to understand why), etc, etc
 Because if that exists, it can be linked to the next time the question is inevitably asked, and anyone google searching will find it, and it might make more people make the jump to PlatformIO
 always a good thing! But certainly, yes
 it will never be ‘complete’ 
 as that would require a lot of nights of lost sleep, frustration, and result in a 300 page book on C++!! :laughing:

3 Likes

Ok, let’s give this a try

After some googling I found this. It is based on the implementation of an arduino-capable VS-extension. First cite, no offense :stuck_out_tongue::

In a nutshell:

  • If you don’t want to learn about C++ declarations and header files, stay with .INO files
  • If you have a more complex sketch with many separate source files that use classes and methods from each other, you will have more control and overview if you use “classic” C++ .CPP and .H files

Nevertheless based on the implementation choosen in this extension you could mimic the standard Arduino-IDE behaviour by edediting files like this:

  1. rename your “main”-file with the setup and loop functions to *.cpp and add #include "Arduino.h" at the very top of this file
  2. rename all other *.ino-files that are part of your project to *.h
  3. At the end of your “main”-*.cpp-file write #include "foo.h", #include "bar.h", etc. for every *.h-file you have where foo and bar are the actual names of the needed files
  4. create function prototypes in a seperate file. To do this create a new file called prototypes.h and add a prototype for each function defined in one of your other *.h-files there.
  5. include the created prototypes.h (#include "prototypes.h") at the top of your “main”-*.cpp-file right below #include "Arduino.h"

Now you are ready to go

Example:

main.ino:

void setup() {
    ;
}

void loop() {
    ;
}

library.ino:

int add(int a, int b) {
    return (a+b);
}

Those get turned into
main.cpp:

#include "Arduino.h"
#include "prototypes.h"

void setup() {
    ;
}

void loop() {
    ;
}

#include "library.h"

library.h:

int add(int a, int b) {
    return (a+b);
}

prototypes.h:

int add(int a, int b); // This is a function prototype - basically the function-head without a {}-block ended by a semicolon, you need one of those for each function you want to use

I really want to point out that this style isn’t particulary beautiful
 no, it really is bad, very bad as it could cause a lot of errors
 but it seems this is the “easy” way to convert a *.ino-project quickly.

  • Why does this work?
    #include is a so called preprocessor directive. Before you code gets translated to something your Arduino can work with (by a compiler and a linker), the preprocessor does some simple stuff (like text replacement). By telling the preprocessor to include something (everything in your source-code beginning with an # is a preprocessor directive) you basically tell him to simply copy/insert the text from the included file at that point. The file created is simply a text-file with all the included files copied together into one

  • Why do you need those strange function prototypes?
    The compiler translating your sourcecode works top-to-bottom. When you include the *.h at the end of your “main”-file the moment you might call the function add() in your loop the compiler doesn’t know there is a add()-function defined; you get an error. To prevent that you have a prototype at the top so the compiler knows that somewhere there is a definition of that function and he doesn’t care about it anymore, no matter where the actuall definition is.
void add(int a, int b); // A function prototype or declaration to "please the compiler"

void add(int a, int) { // The actual function definition with the logic
    return (a+b);
}
  • Why not just #include all those *.h-files at the top of my “main” so I don’t need prototypes?
    Well, idk, this is a bad style in eather way, I just stuck with the explenation given here. Just make sure you include other files AFTER the Arduino.h so all following files inserted “have access” to it.

In the end I still recommend:
Just do it the proper way and learn splitting into multi-file projects the “normal” C/C++ style; it’s not even that big of a step from what I just wrote
 You can not just place functions in other files but global variables and classes too, not to mention that it provides a way clearer structure for large projects, maybe even projects with multiple people.

10 Likes

Is there any movement on this? I am really new to PlatformIO and I am looking for this exact type of tutorial with no luck. I have read the links that were posted here, but it didn’t help me figure out the problem.

I find the tutorial by @Thomseeen above to be well-explained and the links (example, example) helpful. If you didn’t understand something specifically in these you should ask about that.

It might also be possible that your problem is of an entirely different nature. If you feel like that, you need to post a new topic.

2 Likes

I know it may seem that it is well explained from your perspective, but it hasn’t answered the OP’s call. Is there a tutorial for taking an existing Arduino project with or without multiple files and importing it into platformio? Something along the lines of “here is the source code”, “here is how you import it”, “here is the button you click”. All of the links provided do not show how they are implemented in PlatformIO. I am looking for the equivalent of “blink” with multiple files.

1 Like

Because it’s not PlatformIO-specific, it’s only related to C++. What PIO / VSCode gives you is a “new file” button and a source folder. I can show you a blink example with multiple files.

src\main.cpp

#include <Arduino.h>
#include "blinky.h"

void setup() {
   blinky_init();
}

void loop() {
  do_blink(500);
}

src\blinky.h

#ifndef BLINKY_H /* include guards */
#define BLINKY_H

/* blinky initialization function */
void blinky_init();

/* blinks the LED once for an length of <ms> milliseconds. */
void do_blink(int ms);

#endif /* BLINKY_H */

src\blinky.cpp

#include "blinky.h"
#include <Arduino.h> /* we need arduino functions to implement this */

void blinky_init() {
   pinMode(LED_BUILTIN, OUTPUT);
}

void do_blink(int ms) {
  digitalWrite(LED_BUILTIN, LOW);
  delay(ms);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(ms);
}
5 Likes

Yes it does. Simply add more files in the respective src and include dirs.

The way I split code in Platformio is:

create:

  • code.h
  • code.cpp

code.h:

// pragma once prevents the file to be included twice
#pragma once
// #include ...
// all other includes stays here in the .h file

// vars need to be extern
extern String myName;

// prototypes don't need named attributes in the .h file
void sayMyName(String);

code.cpp:

// the only include in the .cpp file
#include "code.h"

String myName = "John";

void sayMyName(String name){

  Serial.print(name);
}

main.cpp

#include <Arduino.h>
#include "code.h"

void setup(){
  
  sayMyName(myName);
}

void loop(){

}
3 Likes