including .ino files in main sketch

Hello!

Simply question, found many threads about it but cant find simple sollution.

My idea is such:
main.ino:

int x=0;
int y=0; //to use in external file

void setup() {
    Serial.begin(19200);
    Serial.print(x); //variable from "main.ino", should be "0"

    external(); //calculate variable in procedure/function in external file 

    Serial.print(y); //variable from "external.ino", should be "y=x+1" -> "y=0+1" -> "1"
}

void loop() {
}

and external.ino

void external () {
  y=x+1;  //variable from "main.ino"
  }

My goals are:

  1. use only Arduino IDE
  2. use the same variables in all included files
  3. don't use libraries and .h/.cpp files
  4. do it easiest way :slight_smile:

I've tried #include "procedures.ino", changing procedures.ino into procedures.cpp and #include procedures.cpp, use "extern" directives - without success.

Is it possible to split main sketch into separated ones (=move functions, procedures) AND use everywhere the same variables (declared in main sketch?

Regards!
Art

(deleted)

Yes, sure, I try to use tabs.First tab - "main.ino". Creating new tab, named "external.ino"/"external.cpp" and saving into a same directory.

IDE see both files, but it doesn't work, "main.ino" didn't see variables in "extrenal.ino/.cpp" and/or vice versa.

the .ino files are concatenated for conversion and compilation. the file with the same name as the project folder is first and other tabs are attached in alphabet order. global variable visibility is from the definition position to the end of the concatenated file

3 Likes

Don't declare global variables in secondary tabs, keep all globals in the top of the main sketch. You can try to find the temporary folder where the IDE stores your merged sketch before it is compiled and see how stuff is combined.

1 Like

(deleted)

I guess you are developing a large sketch, otherwise you'd be happy with the monolithic program approach of having everything in one file (or compilation unit).

My suggestion is to start to use the standard approach and move logically separate modules into their own .h and matching .cpp file . This makes these modules more easily re-usable and easier to maintain.

You can get started very simply. A very basic guide ignoring some of the grey areas:

.h contains statements to expose objects which have to be visible to other compilation units such as variables (declared external), function prototypes etc. A .h file has a special structure to prevent it being active multiple times in a compilation unit. It may also need to include Arduino.h so that special Arduino features (e.g. byte) work.

.cpp contains definitions of those objects (i.e. the statements which allocate storage to them ), supporting code, and of course, includes the matching .h file.

1 Like

You must NOT have a file called main and you must not have any function called main these are provided by the IDE and you must stay away from the name main.

Mark

@6v6gt, why .h and .cpp. it requires to maintain two files and twice so many tabs are there. working with ino files is so convenient. the encapsulation is not so perfect but for the Arduino sketch it is ok. if something is reusable then I put h and cpp into library. did you see my Arduino project?

#ifdef __IN_ECLIPSE__
//This is a automatic generated file
//Please do not modify this file
//If you touch this file your change will be overwritten during the next build
//This file has been generated on 2018-09-01 16:54:38

#include "Arduino.h"
#define I2C_ADC121         0x50
#include <Wire.h>
extern const unsigned long EVENTS_SAVE_INTERVAL_SEC;
extern const char eventLabels[];
extern const char* eventLongLabels[];
extern const char* eventLongLabels[];
#define EVENTS_FILENAME "EVENTS.DAT"
#include <Grove_LED_Bar.h>
#include <StreamLib.h>
#include <TimeLib.h>
#include <MemoryFree.h>
#include <Ethernet.h>
#include <SD.h>
#define FS SD
#define Serial SerialUSB
#include <OTEthernet.h>
#include <SDU.h>
#include "consts.h"
#define BLYNK_PRINT Serial
#define BLYNK_NO_BUILTIN // Blynk doesn't handle pins
#include <BlynkSimpleEthernet.h>
#define STATS_FILENAME "STATS.DAT"

void balboaSetup() ;
void balboaReset() ;
void balboaLoop() ;
void battSettLoop() ;
boolean battSettRead(FormattedPrint& out) ;
const char* bit2s(int value, byte mask) ;
int battSettControl(boolean chargeControlOn, boolean dischargeControlOn) ;
int battSettSetLimit(byte reg, int limit) ;
void beeperLoop() ;
void alarmSound() ;
void beep() ;
void blynkSetup() ;
void blynkLoop() ;
void updateWidgets() ;
void buttonSetup() ;
void buttonLoop() ;
void csvLogSetup() ;
void csvLogLoop() ;
void csvLogPrintJson(FormattedPrint& out) ;
void elsensSetup() ;
void elsensLoop() ;
boolean elsensCheckPump() ;
byte overheatedSecondsLeft() ;
int readElSens() ;
unsigned short elsensAnalogRead() ;
void eventsSetup() ;
void eventsLoop() ;
void eventsWrite(int newEvent, int value1, int value2) ;
boolean eventsSaved() ;
void eventsSave() ;
byte eventsRealCount() ;
void eventsPrint(FormattedPrint& stream) ;
void eventsPrint(FormattedPrint& s, int ix) ;
void eventsPrintJson(FormattedPrint& stream) ;
void eventsPrintJson(FormattedPrint& stream, int ix) ;
void eventsBlynk() ;
int eventsCompare(const void * elem1, const void * elem2) ;
void ledBarSetup() ;
void ledBarLoop() ;
void manualRunLoop() ;
byte manualRunMinutesLeft() ;
void modbusSetup() ;
boolean modbusLoop() ;
void modbusClearData() ;
boolean requestSymoRTC() ;
boolean requestInverter() ;
boolean requestMeter() ;
boolean requestBattery() ;
boolean modbusError(int err) ;
int modbusRequest(byte uid, unsigned int addr, byte len, short *regs) ;
int modbusWriteSingle(unsigned int address, int val) ;
int modbusConnection() ;
void pilotLoop() ;
unsigned short power2pwm(int power) ;
void setup() ;
void loop() ;
void shutdown() ;
void handleSuspendAndOff() ;
void clearData() ;
boolean handleAlarm() ;
boolean restHours() ;
boolean turnMainRelayOn() ;
boolean networkConnected() ;
void statsSetup() ;
void statsLoop() ;
int statsEvalCurrentPower() ;
void statsAddMilliwats() ;
void statsSave() ;
int statsConsumedPowerToday() ;
void statsPrint(FormattedPrint& out) ;
void statsPrint(FormattedPrint& out, const char *label, Stats &stats) ;
void statsPrintJson(FormattedPrint& out) ;
void statusLedSetup() ;
void statusLedLopp() ;
void statusLedShortBlink() ;
void susCalibLoop() ;
void telnetSetup() ;
void telnetLoop(boolean log) ;
void valvesBackSetup() ;
void valvesBackReset() ;
void valvesBackLoop() ;
void valvesBackStart(int v) ;
boolean valvesBackExecuted() ;
unsigned short valvesBackTempSensRead() ;
void watchdogSetup() ;
void watchdogLoop() ;
void WDT_Handler(void) ;
void webServerSetup() ;
void webServerLoop() ;
void webServerRestRequest(char cmd, ChunkedPrint& chunked) ;
void webServerServeFile(const char *fn, BufferedPrint& bp) ;
void printValuesJson(FormattedPrint& client) ;
void printAlarmJson(FormattedPrint& client) ;
const char* getContentType(const char* ext);
void wemoLoop() ;
boolean wemoPowerUsage() ;
int wemoRequest(const char* service, const char* action, const char* param, const char* value, char* response, size_t size) ;

#include "Regulator.ino"

#include "Balboa.ino"
#include "BattSett.ino"
#include "Beeper.ino"
#include "Blynk.ino"
#include "Button.ino"
#include "CsvLog.ino"
#include "ElSens.ino"
#include "Events.ino"
#include "LedBar.ino"
#include "ManualRun.ino"
#include "Modbus.ino"
#include "PowerPilot.ino"
#include "Stats.ino"
#include "StatusLed.ino"
#include "SusCalib.ino"
#include "Telnet.ino"
#include "ValvesBack.ino"
#include "Watchdog.ino"
#include "WebServer.ino"
#include "WemoInsight.ino"

#endif

holmes4:
You must NOT have a file called main

That's no longer a problem with any modern version of the Arduino IDE (something like 1.6.5 and newer). The sketch is now renamed to main.ino.cpp instead of main.cpp after sketch preprocessing so there's no conflict with the core library's main.cpp.

holmes4:
you must not have any function called main

That's still true, unless you want to override the default main() (which is only recommended for more advanced users).

Thank you @Juraj @6v6gt @spycatcher2k @Danois90 for help!

For future generations: I found simple solution :slight_smile:

  1. First: Create new tab called "variables.h" - here I put ALL variables used in program, they will be visible in any place:
int x=7;
int y=9;
int z=8;
  1. Second: Create main sketch, called for example "MainOne.ino":
#include "variables.h"

void setup() {
    Serial.begin(19200);
    
    Serial.print("variable from MainOne   in MainOne:"); Serial.println(x); 
    
    y = 5;
    
    external(); //calculate variable in procedure/function in external file
    
    Serial.print("variable from SecondOne in MainOne:"); Serial.println(z+1);
}

void loop() {
}

As it is demonstrated, I'm including only variables.h file - nothing more!

  1. Third: create in new tab (for example) "SecondOne.ino", sketch and put there additional procedures:
void external () {
    z = 2 * x;
    Serial.print("variable from SecondOne in SecondOne:"); Serial.println(y);
    Serial.print("variable from MainOne   in SecondOne:"); Serial.println(z);

}

Take a look: there is no any "#include SecondOne.ino" in MainOne - it is included automatically!

In this situation all variables are defined in variables.h file, and can be used in any place - common for all sketchs. All .ino files are connected by compiler into one:

#include <Arduino.h>
#line 1 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\MainOne.ino"
#line 1 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\MainOne.ino"
#include "variables.h"

#line 3 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\MainOne.ino"
void setup();
#line 15 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\MainOne.ino"
void loop();
#line 1 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\SecondOne.ino"
void external();
#line 3 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\MainOne.ino"
void setup() {
    Serial.begin(19200);
    
    Serial.print("variable from MainOne   in MainOne:"); Serial.println(x); 
    
    y = 5;
    
    external(); //calculate variable in procedure/function in external file
    
    Serial.print("variable from SecondOne in MainOne:"); Serial.println(z+1);
}

void loop() {
}

#line 1 "C:\\Users\\user_name\\Documents\\Arduino\\MainOne\\SecondOne.ino"
void external () {
    z = 2 * x;
    Serial.print("variable from SecondOne in SecondOne:"); Serial.println(y);
    Serial.print("variable from MainOne   in SecondOne:"); Serial.println(z);

}

which results and the end:

variable from MainOne   in MainOne:7
variable from SecondOne in SecondOne:5
variable from MainOne   in SecondOne:14
variable from SecondOne in MainOne:15

Voila! :slight_smile:

3 Likes

if you replace #include "variables.h" with it's contents, it will be the same. you can't use that include in any other ino. h file should not contain memory allocations, only constants, declarations and type definitions

you project is called MainOne?

Yes, MainOne is "main" program/project. SecondOne is part, where additional functions/procedures are stored.

As mentioned by @Juraj - it is to separate big project into smaller, "functional" parts. For example, I'm using display where images are shown. It tooks more than 2kB const data with bitmaps definitions, which take a lot of place in main project - and needs to a lot of scrolling from end of file to begining while making changes. Putting them into separated "variables.h" let me "forgot" it and slim down a code.

The main problem was visibility of a variables. Variables defined in MainOne were visible only in MainOne. And in SecondOne was visible in a SecondOne. It was possible to work around using "extern" defitions or re-declaratrion - but it was look very ugly and was a possible hard-to-find errors.

Of course, it is possible to do such write own libraries, but needs to use constructors, classes, public, private, headers and others programmers nerds vocabulaty, makes headache ;-). I was looking for easier way, because it is Arduino :wink:

Juraj:
@6v6gt, why .h and .cpp. it requires to maintain two files and twice so many tabs are there. working with ino files is so convenient. the encapsulation is not so perfect but for the Arduino sketch it is ok. if something is reusable then I put h and cpp into library. did you see my Arduino project?

#ifdef __IN_ECLIPSE__

//This is a automatic generated file
//Please do not modify this file
//If you touch this file your change will be overwritten during the next build
//This file has been generated on 2018-09-01 16:54:38

#include "Arduino.h"
#define I2C_ADC121         0x50
#include <Wire.h>
extern const unsigned long EVENTS_SAVE_INTERVAL_SEC;
extern const char eventLabels[];
extern const char* eventLongLabels[];
extern const char* eventLongLabels[];
#define EVENTS_FILENAME "EVENTS.DAT"
#include <Grove_LED_Bar.h>
#include <StreamLib.h>
#include <TimeLib.h>
#include <MemoryFree.h>
#include <Ethernet.h>
#include <SD.h>
#define FS SD
#define Serial SerialUSB
#include <OTEthernet.h>
#include <SDU.h>
#include "consts.h"
#define BLYNK_PRINT Serial
#define BLYNK_NO_BUILTIN // Blynk doesn't handle pins
#include <BlynkSimpleEthernet.h>
#define STATS_FILENAME "STATS.DAT"

void balboaSetup() ;
void balboaReset() ;
void balboaLoop() ;
void battSettLoop() ;
boolean battSettRead(FormattedPrint& out) ;
const char* bit2s(int value, byte mask) ;
int battSettControl(boolean chargeControlOn, boolean dischargeControlOn) ;
int battSettSetLimit(byte reg, int limit) ;
void beeperLoop() ;
void alarmSound() ;
void beep() ;
void blynkSetup() ;
void blynkLoop() ;
void updateWidgets() ;
void buttonSetup() ;
void buttonLoop() ;
void csvLogSetup() ;
void csvLogLoop() ;
void csvLogPrintJson(FormattedPrint& out) ;
void elsensSetup() ;
void elsensLoop() ;
boolean elsensCheckPump() ;
byte overheatedSecondsLeft() ;
int readElSens() ;
unsigned short elsensAnalogRead() ;
void eventsSetup() ;
void eventsLoop() ;
void eventsWrite(int newEvent, int value1, int value2) ;
boolean eventsSaved() ;
void eventsSave() ;
byte eventsRealCount() ;
void eventsPrint(FormattedPrint& stream) ;
void eventsPrint(FormattedPrint& s, int ix) ;
void eventsPrintJson(FormattedPrint& stream) ;
void eventsPrintJson(FormattedPrint& stream, int ix) ;
void eventsBlynk() ;
int eventsCompare(const void * elem1, const void * elem2) ;
void ledBarSetup() ;
void ledBarLoop() ;
void manualRunLoop() ;
byte manualRunMinutesLeft() ;
void modbusSetup() ;
boolean modbusLoop() ;
void modbusClearData() ;
boolean requestSymoRTC() ;
boolean requestInverter() ;
boolean requestMeter() ;
boolean requestBattery() ;
boolean modbusError(int err) ;
int modbusRequest(byte uid, unsigned int addr, byte len, short regs) ;
int modbusWriteSingle(unsigned int address, int val) ;
int modbusConnection() ;
void pilotLoop() ;
unsigned short power2pwm(int power) ;
void setup() ;
void loop() ;
void shutdown() ;
void handleSuspendAndOff() ;
void clearData() ;
boolean handleAlarm() ;
boolean restHours() ;
boolean turnMainRelayOn() ;
boolean networkConnected() ;
void statsSetup() ;
void statsLoop() ;
int statsEvalCurrentPower() ;
void statsAddMilliwats() ;
void statsSave() ;
int statsConsumedPowerToday() ;
void statsPrint(FormattedPrint& out) ;
void statsPrint(FormattedPrint& out, const char label, Stats &stats) ;
void statsPrintJson(FormattedPrint& out) ;
void statusLedSetup() ;
void statusLedLopp() ;
void statusLedShortBlink() ;
void susCalibLoop() ;
void telnetSetup() ;
void telnetLoop(boolean log) ;
void valvesBackSetup() ;
void valvesBackReset() ;
void valvesBackLoop() ;
void valvesBackStart(int v) ;
boolean valvesBackExecuted() ;
unsigned short valvesBackTempSensRead() ;
void watchdogSetup() ;
void watchdogLoop() ;
void WDT_Handler(void) ;
void webServerSetup() ;
void webServerLoop() ;
void webServerRestRequest(char cmd, ChunkedPrint& chunked) ;
void webServerServeFile(const char fn, BufferedPrint& bp) ;
void printValuesJson(FormattedPrint& client) ;
void printAlarmJson(FormattedPrint& client) ;
const char
getContentType(const char
ext);
void wemoLoop() ;
boolean wemoPowerUsage() ;
int wemoRequest(const char
service, const char* action, const char* param, const char* value, char* response, size_t size) ;

#include "Regulator.ino"

#include "Balboa.ino"
#include "BattSett.ino"
#include "Beeper.ino"
#include "Blynk.ino"
#include "Button.ino"
#include "CsvLog.ino"
#include "ElSens.ino"
#include "Events.ino"
#include "LedBar.ino"
#include "ManualRun.ino"
#include "Modbus.ino"
#include "PowerPilot.ino"
#include "Stats.ino"
#include "StatusLed.ino"
#include "SusCalib.ino"
#include "Telnet.ino"
#include "ValvesBack.ino"
#include "Watchdog.ino"
#include "WebServer.ino"
#include "WemoInsight.ino"

#endif

That is certainly an impressive amount of code and a very comprehensive system you've developed. I guess around 2000-3000 lines of your own code. Clearly, multiple .ino files is a way to go with the Arduino development concept.
However, I like the discipline imposed by the conventional .cpp/.h structure which limits interaction between code modules to that explicitly declared.
How long, incidentally, does it take to compile/link because I imagine it has to recompile all the .ino files each time even if only one has changed ?

6v6gt:
However, I like the discipline imposed by the conventional .cpp/.h structure which limits interaction between code modules to that explicitly declared.

+1 for that. Being able to restrict functions and variables to file-level scope really helps to modularize your code. Another advantage (obviously doesn't apply to everyone) is that if your coding interests take you to a job in industry doing embedded development, you'll already know how to do it the right way.

To further impose discipline on my coding, I use the Sloeber / Eclipse IDE which forces one to declare proper function prototypes. This IDE also has lots of features that make it easier to trace through and understand complex code -- especially if it's not your own.

I really can't do serious development work in the Arduino IDE. However, when coding examples and libraries, I always drop back to it just to confirm what I've written works there.

gfvalvo:
To further impose discipline on my coding, I use the Sloeber / Eclipse IDE which forces one to declare proper function prototypes.

I use Sloeber too. No prototypes are needed in ino files with Sloeber. Sloeber uses the same Arduino builder as Arduino IDE.

In my Arduino project I decided after long thought to go the "multiple ino code separation way" with almost no encapsulation of partial functionality. I could encapsulate functionality into classes with declarations in h files, but it would be only more work for me and harder to understand and reuse for a hobby coder.

The current professional software (development) for microcontrolers is very different from Arduino.

6v6gt:
That is certainly an impressive amount of code and a very comprehensive system you've developed. I guess around 2000-3000 lines of your own code. Clearly, multiple .ino files is a way to go with the Arduino development concept.
However, I like the discipline imposed by the conventional .cpp/.h structure which limits interaction between code modules to that explicitly declared.
How long, incidentally, does it take to compile/link because I imagine it has to recompile all the .ino files each time even if only one has changed ?

Thank you. Is 2400 lines of code in the project and 400 in my StreamLib.

Build after Clean in Eclipse (so including core and libraries) is 16.5 seconds on NUC7i3. It doesn't recompile all ino files after a change in one of them. You can see they are included into the generated main file I added to the post, not merged to one file.

Juraj:
I use Sloeber too. No prototypes are needed in ino files with Sloeber. Sloeber uses the same Arduino builder as Arduino IDE.

I don't use ino files with Sloeber. I choose "Default cpp file" when starting a project. That put setup() and loop() in a .cpp file. That way prototypes are required for every function reference that isn't previously defined in the file.

I guess it's personal preference. That's the standard I use for my code.