Tutorial for creating multi cpp file arduino project

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

Hi, It’s really nice but… How we can use it when we try to work with external libraries at main which must be used by splited files?

I’m not sure what you mean by that. It means the external lib has some header file and it needs to be used in multiple source files in the src/ folder? Can you give a concrete example?

Buenas noches.

Creo que lo que intenta decir Poleg, es algo parecido a lo que me pasaba a mí, cuando hice la transformación de los programas del ROV, del entorno Arduino al entorno VSC, que hay bastantes librerías que se utilizan con normalidad en el entorno Arduino, que aun no están convalidadas a VSC + PIO. Sin embargo Poleg ya tiene los archivos divididos.

Supongo que quiere decir, que tiene que haber una manera de decirle al programa que busque las librerías en alguna carpeta, incluso cuando el programa está dividido.

Maxgerhardt, me dio la solución, para poder utilizar las bibliotecas de Arduino, pero mi programa no estaba dividido.

Si Poleg pudiera ser un poco más preciso, e indicar con que librerías concretas tiene problemas, a lo mejor sería más fácil poder ayudarle.

Esta es la respuesta que me diste, supongo que abra que adaptarla a cuando un programa ya esta dividido.

Blockquote

maxgerhardt

3d

asesorplaza1:

src \ main.cpp: 154: 54: error fatal: PS2X_lib.h: No existe tal archivo o directorio

La biblioteca PS2X aún no está registrada en PlatformIO: le sugiero que vaya a GitHub - simondlevy/PS2X_lib: Arduino Playstation2 library with compiler warnings fixed , descargue el archivo ZIP ( https://github.com/simondlevy/PS2X_lib/archive/master.zip ) , a continuación, crear una nueva carpeta en la lib/ llamada PS2X_lib donde se coloca en los archivos del archivo ZIP de modo que la estructura es …

lib/
   PS2X_lib/ 
      PS2X_lib.cpp
      
PS2X_lib.h
      ...

luego agrega la línea

lib_deps = 
   PS2X_lib

en el platformio.ini para que PlatformIO busque la biblioteca.

Blockquote

Un saludo.

Good night.

I think what Poleg is trying to say is something like what happened to me, when I did the transformation of the ROV programs, from the Arduino environment to the VSC environment, that there are quite a few libraries that are used normally in the Arduino environment, which are not yet validated to VSC + PIO. However Poleg already has the files split.

I suppose it means, there has to be a way to tell the program to look for libraries in some folder, even when the program is split.

Maxgerhardt, gave me the solution, to be able to use the Arduino libraries, but my program was not divided.

If Poleg could be a little more accurate, and indicate which specific libraries have problems with, it might be easier to help you.

This is the answer you gave me, I suppose I open it to adapt it to when a program is already divided.

maxgerhardt

3d

asesorplaza1:

src\main.cpp:154:54: fatal error: PS2X_lib.h: No such file or directory

The PS2X library is not yet registered with PlatformIO – I suggest you go to GitHub - simondlevy/PS2X_lib: Arduino Playstation2 library with compiler warnings fixed, download the ZIP file (https://github.com/simondlevy/PS2X_lib/archive/master.zip), then create a new folder in lib/ called PS2X_lib where you put in the files from the ZIP file so that the structure is …

lib/
   PS2X_lib/ 
      PS2X_lib.cpp
      PS2X_lib.h
      ...

then add the line

lib_deps = 
   PS2X_lib

into the platformio.ini so that the library is looked for by PlatformIO.

Blockquote

Greetings.

Pues os comento, el proyecto en sí es un combinación de usos para la utlización del MFRC522 (Lector RFID)

La idea es tener el main en el que estén el setup y el loop. En el loop aparece la petición de modo de trabajo, y tras la introducción por parte del usuario se hace la llamada a uno de los archivos divididos.

El problema es que los objetos creados en el main y deben usarse en los dividos da error

So you have created global variables in your main.cpp and need to use them in your solucion1.cpp.

Your problem is that these global variables are not known to your main.cpp and also that you #include a CPP FILE in another cp file. That will lead to double-definition behaviours.

To resolve it, do:

  • remove #include solucion1.cpp from main.cpp
  • create the new file src/global_vars.h with the content
#ifndef GLOBAL_VARS_H
#define GLOBAL_VARS_H

/* types for global vars */
#include <MFRC522.h>
/* global vars */
extern MFRC522 rfid;
extern bool momento;
extern bool contador1;

#endif
  • in solucion1.cpp, add a #include "global_vars.h"

Now your global variables have been declared extern in a header file which every other cpp file can include.

This is the exact same technique as outlined in all the posts above.

1 Like

Mmmmmm I thought in that, but I didn’t know the concept “extern” thanks a lot…

But, where I initialize extern variables and objets?

And… I should make an #include "solucion1.cpp" at main? Because I need some functions that are coded there

You do not include the implementation (the source code which implements the function), only the function or variable declaration. So no #include "solucion1.cpp".

In one .cpp file only. The extern keyword makes a simple declaration “the exists a variable of this type and name somewhere”. It doesn’t create the variable or define its value. In fact, if you declare the existence of a variable which is never defined, you get an undefined reference error.

See e.g. variable declaration - When to use extern in C++ - Stack Overflow

2 Likes

So how I should do it? I cant see it, sorry

The post above shows you how to create a header file which declares all your global variables and use them in your other cpp file. That’s the solution.

1 Like

ok, thanks for your time

Hi alll, i’m quite a beginner in programming world; i would like to show here an example code to understand if i have well learned splitting principles:
main.cpp

#include "Arduino.h"
#include "main.h"

int delay_ms= 250;

// Set LED_BUILTIN if it is not defined by Arduino framework
// #define LED_BUILTIN 13

void setup()
{
  // initialize LED digital pin as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  blink();
}

main.h

#ifndef MAIN_H
#define MAIN_H

/* types for global vars */

/* global vars */

extern int delay_ms;

// prototypes don't need named attributes in the .h file

void blink();

#endif

blink.cpp

#include "Arduino.h"
#include "main.h"

void blink(void)
{
  // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);

  // wait for a second
  delay(delay_ms);

  // turn the LED off by making the voltage LOW
  digitalWrite(LED_BUILTIN, LOW);

   // wait for a second
  delay(delay_ms);
}

This code works but i have a doubt: is it all ok? Syntax and form are correct? There is something that can be done in a better way?

The example is syntactically correct indeed. You’ve splitted the function and the global variables accross the files correctly.

Now, this are are purely subjective “style” comments but it is general aggreed that global variables should only be used very sparingly (Why global variables are evil). The global variable used in the blink function in that case seems like a bad idea, style wise, since it’s not necessary. You can remove the global state dependency by making the delay_ms an argument of the function. You could also make the pin then an argument of the function. The less global state the better.

Of course, it still makes sense in some cases to have global variables: Like global objects for a e.g. display object, sensor object etc. But parameters which only matter to one function should be taken as an argument.

2 Likes

Thank you!, very clear and concise explanation, even for me :sweat_smile:
Then I will study also best practices as soon as possible.

It certainly looks like you got the idea. I would suggest that instead of calling the header main.h to call it blink.h (updating the include guard, etc, naturally), since it seems to relate to the blink.cpp code, rather than the main.cpp code.

The people who learned C first and then Arduino don’t understand what the Arduino-> PlatformIO migrants are going through.

Arduino doesn’t teach us what they’re doing behind the curtain, what you learned in class and from talking to other programmer peers. I’m trying to learn C well enough to use ESP-IDF, but I’m learning along the way that there are a lot of things I don’t understand- things I didn’t know were things. Holes have opened under my feet, along a path I’ve walked a thousand times.

I support the posters who want dedicated tutorials for the Arduino literate. I’m reading and using online reference guides, and I’m learning, but there are too many unknown unknowns to make this a graceful transition- and this is the transition EVERYONE is going to have to make. You reach the edges of Arduino.

What I really want is someone starting with a moderately complex .ino file- something with a few functions beyond loop and setup and some includes and definitions, and convert it to a proper .cpp source file. How do you rewrite this stuff, so you don’t use all the Arduino build-ins?

Please show me how to declare an OLED for example, because what I’m doing doesn’t work.

4 Likes

I have no idea if this will prove useful to those hoping for an example of converting an Arduino Sketch to PlatformIO, but maybe it will help Arduino converts to understand why things are not as simple as at first expected.

The Arduino system does a “shed load” (technical term) of stuff in the background. I have a book on the internals of the Arduino Software - which I’m not going to mention here - if you need further information.

The IDE will open all *.ino, *.cpp, *.h and *.c files that it finds in the sketch directory. These files will be opened on separate tabs in the editor. When you compile a sketch the following happens:

Preprocessing:

A temporary directory is created, on the sketch’s very first compilation, in the Operating System’s “temp” area. This is /tmp on Linux (and MacOS?) or something like c:\users\YOUR_NAME\AppData\Local\Temp\arduino_build_SOME_NUMBER on Windows. From here on in, I shall refer to this location as $TMP.

If the sketch has a number of *.ino files, all of them are concatenated into a single file with the extension .ino.cpp in the $TMP/Sketch subdirectory. The first file added to this working file, is the main *.ino - the one with the same name as the sketch’s own directory.

The remainder of the *.ino files are appended to the working file in alphabetical order.

If the sketch only has one *.ino file, it is processed as above, by itself, into the working file.

At the top of the working file, #include <Arduino.h> is added, if it was not already found in the original sketch.

Any libraries used by the sketch are detected and the include path for those are discovered. This is done by running a dummy compilation, discarding the output - to the nul device on Windows or the /dev/null on Linux - but processing any relevant error messages.

Function prototypes are generated for all the functions found in the working *.ino.cpp file. Occasionally, this is not possible for all functions, so the programmer has to add one, manually, to the main sketch *.ino file, to get it to compile.

The *.ino.cpp working file is preprocessed to add various #line and #file directives so that any subsequent error messages will reference the correct source file and line numbers within, rather than referencing the full, working *.ino.cpp file’s name and line numbers.

The Arduino Preprocessor carries out all these actions and it can be found on GitHub at GitHub - arduino/arduino-preprocessor: Parses an Arduino Sketch and converts it into valid C++ source code - if you are interested.

Build:

After preprocessing, the Arduino Build tool takes over. It too lives on GitHub at GitHub - arduino/arduino-builder: A command line tool for compiling Arduino sketches - again, if you are interested.

The build process starts by compiling the working *.ino.cpp file in the temporary directory created earlier. This is compiled into an object file named *.ino.cpp.o and stored/written to the $TMP/Sketch subdirectory.

Any additional *.c and *.cpp files in the Sketch’s original directory are now compiled into separate object files in the $TMP/Sketch subdirectory. This may not be necessary if the various source files have not been edited since the previous compilation. The existing *.o files will be reused.

All the libraries used will be compiled into multiple *.o files and written to the $TMP/Libraries subdirectory.

Next up, all the Arduino “core” files are compiled as *.o files and written to the $TMP/core subdirectory. One of the files compiled here is the one which supplies the main() function. You never need to write a main() function in a sketch.

After compiling all the source files, library files and core files, a static library, core.a, is built in the $TMP/core subdirectory.

An ELF format file is then created by linking all the *.o files in $TMP/Sketch and $TMP/Libraries with the core.a static library created above. This file is written to the $TMP directory and named as SKETCH_NAME.ino.elf.

SKETCH_NAME.ino.elf is then processed to create SKETCH_NAME.ino.eep which holds any data defined as being required to be written to the AVR’s EEPROM. Sadly, the Arduino IDE cannot (yet) upload *.eep files to the EEPROM, so this is a bit of a waste of time.

SKETCH_NAME.ino.elf is then processed again to create SKETCH_NAME.ino.hex which is, finally, the code that will be uploaded to the Arduino.

So, there you have it. A lot of work goes on in the background and is hidden from you by the Arduino system. Real C++ programs require you to declare function prototypes before use so that the compiler can set up the correct stack frames to call and return from same, and to return the correct data types from non void functions. The Arduino attempts to do all this for you so that you don’t have to worry about it.

Edited to correct abysmal spelling!

HTH

Cheers,
Norm.

7 Likes

I contributed earlier, but I now have a more comprehensive understanding of the Arduino-PIO transition. Some of it is ‘how we do stuff here’ stuff you just have get yelled at you by the nerds.

None of the differences are tangible to the Arduino newcomer. I plan to post something that I’d like to end up being that Arduino Migration Tutorial mentioned in the first lines of this post. Please look for it. I’m going to actually write it offline, proofread it and then share it. And hopefully we can destroy all the errors before anyone sees it. Just you and me, right?

Have a great weekend.

1 Like