Tutorial for creating multi cpp file arduino project

For much much more detail on the Arduino, see a posting I made about a book - it’s in the “projects made with PlatformIO” forum. I don’t refer to it very often, I’m shy! :wink:

Cheers,
Norm.

1 Like

@normandunbar - thank you so much for this from another Scotsman! I’m going to read and re-read this a few times. I have an Arduino project compiling (somehow) to 280K across 20+ files and it was a miracle keeping compilation stable. I will never do that again and now I think I know how.

1 Like

Did you mean to type 280K? You’ll have to go some to reduce that to 32K for an Atmega328 microcontroller!

Cheers,
Norm.

I walked away for a few weeks in frustration, so I didn’t see your recent primer. I look forward to reading it.
But this- this is the key to the highway:
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.

“Function prototypes” is very, very important. Thank you very much.

Thank you NormanDunbar for the great explanation, I appreciate your work!!!
Now I have come to the following problem. Unable to add an object with multiple files.
The object I want to use across multiple files is bounce. How could I fix this.

Here is my code.

Header.h

#ifndef HEADER_H
#define HEADER_H

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

#define BUTTONB_PIN 2
#define LED_PIN 6

extern int ledState;
Bounce buttons;

void setup();
void loop();
void program();

#endif

loop.cpp

#include "header.h"

void loop()
{
  program();
}

program.cpp

#include "header.h"

int ledState;

void program()
{
    bool needToToggleLed = false;

        // Update the Bounce instance :
        buttons.update();

        // If it fell, flag the need to toggle the LED
        if ( buttons.fell() )
        needToToggleLed = true;

    
    // if a LED toggle has been flagged :
    if ( needToToggleLed )
    {
        // Toggle LED state :
        ledState = !ledState;
        digitalWrite(LED_PIN, ledState);
    }
}

setup.cpp

#include "header.h"

void setup()

{
  //setup the bounce instance for the current button
  buttons.attach(BUTTONB_PIN, INPUT_PULLUP );   
  buttons.interval(25);                                     // interval in ms

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, ledState);
}

I put Bounce buttons; in the header.h file. This gives my an compile error:

.pio\build\nanoatmega328\src\program.cpp.o (symbol from plugin): In function `ledState':
(.text+0x0): multiple definition of `buttons'
.pio\build\nanoatmega328\src\loop.cpp.o (symbol from plugin):(.text+0x0): first defined here
.pio\build\nanoatmega328\src\setup.cpp.o (symbol from plugin): In function `setup':
(.text+0x0): multiple definition of `buttons'
.pio\build\nanoatmega328\src\loop.cpp.o (symbol from plugin):(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\nanoatmega328\firmware.elf] Error 1
1 Like

You did it correctly with the first variable and then did the second variable wrong.

extern Bounce buttons;

in the header file, and Bounce buttons; in one cpp file. Choose whichever.

2 Likes

Thank you for your response!

Have it working now!

1 Like

I have learned quite a bit reading through this. Thanks for all the contributions.
A question - I have a setup a multiple source file project consisting of

main.cpp
sensors.cpp
sensors.h

main.cpp contains setup() and loop()

sensors.cpp contains functions for getting data from sensors
I include sensors.h in the main.cpp

However, I am confused as to where I put the include for Arduino.h? If I put it in main.cpp but not sensors.h the program will not compile. However, if I put it in sensors.h but not in main.cpp it all works correctly.

My question is why? and should/could I put it in both places.

Here is some of my code for clarity:
sensors.h

/***************************************************************************
 * Header file for functions used for connecting to the bme680 module and 
 * water level sensor
 ***************************************************************************/

#ifndef SENSORS_H
#define SENSORS_H

/* libraries */
#include <RH_RF95.h>                            /* Radiohead RFM95 Radio lib - needed for RH_RF95_MAX_MESSAGE_LEN */
#include <Wire.h>                               /* I2C interface library */
#include <Adafruit_Sensor.h>                    /* Adafruit Unified Sensor library */
#include <Adafruit_BME680.h>                    /* Adafruit BME680 library */

/* sensor paramaters */
#define LEVEL_PIN 13                            /* Digital GPIO pin to read liquid level state */
#define BME680_I2C_ADDRESS 0x77                 /* define device I2C address: 0x76 or 0x77 (0x77 is library default address) */
#define ALTITUDE 85.0                           /* height abover sea level for pressure calculation */

/* defines */
#define MAXTRIES 20                             /* maximum connection attempts for wifi and mqtt */

/* function declaration (prototype) */
bool setup_bme680();                                                /* setup bme680 sensor */
bool setup_levelsensor();                                           /* setup water level sensor */
bool getdatafromsensors(char message[RH_RF95_MAX_MESSAGE_LEN - 1]); /* grab data from the sensors */

/* variables */
extern Adafruit_BME680 bme;         /* bmp280 object using I2C - declared in sensors.c */

#endif  /* SENSORS_H */

main.cpp

/***************************************************************************
 * This is a program to send temperature, humidity and pressure data 
 * from an ESP32-S 
 * based on rf95_reliable_datagram_client.pde which uses a simple addressed,
 * reliable messaging client with the RHReliableDatagram class, using the 
 * RH_RF95 driver to control a RF95 radio.
 ***************************************************************************/

/* libraries */
#include <stdint.h>

/* user defined headers */
#include "rfm95.h"                              /* Radiohead RFM95 functions */
#include "sensors.h"                            /* BME680 and water level sensor functions */
#include "esp32sleep.h"                         /* ESP32 sleep functions */
#include "Arduino.h"                            /* Arduino library */

/* defines */
#define uS_TO_S_FACTOR 1000000ULL               /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 300                       /* Time ESP32 will go to sleep (in seconds) */
#define DEBUG 1                                 /* set debug mode to output information to serial port */

/* Global variable declaration */
RTC_DATA_ATTR int bootCount = 0;                /* The number of times that the system has rebooted - stored in writeable RTC memory */

Put in in sensors.h because, I suspect, the Adafruit headers might rely on it, assuming they don’t actually include it themselves. Because main.cpp includes sensors.h, it will still see the benefits.

Should you: it’s not necessary – as you have found – in this case.

Could you: of course! No harm done, if you do other than a tiny delay to open and close the file.

HTH

Cheers,
Norm.

Thank you,

So it is okay to potentially include Arduino.h more than once? I am rather new to cpp (brought up on fortran many years ago and been using a python more recently).

I have made a header file called header.h which contains:

#ifndef HEADERS_H
    #define HEADERS_H
    #include <stdint.h>
    #include <Arduino.h>                            /* Arduino library */
#endif  /* HEADERS_H */

and have included that in all the header for my main program (main.cpp) sensor functions header file (sensors.h). As I understand this would only include the libraries one time as the preprocessor looks at the files?

I realised with more reading that the directory structure in platformio allows you to move user defined libraries to the libs directory and the headers.h file to the includes directory. However, as far as I can tell if I now try to include the headers.h in the library is cannot find it.

Here is a diagram showing what I mean.

|--include
|  |
|  |--headers.h
|--lib
|  |
|  |--sensors
|  |  |- sensors.cpp
|  |  |- sensors.h (this includes a reference to headers.h)
|  |
|
|--src
   |- main.cpp

Would the most robust option to use a relative directory to include headers.h in the sensors.h file or simply include a second reference to the Arduino.h library in sensors.h.

I am just trying to get things sorted in my head before I make a mess which is difficult to debug later on.

Thank you in advance,

Martyn

Yes indeed. The check at the top of the header would prevent it being used twice.

Yes again. If the headers were #included more than once (in the same compilation unit) errors would occur if there wete #defines in the header.

Normal headers can go in src or include as desired as both are on the list of directories searched for headers.

If you have a library named xxxx then source and headers should be together in lib/xxxx. Library yyyy would be in lib/yyyy. Etc.

I would use #include "sensors.h" in both "sensors.cppandmain.cpp` as that’s the header for the “sensors” library. Doing so explicitly tells me later which library or libraries I’m using for this source file.

Also, you really want a library to be self-contained. You might think it will only be useful once, but you will be surprised. Not only that, if you eventually publish your library/libraries for others to use, it’s just a quick copy of the files in the appropriate lib sub-directories and no need to look elsewhere.

Don’t use relative paths. It might be fine in this case, but move things around, or in another project, it will bite you where it hurts! :wink: (Ask me how I know?)

Personally, I would put headers.h in src alongside main.cpp. But see above for how I would suggest you do it.

HTH

Cheers,
Norm.

Thanks again for your very useful reply. I am learning quickly.

I have tried putting headers.h in both src and include but I get the error:

Building in release mode
Compiling .pio/build/nodemcu-32s/lib8ad/sensors/sensors.cpp.o
lib/sensors/sensors.cpp:8:21: fatal error: headers.h: No such file or directory


  • Looking for headers.h dependency? Check our library registry!

here is the top of sensors.cpp

/***************************************************************************
* These functions are for connecting to the bme680 module and
* water level sensor
***************************************************************************/

/* header file */
#include "sensors.h"
#include "headers.h"

/* variable declaration */

I can’t see why it is not finding it? It works if I start adding paths, ie:

#include “…/…/include/headers.h”

I must be doing something daft…

Seems a tad weird! A #include in a source file in src should be able to find a header file that lives in include. (Without needing to add relative paths.)

Aha!

It’s building the library, not the main.cpp file, my mistake.

Headers for libraries need to go in the library directory I have found. This keeps them self-contained. The exception to this are the compiler supplied headers.

So,

  • sensors.cpp and sensors.h are in lib/sensors
  • Any other headers specific to the library, are in the same place.
  • In the library sources, you would #include the list of required headers explicitly. (<stdint.h> and “Arduino.h”).
  • Headers for the main app file(s) go in src or include.

HTH

Cheers,
Norm.

1 Like

That explains it. I am going to try and write up what I have done from a novice’s perspective. Might help others.

It also brings me full circle, I was unsure if including arduino.h in the main.cpp file and the library file was bad practice - it does not throw errors which I suppose answers my question but it seems that I now have two instances of

#include <Arduino.h>

across my program and libraries, which to the uninitiated seems like it could be problematic…

It is interesting to play and learn.

Thank you for your help,

Martyn

I should have mentioned. The libraries are built separately from the main application. So they don’t get include passed on the list of directories to scan for header files. The non-library compilations do.

Anyway, glad we got there. Take care.

Cheers,
Norm.

1 Like

Nice Job.
Thanks for the time you spent to do this.

Regards
Júlio Silva

1 Like

I had to come here too after 3 hours struggling in fix errors from 10 tabs .ino converted to .h. Man, this is more painful than appears after the project gets long and you start to have multiple variables and objects being used in many different functions and places. I really got tired of including in every file the “arduino.h” and “extern” object, variables, structs, so, I gave up and just start to put the functions implemented direct in the .h and include them in the order of the use. I know this is not right, but this way I don’t need to keep repeating these “includes” in every pair file .h/.cpp.
I am new on it too and I wanted to know Why,in hell, when you create the pair .h/.cpp, you need to “include” and “extern” all the things you will use in it, even if that was already included before, I really didn’t get it, it’s like the pair .h/.cpp just see what is inside it. Are they compiled individually and them joined to the others ? I am going search more about it…

Briefly, as I’m off out with MrsD…

Extern is required because if you declare int XXX in s header file which is included in multiple cpp files, you get int XXX in each one and that’s a duplication.

What you do is declare extern int XXX in the header and in one of the CPP files, declare int XXX. That way you have one copy of XXX and every CPP which includes the header knows that it exists elsewhere.

You need header files to tell the compiler what functions, globals etc exist. It needs to know the function prototypes in advance so that it can validate that they are being called properly and to allow it to generate the correct calling code.

The Arduino IDE hides all this from you and this causes a lot of confusion when people migrate to “proper” C++. Without wishing to spam, my book covers in great detail, the Arduino compilation process. If you are interested I think the first few chapters, where this is explained, are available as trial chapters on Apress.com, and possibly Amazon too.

Happy to explain in more detail though, if you have more questions.

Cheers,
Norm.

1 Like

So, there is not other way to split the code without repeating the same ‘include’ in each file ? I mean, I literally just want to take a single long text and divide into chapters (files), but apparently each chapter doesn’t know anything what happened in the others =/.

To split this:

Main.ino

#include <Arduino.h>

int mGlobal = 10;

void setup(){
        Serial.begin(115200);
        mRun("Running..");
        mWait("Waiting..");
}

void loop(){}

void mRun(String s){
        Serial.println("File 1: "+s);
        mGlobal = 1;
}

void mWait(String s){
        Serial.println("File 2: "+s);
        mGlobal = 2;
}

I must do this: ?

Main.ino

#include <Arduino.h>

#include "src/0_globals.h"
#include "src/file1.h"
#include "src/file2.h"

void setup(){
        Serial.begin(115200);
        mRun("Running..");
        mWait("Waiting...");
}

void loop(){}

file1.h

#ifndef FILE1_H
#define FILE1_H

#include <Arduino.h>
extern int mGlobal;

void mRun(String);

#endif

file1.cpp

#include "file1.h"

void mRun(String s){
        Serial.println("File 1: "+s);
        mGlobal = 1;
}

file2.h

#ifndef FILE2_H
#define FILE2_H

#include <Arduino.h>
extern int mGlobal;

void mWait(String);

#endif

file2.cpp

#include "file2.h"

void mWait(String s){
        Serial.println("File 2: "+s);
        mGlobal = 2;
}

I am just wondering if there is some useful workaround to make it faster and safer.

Ok, I’m back home and no longer typing on my phone!

Your code example, I assume is not the real code? This:

#include <Arduino.h>

int mGlobal = 10;

void setup(){
        Serial.begin(115200);
        mRun("Running..");
        mWait("Waiting..");
}

void loop(){}

void mRun(String s){
        Serial.println("File 1: "+s);
        mGlobal = 1;
}

void mWait(String s){
        Serial.println("File 2: "+s);
        mGlobal = 2;
}

Is really not worth splitting as it is so small, the effort is not really worth the final outcome.

In a proper C++ application, however, you would not be able to call mRun() or mWait() from either setup() or loop() because those two functions have no idea that the other two exist! You cannot call in a forward direction in a C/C++ application, only stuff (technical term) that has come before in the compilation. So, what is done is to copy the function prototypes above all the other functions in this source file, so that they are all known about beforehand. In other words:

#include <Arduino.h>

void setup();
void loop();
void mRun(String s);
void mWait(String s);

int mGlobal = 10;
...

Now when the compiler gets to the actual calls to mRun() or mWait() etc, it knows what parameter types and quantities to expect, and the proper return type – this means it can set up a function call and return “frame” on the call stack at runtime. If you don’t have the function prototypes ahead of time, they are expected to return int, but I can’t remember what the expect for the parameter(s), if anything.

Moving on, we have added the function prototypes to the top of one source file, but any other source file which also calls these functions needs to know about them as well. For this reason, we put them into a header file, and include the header in any source file that wants to call one or more of the functions (I’m not thinking about global variables at this point.) This is purely to save typing and/or to allow the prototypes to exist in one place, in case we ever change them – less typing to fix everything!

Normally, when splitting a large source file into smaller ones, you do it by function – by what the code is doing, rather than just by the C/C++ functions within the application. So, you might have all the Serial handling functions in, say,. mySerial.h and mySerial.cpp, all the maths functions in myMaths.h and myMaths.cpp and so on. You would keep all the functionality together in a pair of source and header files. You could, if it was useful, convert these pairs into separate libraries – but that’s a problem for another day! You would also give them meaningful names to describe their function too.

In Main.ino you wouldn’t need the “src/” part of these lines, if you have your headers in src or include in your PlatformIO project structure, they will be found automatically at compile time.

#include "src/0_globals.h"
#include "src/file1.h"
#include "src/file2.h"

Change the above to:

#include "0_globals.h"
#include "file1.h"
#include "file2.h"

You are also missing the actual definition of mGlobal, you have declared it extern in both header files, but never declared it – I assume, always a bad thing, that 0_globals.h has something like:

#ifndef  0_GLOBALS
#define  0_GLOBALS

int mGlobal = 10;

#endif // 0_GLOBALS

Now, there’s absolutely nothing to stop you having a header file like this:

#ifndef ...
#define ...

extern int mGlobal;
void setup();
void loop();
void mRun(String s);
void mWait(String s);

#endif // ...

Which could be included into all your other source files, even if they don’t need to call all of the functions, they only need to include the header if they are calling one or more. The fact that the other functions are also declared makes no difference as the compiler will find them again later, when compiling a source file that does call one of the others.

Each source file is a “compilation unit” and the compiler forgets everything it knows about that unit when it has completed the compilation of said unit. This is why you need to include the header into every compilation unit – basically, each of your source files – so that they all know about the functions and extern globals. But you still have to remember to declare the global(s) in one compilation unit as non extern otherwise the linker will complain about missing stuff when it comes to link all the compilation units’ object files together.

As for a workaround, hopefully the above will help.

Cheers,
Norm.

2 Likes