A question regarding .h files

For the first time, I have sub-divided my very large code into multiple .h files which has made my code easier to navigate.

I understand that I need to include the relevant .h files for any particular .h to run if it needs them.

However, if I jump out to a particular .h file.... say test.h for example, am I right in saying that I can only have one void loop?

#pragma once                                                                                              // Only compile this code once  (same as an include guard)

#include "Arduino.h"
#include "GlobalVars.h"
#include "EEPROMretrieve.h"

void uploadSDdata() {
  //blah blah
  Checktype();
}

void Checktype{
  // More blah
}

I am testing the contents of an SD card and I made a routine to check through that data.

I added another void CheckType(){ blah } in that .h file and it says that isn't allowed ('Checktype' was not declared in this scope).

I don't really want to make another .h file just for this short routine.
Thanks

do a web search for C++ header file you will get plenty of links on how to use them, e.g. 2.11 — Header files
in summry you have .cpp files and associated .h files

  1. .cpp holds variable and function definitions
  2. .h holds extern variable declarations and function prototypes

I have not used any CPP files. I have found that confuses the heck out of me.

I could just make Checkfiles.h and include that, but then I have made another .h file for a tiny amount of code

You can omit .cpp files for very simple code only. As you start to define a functions in the .h files - you MUST use a .cpp with them.

You must understand that dividing code into separate modules is not just a mechanical division of files into parts.

the general rule is don't put variable definitions (which allocate storage) and function definitions (which creates code) in a header file
otherwise you could end up with multiple definitions at link time

1 Like

Either move the definition of CheckType() to before uploadSDdata(), or create a function prototype before uploadSDdata(). I suggest the former.

But putting code in header files is not generally a good idea. Unfortunately having a collection of .h and .cpp files can not really be avoided.

I don't think I will ever understand all of this!

My code was 4500+ lines and getting very difficult to manage.

I tried using/writing classes etc and just got horribly lost.
I just don't understand it. Don't think I ever will.

That link in post #2 is just baffling.

Dividing it into .h files only seems to have worked well for me - rightly or wrongly.
I have been using them almost like basic subroutine that I just jump out to and then back.

It doesn't seem to have broken anything, but then again.... hobby level and all that.

That is ok if you only include the files in one place. So if you have sketch.ino which includes each "sub file", and none of the sub-files are included anywhere else.

so long as the header files are independent and don't attempt to include each other you are probably OK
you will get a problem when two or more header files include another header file which contains definitions and at link time get multiple definitions error message

it is good practice to organize code into separate .cpp files. those files contain functions called from other files. those files should also contain variables and additional functions only used within the file which makes the code more maintainable: easier to understand, debug, expand

but the compiler needs to know the argument and return types of the functions in those separate .cpp files. .h files contain the function prototypes e.g. int myFunc (int a, int b);

the separate .cpp file should also include the corresponding .h file and the compiler will insure that the prototype match the actual function definition

1 Like

If you take on writing such large programs, this can't be proceed in "hobby level" at all and it is vital for you to study the language more deeply. This will help you not only in the specific case of breaking code into parts. but most likely you will be able to significantly simplify and optimize the program, making it several times shorter

Hmm.

Well every one of my .h files has the same includes at the top:

#include "Arduino.h"
#include "GlobalVars.h"

This is so they can access the variables they need.

Not sure what you mean by 'access each other'?

I have a small main ino program. This might jump out to a header file that does nothing but check the touchscreen presses.

This then returns to the main ino, where it then jumps out to a header that works out what to do with that touch event.

Basically, my headers are moving large sub-routines out of my main ino to make it more readable.

I do however, have multiple header routines that include the access to the same EEPROM header file.

I have a header file for controlling all writes to the EEPROM (storing data).

Any header that alters the data in the EEPROM obviously has to include that EEPROM storage header, so that is can write to it.

I discussed breaking up a large code in here and this was one of the suggestions and it seems to have worked fine. Now it looks like it's a bad plan.

Whatever happens, as long as it works I suppose and compiles correctly.....

I have tried and tried and tried to understand Classes, OOP etc. But my old brain just cannot get past all the jargon.
Even the basic explanations seem to get bogged down in the fact they assume you have a basic knowledge of C programming.

If you have a variable definitions (and not just a declarations) in the file "GlobalVars.h" - you can't include it more than once in the other files otherwise it will lead to the "Multiple definition" error during compilation or linking

understanding the language doesn't mean you understand how to use. doesn't mean you understand the use of state machines, linked list, ...

it helps to see and understand well written programs

...all of this are just a steps on the very long way...

I am absolutely not disagreeing.... but I don't get any error?

If I don't put #include "GlobalVars.h" at the top of my header files, then yes... I get a compile error.

This is my GlobalVars.h

#define dmxSEL3 4                                                       // DMX Solder pad number of universes selection inputs
#define dmxSEL4 5
#define dmxSEL5 6
#define magpen 32                                                       // Magnetic pen sensor (top of enclosure)
#define heartbeatLED 38

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx1{Serial1};
teensydmx::Sender dmxTx2{Serial2};
teensydmx::Sender dmxTx3{Serial3};
teensydmx::Sender dmxTx4{Serial4};
teensydmx::Sender dmxTx5{Serial5};

#pragma once                                                           // Only compile this code once  (same as an include guard)

unsigned long currentMillis;
unsigned long heartbeatMillis;                                         // Heartbeat LED timer
unsigned long screenMillis;                                            // Timer for deboucing the touchscreen
unsigned long getdmxMillis;                                            // Timer for obtaining the DMX values to send out
unsigned long testMillis;                                              // Timer for test mode outputs
unsigned long flashMillis;                                             // Timer for flashing text/graphics
unsigned long loops = 0;                                               // Counts the main loop speed
unsigned long lastMillis;

unsigned long heartbeatpulse = 2000;                                   // Speed of the heartbeat in ms
unsigned long detailupdate = 500;                                      // Update time in ms of the DMX details pages
unsigned long dmxupdate = 100;                                         // Update time in ms to get the DMX data to send

bool dmxEN1_2 = true;                                                  // Default DMX channels 1 & 2
bool dmxEN3 = false;                                                   // Optional DMX channel 3
bool dmxEN4 = false;                                                   // Optional DMX channel 4
bool dmxEN5 = false;                                                   // Optional DMX channel 5
byte dmxUNI = 2;                                                       // How many DMX Universes are active (Default is 2)

byte temp_edited_ch;                                                   // Temp storage of the channel being edited in SETUP

byte dmxuni_1TYP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Defined output type of Universe 1 channels.
byte dmxuni_2TYP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Defined output type of Universe 2 channels.
byte dmxuni_3TYP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Defined output type of Universe 3 channels.
byte dmxuni_4TYP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Defined output type of Universe 4 channels.
byte dmxuni_5TYP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Defined output type of Universe 5 channels.

int dmxuni_1ADD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Defined output addresses of DMX Universe 1 ,channels 1-10                          What address each channel uses 1-511
int dmxuni_2ADD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Defined output addresses of DMX Universe 2 ,channels 1-10
int dmxuni_3ADD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Defined output addresses of DMX Universe 3 ,channels 1-10
int dmxuni_4ADD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Defined output addresses of DMX Universe 4 ,channels 1-10
int dmxuni_5ADD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Defined output addresses of DMX Universe 5 ,channels 1-10

byte dmxuni_1VAL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Output ON values of DMX Universe 1 ,channels 1-10                                  DMX value to be sent to the target address 0-255
byte dmxuni_2VAL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Output ON values of DMX Universe 2 ,channels 1-10
byte dmxuni_3VAL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Output ON values of DMX Universe 3 ,channels 1-10
byte dmxuni_4VAL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Output ON values of DMX Universe 4 ,channels 1-10
byte dmxuni_5VAL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Output ON values of DMX Universe 5 ,channels 1-10

int dmxuni_1RUP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp up time in seconds/minutes of Universe 1, channels 1-10                       RAMP UP value
int dmxuni_2RUP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp up time in seconds/minutes of Universe 2, channels 1-10
int dmxuni_3RUP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp up time in seconds/minutes of Universe 3, channels 1-10
int dmxuni_4RUP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp up time in seconds/minutes of Universe 4, channels 1-10
int dmxuni_5RUP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp up time in seconds/minutes of Universe 5, channels 1-10

int dmxuni_1HLD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Hold time in seconds/minutes of Universe 1, channels 1-10                          Hold time
int dmxuni_2HLD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Hold time in seconds/minutes of Universe 2, channels 1-10
int dmxuni_3HLD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Hold time in seconds/minutes of Universe 3, channels 1-10
int dmxuni_4HLD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Hold time in seconds/minutes of Universe 4, channels 1-10
int dmxuni_5HLD[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Hold time in seconds/minutes of Universe 5, channels 1-10

int dmxuni_1RDN[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp down time in seconds/minutes of Universe 1, channels 1-10                     RAMP DOWN value
int dmxuni_2RDN[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp down time in seconds/minutes of Universe 2, channels 1-10
int dmxuni_3RDN[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp down time in seconds/minutes of Universe 3, channels 1-10
int dmxuni_4RDN[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp down time in seconds/minutes of Universe 4, channels 1-10
int dmxuni_5RDN[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Ramp down time in seconds/minutes of Universe 5, channels 1-10

bool dmxuni_1TGL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Toggled output state of the 10x channels of DMX Universe 1                         Toggle state  0-1
bool dmxuni_2TGL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Toggled output state of the 10x channels of DMX Universe 2
bool dmxuni_3TGL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Toggled output state of the 10x channels of DMX Universe 3
bool dmxuni_4TGL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Toggled output state of the 10x channels of DMX Universe 4
bool dmxuni_5TGL[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Toggled output state of the 10x channels of DMX Universe 5

byte dmxuni_1TXF[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Initial transmission flag of the 10x channels of DMX Universe 1                    Transmission flag  0-1
byte dmxuni_2TXF[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Initial transmission flag of the 10x channels of DMX Universe 2
byte dmxuni_3TXF[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Initial transmission flag of the 10x channels of DMX Universe 3
byte dmxuni_4TXF[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Initial transmission flag of the 10x channels of DMX Universe 4
byte dmxuni_5TXF[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                   // Initial transmission flag of the 10x channels of DMX Universe 5

int dmxuni_1TMP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // DMX value count for the 10 channels of Universe 1                                  DMX counting values (for counting up to and back from a target)
int dmxuni_2TMP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // DMX value count for the 10 channels of Universe 2
int dmxuni_3TMP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // DMX value count for the 10 channels of Universe 3
int dmxuni_4TMP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // DMX value count for the 10 channels of Universe 4
int dmxuni_5TMP[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // DMX value count for the 10 channels of Universe 5

int dmxuni_1HCT[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Temp counter for the hold function Universe 1                                      Temp counter for the hold fucntion
int dmxuni_2HCT[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Temp counter for the hold function Universe 2
int dmxuni_3HCT[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Temp counter for the hold function Universe 3
int dmxuni_4HCT[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Temp counter for the hold functionof Universe 4
int dmxuni_5HCT[10] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};                    // Temp counter for the hold functionof Universe 5


bool firstupdate1;                                                     // If true, it forces a first update of the DMX values on the details screen
bool firstupdate2;
bool firstupdate3;
bool firstupdate4;
bool firstupdate5;

bool magpenstate = false;                                              // Magnetic sensor

bool runmode = false;                                                  // True if running
bool setupmode = false;                                                // True if in setup mode
bool testmode = false;                                                 // True if in test mode
bool settings = false;                                                 // True if in settings mode

byte ScreenData;                                                       // Required value from screen
byte ScreenDump;                                                       // Unused data
byte Page;                                                             // What page we are on
byte oldpage;                                                          // Remember the previous page

byte siggraphic = 0;                                                   // Counter for the radio signal graphic

bool touchSTOP;                                                        // Flag to prevent repeat operation of touchscreen events
int testmenu;                                                          // Test menu selection
int testaddress;
byte testuniverse = 1;

byte testrangelowByte;                                                 // Variables to determine value of the SoftDMXaddress
byte testrangehighByte;
int testrange = 255;                                                   // Default setting for the test range

byte timer;

byte keypadrequest;                                                    // Above 0 when a keypad value is requested
int keypadnumber;                                                      // The number retrieved from the keypad
int keypad100;                                                         // Digits converted from keypad
int keypad10;
int keypad1;
byte entereddigits;                                                    // How many digits have been entered
int keypadvalue;                                                       // The final value to be sent, deduced from the keypad
int keypadaddress;                                                     // The final address to be sent, deduced from the keypad

byte setupUniverse;                                                    // Which Universe we are setting up
byte setupOption = 1;                                                  // What setup option we are viewing

byte setupDMXonvalue;                                                  // Setup ON value (0-255)
int setupDMXaadress;                                                   // Setup address required
int setupRAMPup;                                                       // Setup ramp up time
int setupHOLDtime;                                                     // Setup hold up time
int setupRAMPdown;                                                     // Setup ramp down time
byte selectstage = 0;                                                  // What stage of the setup process we are at

bool button1set = false;                                               // Flags to ensure all data has been entered in the setup menu
bool button2set = false;
bool button3set = false;
bool button4set = false;

byte tempval;                                                          // tempval used for debugging
byte settingsstage = 0;                                                // What stage the settings process is at
byte resetcount = 0;                                                   // Counter for the factory reset progress  bar

bool screenstate = true;                                               // Whether the screen is on or off (Magpen)
bool maglockoutstate = false;                                          // Whether the module is locked out

bool magpenboot = false;                                               // Whether the system awaits a magnetic pen input to allow booting
bool maglockout = false;                                               // Whether screen lockout with the mag key is active
bool magscreenpower = false;                                           // Whether the screen power is controlled with the mag key
bool magfulltest = false;                                              // Whether the mag pen provides a full test

bool menuexit = false;                                                 // Used to leave menus

byte DMXchanneltype;                                                   // Used when sending the DMX data

byte randomDMX[9] {1, 2, 3, 4, 5, 6, 7, 8, 9};                         // Random generator for the random DMX functions

const int chipSelectINT = BUILTIN_SDCARD;                              // Onboard Teensy 4.1 SD card CS/SS
const int chipSelectEXT =  2;                                          // External SD card CS/SS
const int extSDsw = 3;                                                 // External SD card switch (Low = card inserted)

File sdFile;                                                           // SD card data
char sdData[100];                                                      // Lines of text within the SD card data
byte Size_of_SD_array;                                                 // The length of the sd text array, including empty spaces
bool uploadvalid;                                                      // Whether the uploaded SD file is valid
byte sdTEMPsearch;                                                     // Temp search variable for uploading from SD card
byte uploadTEMPdata;                                                   // The uploaded temp data

#define DEBUG 1                                                        // Serial debug routine for critical functions

#if DEBUG==1                                                           // Setting this to anything but 1 will send all non-critical serial prints to the debug screen (which slows the routine)
#define outputDebug(x); Serial.print(x);
#define outputDebugLine(x); Serial.println(x);
#else
#define outputDebug(x);
#define outputDebugLine(x);
#endif

well ... it's essential that you understand the language

why if you want to call a function in a library that the library .h file is included in your code so that the compiler knows what the return and argument types are of the library functions

Thanks all.

Feeling a bit deflated now.
I think it's time to call it a day with programming then for me.
I have tinkered with Arduinos for as a long as I can remember. I clearly am not getting it.

Thanks for the advice

You used a

directive in the "GlobalVars.h" that prevents the file to be included more than once in the same compilation unit.

So if you put the GlobalVars.h to the .h files only and include these .h files in the single .ino file - it saves you from compiling errors. But you are on the edge and if you change something, the code can stop compiling at any moment.

Yes, the only place ALL the .h files are listed is the original .ino file.

The 'slave' header files (for want of a better word) only usually include the GlobalVars.h and maybe one or two other header files if they need to be called because something in that subroutine needs something from another header.

Dividing this up was suggested to me (in the Teensy forum actually) and it worked.
Now maybe I should have just left it as one huge code.

I did try making some classes for various functions (E.G reading the touchscreen), but without really understanding how format the code, they just didn't work.

I used an online tutorial for classes that had an error in the online tutorial... that threw me for 2 days.

I had real issues, as I could not get it to share the public variables and never did find out why.

ho hum