If you’ve spent time working with Arduino, chances are your first sketches started as a single .ino
file. Over time, as features grow, that once simple sketch often turns into a maze of functions, global variables, and scattered logic.
Maintaining, testing, or extending these large monolithic sketches can quickly become frustrating.
That’s where refactoring comes in: transforming your single-file .ino
sketch into a clean, modular C++ project. And when you pair this with a powerful build system like PlatformIO, you not only improve code quality but also gain better scalability, maintainability, and collaboration.
In this post, we’ll explore:
- Why refactoring matters.
- How PlatformIO changes your workflow.
- Step-by-step guide to refactor a typical
.ino
sketch into.cpp
and.h
modules. - Tips for organizing your codebase.
Let’s get started.
Why Refactor Your Arduino Sketch?
For many hobbyists, writing everything in a single file works fine at first. But as your projects grow, problems emerge:
- Hard-to-find bugs buried in hundreds of lines of code.
- Repeated code blocks that could be shared as functions or classes.
- Difficulty adding new features without breaking existing functionality.
- Nearly impossible testing or reuse in other projects.
By refactoring into proper C++ modules:
- You separate logic into cohesive parts.
- You get clearer, shorter files each focused on a single responsibility.
- You can reuse and test parts of your codebase more easily.
- You write code that is easier to understand — even for your future self.
And PlatformIO makes this process seamless.
Why Use PlatformIO for Refactoring?
PlatformIO is a modern alternative to the Arduino IDE, built on top of Visual Studio Code (VS Code) and other editors. It adds:
- A real C++ build system.
- Per-project library dependencies.
- Support for multiple boards and environments.
- Advanced debugging and unit testing.
When refactoring, PlatformIO's build system recognizes .cpp
and .h
files naturally — unlike the Arduino IDE, which preprocesses .ino
files in ways that can complicate modular code.
Step-by-Step Refactoring Guide
Imagine you have this classic Blink.ino
sketch (simplified):
int ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
It works, but let’s turn this into a maintainable project.
Step 1: Create a PlatformIO Project
- Install VS Code and the PlatformIO IDE extension.
-
Create a new project:
- Open the PlatformIO Home tab.
- Click “New Project”.
- Choose your board (e.g., Arduino Uno).
- Give your project a name (
ModularBlink
). - Click “Finish”.
This creates a folder structure like:
ModularBlink/
├── include/
├── lib/
├── src/
│ └── main.cpp
├── platformio.ini
└── ...
Step 2: Move Code to main.cpp
Instead of .ino
, PlatformIO uses standard C++ files.
Copy your sketch code into src/main.cpp
:
#include <Arduino.h>
int ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
Build and upload to check it works:
- Click the check mark (build).
- Click the right arrow (upload).
Step 3: Identify Separate Responsibilities
Look at what the sketch does:
- Defines a LED pin (
ledPin
). - Initializes the pin in
setup()
. - Blinks in
loop()
.
This blinking logic could belong in its own module.
Step 4: Create a Module
We’ll create a module to manage the LED blink.
In include/
folder, create two files:
BlinkLed.h
BlinkLed.cpp
(goes insrc/
)
BlinkLed.h
#ifndef BLINKLED_H
#define BLINKLED_H
#include <Arduino.h>
class BlinkLed {
public:
BlinkLed(int pin, unsigned long interval);
void begin();
void update();
private:
int ledPin;
unsigned long blinkInterval;
unsigned long lastToggleTime;
bool isOn;
};
#endif
BlinkLed.cpp
#include "BlinkLed.h"
BlinkLed::BlinkLed(int pin, unsigned long interval)
: ledPin(pin), blinkInterval(interval), lastToggleTime(0), isOn(false) {}
void BlinkLed::begin() {
pinMode(ledPin, OUTPUT);
}
void BlinkLed::update() {
unsigned long currentTime = millis();
if (currentTime - lastToggleTime >= blinkInterval) {
isOn = !isOn;
digitalWrite(ledPin, isOn ? HIGH : LOW);
lastToggleTime = currentTime;
}
}
Step 5: Use the Module in main.cpp
Update src/main.cpp
:
#include <Arduino.h>
#include "BlinkLed.h"
BlinkLed led(13, 1000); // Blink every 1000 ms
void setup() {
led.begin();
}
void loop() {
led.update();
}
Step 6: Build and Upload
Use the VS Code interface:
- Build (check mark).
- Upload (right arrow).
Your board should blink as before — but now your code is modular.
What Did We Achieve?
- Moved from
.ino
to proper.cpp
/.h
files. - Created a
BlinkLed
class that encapsulates LED behavior. main.cpp
is now short and easy to read.- It’s easier to change blink intervals, add new LEDs, or reuse the
BlinkLed
class in other projects.
Tips for Larger Projects
As projects grow, apply these ideas:
-
Group related functionality into classes or modules. Example: sensors, displays, networking, etc.
-
Separate platform-independent logic from Arduino-specific code. Makes future porting easier.
-
Use the
lib/
folder for libraries shared across projects. PlatformIO automatically includes them. -
Document your modules with comments and README files.
-
Write unit tests (PlatformIO has built-in support).
Organizing Folder Structure
A real-world project might look like:
MyProject/
├── include/
│ ├── DisplayManager.h
│ ├── SensorReader.h
│ └── ...
├── lib/
│ └── CustomLib/
│ ├── CustomLib.cpp
│ └── CustomLib.h
├── src/
│ ├── main.cpp
│ ├── DisplayManager.cpp
│ └── SensorReader.cpp
├── test/
│ └── test_main.cpp
├── platformio.ini
└── README.md
Common Questions
Q: Why not just stick to .ino
files?
Arduino IDE preprocesses .ino
files, which can hide header issues. In .cpp
/.h
, you control everything, making large projects safer.
Q: Do I need to write everything as a class? No, but classes help group state and behavior logically. For smaller tasks, free functions might suffice.
Q: What about libraries?
Add external libraries in platformio.ini
under lib_deps
.
Example:
lib_deps =
adafruit/Adafruit SSD1306
Conclusion
Refactoring from a single .ino
sketch to a structured C++ project is one of the best things you can do as your Arduino projects get bigger. Combined with PlatformIO’s modern build system, it makes your code easier to read, test, and extend — and makes you a better embedded developer. If you’ve never tried PlatformIO, now is the perfect time.