Splitting into multiple files

Hello.

Iā€™m trying to split a program that compiles as a single file into separate files (to try and reduce my brain load a bit!).

Iā€™m not particularly familiar with C/C++, so Iā€™m possibly doing something stupid.

If I set up the following directory structure:

project/comm.c
project/comm.h
project/main.ino

and set up my includes as follows:

// project/main.ino

#include "comm.h"
#include "comm.c"

then I get a load of compilation errors, specifically that variables declared in comm.h are not declared. e.g.

comm.c: In function 'connect':
comm.c:2:3: error: 'tcp_client' undeclared (first use in this function)
   tcp_client.connect(server_ip, 2000);

My theory is that rather than only compiling my main.ino file, the compiler is attempting to compile my ā€œlibraryā€ (for want of a better term). To try and prove this theory, I changed my directory structure to look like this:

project/comm.lc
project/comm.lh
project/main.ino

and set up my includes as follows:

// project/main.ino

#include "comm.lh"
#include "comm.lc"

Success! The program now compiles and links successfully.

So my question is, how do I do this ā€œproperlyā€. Presumably I need to tell the compiler (somehow) that main.ino is the entry point, and that everything else shouldnā€™t be compiled until it is included?

I am using the particle-cli, rather than the web IDE.

Thank you in anticipation.

  • You would only include the .h file and your other (which should be a .cpp) not.
  • In your .h file you should add an #include "Particle.h" and your .cpp should include your .h too.
  • And sure your ā€œlibraryā€ will be compiled too and then linked to your project - thatā€™s how itā€™s supposed to be.
  • Use #pragma once or
#if !defined(__COMM_H__)
#define __COMM_H__
...
#endif

in your .h file to prevent your header from being included multiple times.

  • The ā€œentry pointā€ is the file that contains void setup() and void loop().

Yup. What @ScruffR said. Plus ā€¦

Basically, any variable, macro, function, etc you write and/or use in one source file and that you may want to use in one or more other source files, should be declared in a header file. By convention, the header file will have the same name as .cpp file that implements its declarations ā€“ but with .h instead of .cpp as the extension. (It doesnā€™t strictly have to be named so.)

The idea is that .h header files get #included, while .cpp files never are.

  • This is only convention. Technically, you can #include any file you like at any point in your code. Itā€™s just not usually a good idea.

Header files should contain only the type definitions of functions, variables, etc. Assignments should only be in the .cpp files. For example, ā€¦

comms.h

#if !defined _COMMS_H
#define _COMMS_H

extern int commsEnabled; // global variable. definition only. no assignment
                         // see note below about the "extern"

void sendChar(char c);

#endif

comms.cpp

int commsEnabled = 1; // value is assigned here and MUST have the same type as in comms.h

void sendChar(char c) {
    if (commsEnabled) Serial.write(c);
}
  • Hereā€™s a trap to keep in mind: In C++ but not in C the extern is required to tell the compiler to just trust that this int is assigned somewhere else. The linker will complain later, if it is not. The int type must be re-declared when the first value is assigned ā€¦ because, ā€œC++ requires a type specifier for all declarationsā€. Without the extern, the compiler will complain that commsPort is being re-defined in comms.cpp. Itā€™s different for C though! At the end of the day, just use .cpp for everything you code for a Particle/Bluz device and you should be fine.

Then, in main.ino:

#include "comms.h"

void setup() {
}

void loop() {
    sendChar('A');
    delay(1000);
}

You might then add a third ā€œmoduleā€ ā€¦
debug.cpp

#include "comms.h";

void debug(char *msg, char letter) {
    Serial.print(msg);
    Serial.print(" -- ");
    sendChar(c);
}

Presumably that debug() function would be used from multiple places, so youā€™d want a debug.h file as well ā€¦

#if !defined _DEBUG_H
#define _DEBUG_H

void debug(char *msg, char letter);

#endif

And on it goes.

Note that technically, Particle/Arduino etc stuff uses a language named, ā€œWiringā€. For the most part, itā€™s simply C++ with a library of handy functions The ā€œsketchā€ file, main.ino is a break from C++ tradition and cannot have a main.h go with it, as far as I know. No biggie at all. Only us more advanced C folks dabble in folks other than main.ino anyway, right?!

AS a rule of thumb, put all global or potentially re-usable definitions into a module.h file and include that from the module.cpp file and any others that will use it.

Oh and those #define _COMMS_H tags ā€¦ they can be anything you like. They just have to be unique across the entire projectā€™s scope.

Finally, a note about .c versus .cpp. These are NOT the same thing. For example, the C language doesnā€™t know about class (object oriented syntax) and depending on the standard being enforced by the compiler of the day, will not allow any nested scope variable declarations. C also has a slightly different calling convention at the machine code level, compared to C++. For Wiring projects, for the most part, you should probably always use the .cpp extension, so that everything will play nice.

Example: The following should not compile in a .c file but will in a .cpp file ā€¦

int LED = 13;
void loop() {
    if (LED == 13) {
        int alarm = 5;     // <--- C (strictly enforced) doesn't allow this. C++ does.
        while (alarm--) {
          ...
        }
    }
}

ā€¦ though there are versions of C compilers that allow it anyway. Iā€™m not sure about GCC as used for Particle and Arduino code. Never tested it. As usual, the only standard is that there are no standards! :stuck_out_tongue:

DISCLAIMER: All of the above was test compiled only in my grey matter. I may have led you astray here or there, despite my best efforts.

Thereā€™s a tonne more detail about all this online, of course. I just happen to be laid up in bed recovering from surgery, going crazy with boredom. So here I am. Hope it helps some.

1 Like

@gruvin, did you actually want to include comms.cpp in both your code snippets and not comms.h? :confused:
This might lead to the mentioned re-declaration linker error, since you are in fact creating commsEnabled and sendChar() in two modules (main.ino & debug.cpp) which should be linked and will clash.

BTW: I would not use main.ino as there already is a main.cpp/.h combo in the Particle framework.


Best wishes for your recovery :hospital:

Wow.
Thank you for all your help (particularly @gruvin - thatā€™s amazing) . That makes much more sense.

Iā€™ll have a proper look through again this evening, and let you know how I get on.
It sounds like problem is that I was trying to include the ā€˜.cppā€™ files in my ā€˜main.inoā€™ - as I said, doing something stupid!

Just to follow up - all of the above did sort my problems.

Thank you so much for your help!
:beers:

1 Like

Oh! Nope. That was an unfortunate typo! I've just corrected it. Glad @hayden.ball got there in the end regardless.

EDIT: Notification of those replies only just showed up for me today -- 19 days later. Strange.

1 Like

Hey,
Sorry for opening up such an old topic but I am trying to do something very similar to the OP. Both yours and @gruvin replies were super helpful. I was wondering if there was a way to access declared classes from the ino file in the external file? My ino file is getting quite large and I would like to split it up into ā€œmodulesā€

This is how libraries work.
You have a header file (.h) that containts the declarations of your "external" entities and a .cpp file (or the and your .h file itself) contain the implementation.
In the .ino file you add an #include statement to pull in the .h file.

But I think this is already what the thread above is all about.

If you want to do it the other way round (.ino exposing to other modules) then I'd rather argue against that.
While it is possible to access global entities (by defining an extern reference to it), it's not best practice to send items upstream. Rather extract all things that may be needed into a single .h/.cpp combo and include wherever needed.
The further down you get in your project structure the scope of objects should get smaller and smaller.

1 Like