Yeah yeah, I know people ask this type of thing a lot, but I couldn't find an answer anywhere.

I understand that you can have multiple .ino files in one sketch, but I don't just want functions and variables separated, I want functionality.

I am writing a Braille program for the NodeMCU -- It takes a message from a served website and moves servos to emulate the appropriate Braille. I want to have 2 files: one to serve the website and get input from it (website.ino), and the other one to manipulate the servos(braille.ino). The only problem is, I can't figure out where to declare/attach the servos.

I plan to have a list of servos in braille.ino that will be looped over with each servo being set to a different value. If I declare them in website.ino (my "main" file, ie, with a setup and loop), I will have to pass them to my braille function every time. On the other hand, if I choose to declare them as globals in my braille.ino, where would I attach them? Can I have another setup in braille.ino?

  • I wanted the same and my solution was to use MS Visual Studio with Arduino IDE from Visual Micro. It makes using multiple files easy and has lots of additional advantages. ![enter image description here](i.stack.imgur.com/YQeBV.png)
    – Edgar
    Feb 15, 2019 at 3:21
  • 2
    just have code in the "main" .ino call a function you define in any subsequent .ino Feb 15, 2019 at 6:13
  • You can use VS Code with the extension "vscode-arduino". It will allow you to manage multiple .ino files as Tabs as in the Arduino IDE does. Plus the advantages of intellisense. Checkout the Maker.pro website for a how to: maker.pro/arduino/tutorial/… Aug 23, 2021 at 15:58

2 Answers 2


What isn't obvious is that with Arduino, you can have normal C and C++ files in the same directory as the .ino file. These can have functions and variables inside them, and Arduino will link them into the main program too. You only need one .ino file - all the rest can be in modules with a .c or .cpp (for C++) extension.

That way, you can group "helper" functions in different files by category - for example, a BrailleServo.c file. These contain the functions and variables for just that category.

If you haven't programmed native C/C++:

  • Put the functions and variables in the BrailleServo.c file, and then repeat the functions' header in the BrailleServo.h file.
  • Inside the BrailleServo.c file, at the top, put the following line:

    #include "BrailleServo.h"

    Note the quotes (" "), not angle brackets (< >)

  • Inside the .ino file, put the same #include line. That tells the module that "there are other functions elsewhere, and these are their definitions".

The only problem? Getting the silly Arduino environment to let you add .c and .h files to that directory. You could just use Notepad, but there's a trick (if you're using Windows):

  1. Choose File|Open…
  2. Right-click the pane with the files in it, and choose New >|Text Document
  3. Give it a complete name, and make sure you replace the .txt part of the name too with .c or .h. You may get the "If you change the extension, the file may be unusable" warning - just click OK.
  4. Select the new (blank) file in the file pane, and a new tab will appear.

Note that 3. above assumes that you've told Windows File Explorer to NOT hide the extensions of known file types. I always do that - if you haven't, you'll need to. With:

  • Windows 10: In any folder's View tab, tick the File name extensions checkbox;
  • Windows 7: In Control Panel | Appearance and Personalization | Folder Options' View tab, untick Hide extensions for known file types

Here are three examples:


// BrailleServo.h

// Call this function once in setup()
void BrailleInit();

// This function activates the six servos to form the passed-in pattern.
void BrailleShow(byte pattern);


// BrailleServo.c

#include "BrailleServo.h"

// The number of servos
const byte NumServos = 6;

// These are the pins for the six servos
const byte servo[NumServos] = { 3, 4, 6, 7, 9, 10 };

// This initialises the six servos
void BrailleInit() {
   for (byte i=0; i<NumServos; ++i) {
      pinMode(servo[i], OUTPUT);
      // Do what else you need to do with servo[i]
   } // for
} // BrailleInit()

// This is how to use the six servos to form the passed-in pattern
void BrailleShow(byte pattern) {
   // … Complicated code here …
} // BrailleShow(pattern)


// Main.ino

#include "BrailleServo.h"

void setup() {
   // Other stuff
} // setup()

void loop() {
} // loop()

Incidentally, for ideas you may want to check out my answer to a more generic Braille question.

  • Thanks so much! I guess I wasn't really thinking about having the servos do things until they were attached, but i guess that's what happens when you work overtime. I don't think I'll bother re-encoding the letters, since I already have arrays of true-false, but thanks anyway! Feb 15, 2019 at 18:18

It is important to know that the tabs/ino files are concatenate into one by the builder. The order of the concatenation is the same as the order of tabs in IDE. First is the main ino file with the same name as the project. The rest of the files is concatenated in alphabetical order.

The concatenated ino file determines the visibility of variables. All ino files 'see' the variables in the main ino file, but variables in other ino files are visible only in ino file with names later in alphabetical order.

For functions the builder generates function prototypes to the beginning of the resulting cpp file, so as usual all functions are visible everywhere in all ino files.

One way to work with additional tabs is group is to write xySetup() and xyLoop() functions in the xy.ino file and then call this functions from the main setup() and loop(). My main Arduino project is created this way, so it is a big and complex example of this approach.

To illustrate, this is the resulting cpp file entering the cpp part of the build toolchain (scroll it to end):

#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[];
#include <Grove_LED_Bar.h>
#include <StreamLib.h>
#include <TimeLib.h>
#include <MemoryFree.h>
#include <UIPEthernet.h>
#define ETHERNET
#include <ArduinoOTA.h>
#include <SD.h>
#define FS SD
#define Serial SerialUSB
#include "consts.h"
#define BLYNK_PRINT Serial
#define BLYNK_NO_BUILTIN // Blynk doesn't handle pins
#include <BlynkSimpleUIPEthernet.h>
#include <RTCZero.h>

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 beeperTone(int freq, uint32_t time) ;
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"

Not the answer you're looking for? Browse other questions tagged or ask your own question.