Department of Engineering

Using multiple source file with arduinos

In the IDP you might be writing several hundred lines of C++ code. This document will try to explain how to organise your files.

Background information - compiling in general

Compilation is a multi-stage process (an example from another project is on the right). The first stage is pre-compilation, when the lines beginning with # are processed. When the pre-compiler reads

#include "header.h"

in a file called main.cc, it will read in the contents of header.h from the current folder (like on-the-fly copy-pasting). As far as the next stage of compilation's concerned, it's as if contents of "header.h" were in "main.cc". The compilation stage converts the source code into low-level fragments. These are joined up along with other libraries of code to produce a machine-code file that the CPU can run.

Calling functions from other files

Suppose you have created a function in navigation.cc called turn_left() and you tried to call it from a file called main.cc file. Unless you're careful the compiler won't like this because it wants to check that you've called the function correctly (with the correct number of input parameters, etc) but you've not told it that information. That information can be supplied to the compiler when it reads main.cc by supplying a function prototype. A function prototype is like the first line of a function with a semicolon at the end, so putting

int turn_left();

at the top of the main.cc file would let you use the turn_left() function from main.cc.

Avoiding code duplication

Repeating the prototype at the top of each file is error-prone. If instead you put the prototypes for all the functions in header.h and include it in each file, you won't need to have lots of copies of the prototypes. Note that there's no need to have the code of the functions in header.h, just the prototypes suffice.

Global variables

It's possible that all these files might need to access the same variable. For example, the robot_link rlink variable might be used in all the files. Putting robot_link rlink; at the top of each file won't work, because you'll have a separate variable in each file. Putting robot_link rlink; in header.h has the same effect.

The solution is to

  • create the variable in one (and only one) file outside of all functions.
  • in the other files put extern robot_link rlink;. This is telling the compiler that external to the current file, there's already a variable called rlink

In fact, you can put extern robot_link rlink; in header.h even if the file where you create rlink includes header.h - the compiler will understand what you want.

Arduino

The standard Arduino environment does some extra work behind the scene when you compile a *.ino, adding an include file for the Arduino core, etc. If you have multiple *.ino in the same folder, the compilation process will concatenate the source files, starting with the file that matches the folder name followed by the others in alphabetical order. It will also generate function prototypes on-the-fly. See "What does the IDE change in my sketch" and "Arduino Build Process" for details.

It may be preferable to use a more standard C++ approach - have just one, minimal *ino file and write extra code in *.h and *.cpp files, using the C++ ideas described above. The *.cpp files will be automatically compiled and used when creating the program, though you'll need to write and include the prototypes yourself. Note that you'll need to have #include <Arduino.h> in your *.cpp files to use Arduino facilities - the IDE won't add them for you.

Summary

  • Use files to keep related code together (and unrelated code apart)
  • Use header files for prototypes, externs, and constants, not for code.
  • Don't use #include to include source files that have code in them. If your project's code was in main.cc, navigation.cc and motor.cc you could, in main.cc, have
    #include "navigation.cc"
    #include "motor.cc"
    
    and compile main.cc it might well work, but you're losing many of the benefits of having multiple files. Because the pre-compiler reads in these 2 files, the compiler would essentially process one big file.