Declaring global variables in different tab

Hi, I've been through a few other forum threads with a similar problem but haven't really understood them. I've got a large program, and want to split it into multiple tabs for better organization. This means I also want to declare variables that have to do with certain tabs in that tab as well, but they still need to be accessible by other tabs.

Basically what I need is a way to handle multiple tabs in the same way that Processing does, in that you can declare a variable in any tab just like normal and it will be accessible from any other tab like normal. However, when I try to compile, it will complain that the variable the tab is trying to access (declared in a different tab) doesn't exist.

All tabs have .ino extension, I've tried the whole "extern" thing to no avail... I'm just kinda confused on how this works. My methods seem to work cross-tab without an issue, but variables only work in the tab in which they're declared. Any suggestions for how to fix this?

1 Like

First of all, you're confusing define versus declare. This is a data definition:

int value; // This is a definition

and if it is defined outside of a function, it is in scope everywhere in that source file (i.e., global scope). Your problem is that you want to access that variable in a different file. That it, you need to declare the variable in the second file. You do this with:

extern int value; // This is a declaration

Think of the extern keyword as a message from the compiler to the linker saying: "My programmer wants to use this variable in this file as an int named value. I'll leave a couple of question marks here and some information about value and I'll let the linker fix things." The linker then comes along and sees value defined in file 1 and makes a note of the memory address of where it gets stored. It then goes to the second file, finds the question marks left by the compiler, and fills in the memory address of value.

Simply stated, a declaration is an attribute list for a variable that has the information to allow you to use it. A definition is that same attribute list, but also allocates memory for that variable. Define and declare are two very different processes.

1 Like

econjack:
extern int value; // This is a declaration

so the point would be, why move them off to a helper file?

another approach would be to put your global vars into a header like this:

myHelpers.h

#ifndef MYHELPERS_H
#define MYHELPERS_H

bool myToggle = true;

#endif

and use the include directive in your sketch:

#include "myHelpers.h"

void setup(void) 
{
  myToggle = false;
}

void loop(void) 
{

}

but... it is not orthodox, you'd like to see everything you need in the file you are using, less library implementations that you depend on. those types of dependancies are sort-of standard C++.

i also wonder why you have so many what-must-be global variables that you need to push them off to another file?

If you create an Arduino project with the principal .INO file and a few other .INO files in the same folder the Arduino IDE will load the principal file first and then load the others in alphabetical order. A global variable will be visible in all files loaded after the file in which it is declared but not in files loaded before. So the best place for global variables is in the principal file.

Alternatively use the regular C/C++ approach mentioned by others.

...R

1 Like

In my larger projects I like to create a tab specifically for all my global declarations to make the project easy to navigate. But this can cause a problem if one of the globals is referenced in an .ino file with a name that is alphabetically sorted before the name of the globals tab. For this reason I prefix each of the tab names with A10, A20, A30, etc. so that I can order them as I like without worrying about the alphabetical order of the descriptive name that follows the prefix. The reason I don't use adjacent numbers is that I may later want to insert other tabs and this allows me to do so without changing the name of every tab that comes after that point, a trick from the old BASIC line numbering days. I put the includes in the main project tab, then a tab for constants, then globals, setup, loop, functions (may be multiple tabs), readme, then the last tab I use for a "to do" list.

pert:
In my larger projects ...

Sounds to me like time to use a "proper" editor for your code :slight_smile:

...R

I will continue to use the Arduino IDE's editor for one reason: Beta testing. I would love to switch to a better editor, which I already use for other purposes, but I'm committed to contributing to the Arduino project even if this means a bit of inconvenience. Using the Arduino IDE editor and the hourly builds has allowed me to report many bugs to the Arduino developers that I would not have found if using a different editor.

pert:
I would not have found if using a different editor.

well you still would if you used any other editor but you still compiled using Arduino toolchain.

the Arduino editor, and lets be honest here, is an anachronism.

you can't swing a dead cat not hit a better editor

Not true, here are 8 issues I reported that I would have only found through regular use of the Arduino IDE's editor:

I know there are more than that, those just happen to have the editor-refactor label so they were easy to find.

You need to keep in mind who the target users are for Arduino. All the features of your better editors will only steepen the learning curve and confuse the sort of people that Arduino is able to make microcontrollers accessible to. Of course the IDE's editor could be improved and that's precisely what I've tried to do, albeit in a very minor way. The external editor function is always there when people are ready for it.

Thanks for the responses, all. I may consider switching to a better editor at some point, but for now, for each tab I created separate .h and .ino files.

For example all the hardware-based values like pin numbers etc. would be declared and defined in hardware.h, and functions like void readDigital () are in hardware.ino... So that works.

Globals are bad, mmkay?

If you have units of work that fit into separate files then those units must have a defined contract between them. You can't just modify a variable inside another unit.

C++ defines a way of working with logical units called classes that structure the communication between different classes. Learn to use classes. Maybe some of them get used in multiple sketches, so they become libraries.

The usual way to do this is to declare the variable or function in a header file (.h or .hpp) usinf extern and to include that header file in the other files that need it.

That's how C has always worked.

pert:
but I'm committed to contributing to the Arduino project even if this means a bit of inconvenience.

Much appreciated. Thank you for that.

...R

PaulMurrayCbr:
The usual way to do this is to declare the variable or function in a header file (.h or .hpp) usinf extern and to include that header file in the other files that need it.

That's how C has always worked.

Agreed. I think that the C / C++ technique of .h and .cpp files is the way to go. That’s the way the Arduino libraries do it. Gives defined control over global and file-level variable / function scope. The use of multiple .ino files is a gimmicky kludge at best. Poor scope control and the alphabetical order thing could bite you.

The example below is non-functional, contrived, and probably poor use of globals. But, it illustrates the techniques.

Finally, as mentioned, using C++ classes gives even better abstraction and encapsulation. But, I’m an Old School C coder and have not (yet) learned C++.

Main (and only) .ino File:

#include "HelperFunctions.h"

void setup() {
  myStruct *ptr;
  initStuff();
  for (uint8_t i=0; i<numElements; i++) {
    globalArray[i].var2[0] = i+0.0;
    globalArray[i].var2[1] = i+4.0;
    globalArray[i].var2[2] = i+8.0;
    globalArray[i].var2[3] = i+16.0;
  }

  for (uint16_t i=0; i<numElements; i++) {
    ptr = doStuff(i);
    ptr->var2[2] += 32.0;    
  }
}

void loop() {
}

HelperFunctions.h:

#ifndef HELPER_FUNCTIONS_h
#define HELPER_FUNCTIONS_h
#include <Arduino.h>

typedef struct {
  uint8_t var1;
  float var2[4];
} myStruct;

extern myStruct globalArray[];        // Global variable
const extern uint16_t numElements;    // Global variable

extern void initStuff(void);          // Global function
extern myStruct *doStuff(uint16_t);   // Global function

#endif

HelperFunctions.cpp:

#include "HelperFunctions.h"

#define SIZE 10
myStruct globalArray[SIZE];               // Global variable
const uint16_t numElements = SIZE;        // Global variable
static myStruct privateArray[SIZE];       // File-level variable

void initStuff(void);                     // Global function
myStruct *doStuff(uint16_t);              // Global function
static void privateFunction(myStruct *);  // File-level function

void initStuff(void) {
  for (uint8_t i=0; i<SIZE; i++) {
    privateArray[i].var1 = i;
    globalArray[i].var1 = i;
  }
}

myStruct *doStuff(uint16_t dog) {
  privateFunction(privateArray+dog);
  return privateArray+dog;
}

static void privateFunction(myStruct *aPointer) {
  aPointer->var1++;
}