Arduino Internals: how Arduino IDE builds your code

As an embedded developer, you probably can’t help wondering what actually happens when Arduino IDE builds the sketch. What’s being compiled, where the files go, and how the toolchain turns the code into instructions able to run on the board?
You hit the spot! We’re about to take a closer look at the Arduino build process.
While building an Arduino sketch in the IDE seems as simple as clicking one button, under the hood it performs several important steps — some common to nearly every embedded development environment, and others unique to Arduino.
Understanding this process is a great way to move beyond the beginner stage and start thinking like an embedded developer.
Understanding Arduino IDE build process
- Arduino UNO R4 WiFi board
- Arduino IDE 2.3.6
- Linux Mint
If you’re using a different board, IDE version, or operating system — don’t worry! You can still follow along. Some details may just look a little different on your setup.
What you will learn?
After reading this article, you will:
- understand the standard build flow in embedded software development,
- learn about the additional steps performed during the Arduino build process,
- know where to find the build files and caches.
Let’s get started!
Let’s build a sketch together and go step through the build process. We’ll pretend we know nothing and analyze the build output to infer what the Arduino IDE actually did.
Build an example sketch
Open the Arduino IDE and select the correct board from Select Board menu. In my case it’s Arduino UNO R4 WiFi.
Load the Blink example and save the sketch in your sketchbook. Then, to trigger the build, click Verify/Compile and observe the Output window.
It looks that the sketch has been built successfully and is ready to upload to the board. The message contains just two lines because I haven’t changed the Arduino IDE’s default settings yet — by default, most of the build output is hidden.
Enable verbose output
To understand the build process better we need to enable the verbose output in IDE.
To do so, go to File-> Preferences and check Show verbose output setting for both compiling and upload. When you compile again,
the output will be complete.
Let’s break it down to atoms!
Board and package identification
In embedded software development, the compilation process is always target-dependent. Each hardware architecture — such as ARM or AVR — requires its own dedicated toolchain. That’s why the first lines in Output are identifying the board.
Before the build process starts, Arduino environment checks which board you are using, based on what you have selected in Select Board menu. Thanks to this, the compiled code will match the board’s architecture, hardware, pin mapping etc.
This board identity is called FQBN (Fully Qualified Board Name) and looks as follows:
|
|
FQBN is unique for each board. It consists of three segments:
- Vendor:
arduino
(it’s official Arduino package) - Architecture:
renesas_uno
(Renesas-based boards family) - Board:
unor4wifi
(exact model: Arduino Uno R4 WiFi).
If you choose wrong board, the code may compile but fail at runtime, or compilation may fail.
Compilation error: Missing FQBN (Fully Qualified Board Name)
means you have not selected any board from menu.Arduino specific directories
It’s also useful to know where the compilation artifacts end up and where the packages used for building a specific target are stored. This isn’t immediately obvious, since these files are placed in several hidden directories. However, their locations can be found in the build log. Let’s take a quick look at these directories.
Arduino15 directory
Arduino15 contains user preferences, downloaded board packages (cores, toolchains, board definitions) and libraries that Arduino automatically installs. The exact location depends on your operating system.
Files related to the target board can be found in the path displayed in the build log under the FQBN, for example:
|
|
There are several interesting files in this directory. One of them is boards.txt file, which lists all boards supported by that platform. Each board has its own section with key-value pairs that define how to compile, upload, and debug for that target.
Here’s the entry for the Arduino UNO R4 WiFi:
|
|
Thanks to those settings, once you select your board from the Select Board menu, the correct toolchain, compiler flags (such as MCU type and clock speed) and upload tool are selected automatically.
Toolchain
Arduino15 also contains the toolchain:.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/
.
Besides the compiler itself, toolchain contains other useful tools like objdump
. We will use it later!
In some cases, you may want to move the Arduino15 folder — for example, to free up space on your primary drive.
To do this:
-
Copy the Arduino15 directory to your desired location.
-
Open the file
~/.arduinoIDE/arduino-cli.yaml
and update thedirectories:data
path, for example:
|
|
-
Restart the Arduino IDE. It will now use the new Arduino15 location. Verify the build output to ensure everything is working correctly.
-
Once you’ve confirmed everything works correctly, you can safely delete the old folder.
Arduino core
The Arduino core directory provides the hardware-specific implementation of core Arduino functions.
It is located in the cores
subdirectory of your board package, for example:
|
|
Each board family, or platform, has its own core, tailored to the hardware and peripherals.
The Arduino core contains, among other files:
main.cpp
— defines the startup code that runs before yoursetup()
function and repeatedly calls yourloop()
function,Arduino.h
header — the main header file included by all sketches,- implementation of interrupts, UART communication, timing functions like
delay
and other low-level routines.
Open the main.cpp
and browse the logic that Arduino is adding around the code you implemented in the sketch.
Build directory
When you compile a sketch, the build artifacts (i.e. intermediate object files) are not stored in the same directory as your source code. It would make a mess inside your sketch folder, or even made conflicts when building the same sketch for different boards.
That’s why build files are always stored in separate build directory.
In case of Arduino IDE, the build directory is a per-sketch cache, created in a hidden folder.
You can find the path to this build directory in the build log. My sketch was built here:
|
|
If you open this folder, you’ll see the build artifacts, including:
- object files (
.o
), - preprocessed sources (
.cpp
), - final binaries (
.bin
,.hex
,.elf
).
.hex
, .bin
, .elf
and .map
inside the sketch sources
folder so it’s easy to find later.We already discussed board identification and the Arduino specific directories. Let’s move to actual build process.
General embedded software build process
At a high level, the Arduino build process is essentially a C++ build process adapted
for embedded targets, and with some Arduino specific features. The general C/C++ build pipeline is presented below:
Preprocessing
The first phase of every C/C++ program build is preprocessing.
In general, preprocessor expands macros like #include
, #define
, and conditional compilation (#if
, #ifdef
, etc.).
The output is a single expanded source file.
In Arduino, some additional steps are added around preprocessing stage.
Concatenating .ino files
If the sketch contains of multiple .ino
files, they are first concatenated into a single .cpp
file.
Detecting libraries used
Arduino uses a special build recipe (recipe.preproc.macros
[1])
to detect which libraries are required by your sketch.
During this step, the preprocessor scans the sketch for #include
s that reference library header files.
If a matching header is found, the corresponding library is automatically marked as needed and included in the build process.
|
|
As we see in build output, the compiler is invoked with the -E
flag, which tells it to stop after the preprocessing
stage and not produce any compiled output.
For a simple sketch like Blink, this stage isn’t very exciting: it doesn’t detect in any additional libraries. That’s why the build output doesn’t show anything under Detecting libraries used…. Open other example sketches, such as those from WiFiS3 or EEPROM, and you’ll see how more complex projects trigger detection of multiple libraries.
Why does Arduino detect libraries automatically?
The Arduino IDE compiles only the libraries that your sketch actually needs — no manual setup required. In most other development environments, this step is up to the developer: you have to configure which libraries or dependencies should be included in the build. Arduino takes care of this automatically, keeping things simple and beginner-friendly.
Generating function prototypes
The next stage in the build process is automatic function prototype generation. In C or C++, each function must be either defined or declared before it’s called. In Arduino, you don’t need to worry about that — you can write functions in any order, without adding their prototypes. How does it work? Arduino generates these prototypes for you!
Here’s what the build log shows:
|
|
Again, the compiler is invoked with the -E
flag, meaning only the preprocessor runs.
This is what happens now:
-
Preprocessing
The
MyBlink.ino.cpp
(file concatenated of all.ino
files) is input for the preprocessor:#include
s are expanded and macros replaces. The expanded file is stored as/tmp/1121713208/sketch_merged.cpp
. -
Scanning functions
Tool
ctags
is running over expandedsketch_merged.cpp
, to extract function symbols. -
Generating prototypes
Based on the
ctags
result, Arduino environment inserts forward declarations for you. This way, you don’t need to worry about undeclared functions, functions order, etc. -
Store prototypes into .ino.cpp
The resulting version is stored into
MyBlink.ino.cpp
. This is what the compiler will actually use later on.
Your simple sketch (MyBlink.ino
):
|
|
gets transformed into (MyBlink.ino.cpp
):
|
|
To summarize differences:
#include <Arduino.h>
is added,#line
macros are added — they keep compiler error messages pointing to the right lines in your.ino
file. Without them, errors would show up with the line numbers of the generated.cpp
, which would be super confusing.- function prototypes are added.
Compilation
Here’s where your code finally turns into machine instructions that the microcontroller can execute.
Sketch compilation
During this step, the compiler checks your code for syntax errors and converts it into the object code that can be linked with libraries later.
|
|
Compiler is invoked with -c
flag, which means compile only (and don’t link yet).
Input: MyBlink.ino.cpp
- the auto-generated sketch with prototypes, includes, and line directives.
Output: MyBlink.ino.cpp.o
- machine-code object file, ready to be linked later.
I shortened the compilation command, so it’s not in the snippet above, but in IDE we can observe a lots of compilation flags that were used. Some highlights:
-Os
→ optimize for size.-g3
→ include debug info at max level.-fno-rtti
,-fno-exceptions
→ strip C++ runtime features to save space.-nostdlib
→ don’t link against standard system libraries [1].-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
→ target an ARM Cortex-M4 with hardware floating point.-I
and@.../includes.txt
→ add search paths for Arduino core and variant headers.- Many
-D...
→ defines for board, CPU frequency, Arduino version, etc.
Most advanced IDEs let you change compiler settings like optimization level, debug info, or extra flags right from a project menu.
Arduino IDE is so simple that it doesn’t offer this option: you don’t get a button or menu for that.
If you want to change some settings, you’ve got several ways:
- Edit build recipes: you can change files like
platform.txt
orboards.local.txt
to change or add your own compilation flags, - modify
arduino-cli.yaml
from ArduinoIDE folder, - use Arduino CLI directly: the command-line tool gives you more flexibility and can build your sketch with whatever options you like.
At this point, your sketch has been turned into machine code, stored in MyBlink.ino.cpp.o
.
This file contains your setup()
and loop()
code, ready to be linked with the Arduino core and libraries in the next step.
Compiling libraries
|
|
For the Blink sketch this step is empty. In the previous phase (Detecting libraries used) no additional libraries were found.
Blink only relies on core Arduino functions such as pinMode
, digitalWrite
, and delay
, which are part of the board core, not separate libraries.
In more advanced sketches the output looks different. For example, in WiFiS3/ConnectWithWPA the WiFiS3 library is detected and its source files are compiled here.
Compiling core
|
|
The core is the foundation of every Arduino program. We disussed in in Arduino core chapter. Instead of recompiling the same core files every time you build, the Arduino IDE uses precompiled objects from a cache. It speeds up the compilation.
Linking everything together
Once all the individual files are compiled, the Arduino build system needs to link them into a single program. I shortened the link command to present only the most important stuff:
|
|
After this step, the final binary will be MyBlink.ino.elf
.
Please note which libraries are linked into the build:
libfsp.a
→ the Renesas Flexible Software Package, providing hardware drivers and low-level functions for peripherals.core.a
→ the Arduino core for this board. Libraries above are board support libraries. These are part of the Arduino board package (for the Uno R4 in this case). They are not optional user libraries, so they don’t appear in the detection phase.
Toolchain standard libraries:
-lstdc++
→ the C++ standard library,-lsupc++
→ low-level C++ runtime support,-lm
→ the math library,-lc
→ the standard C library,-lgcc
→ helper routines from GCC itself,-lnosys
→ stubs for system calls.
Finalizing build
Creating binary and hex files
After the ELF file (MyBlink.ino.elf
) is created, the build system uses objcopy
to generate hex
and bin
formats:
|
|
Both formats contain the same program, just encoded differently for different flashing tools.
Measuring the program size
Finally, the build system reports memory usage with arm-none-eabi-size
:
|
|
Cleaning the build
Unfortunately, Arduino IDE does not offer the Clean build
or Force rebuild
option ([1],
[2],
[3]). The workaround for it is to manually delete the cached build directories we mentioned earlier.
More reading
- Build Sketch Process from docs.arduino.cc
- Find sketches, libraries, board cores and other files on your computer from support.arduino.cc
- ARM Reverse Engineering Notes: Compilation
- Open issues in arduino-cli related to build process
- De-Mystifying Libraries - How Arduino IDE Finds and Uses Your Files - OhioIoT