Refactoring Arduino Sketches into Clean C++ Projects with PlatformIO

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

  1. Install VS Code and the PlatformIO IDE extension.
  2. 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 in src/)

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.