· Arduino  · 5 min read

Refactoring Arduino Sketches into Clean C++ Projects with PlatformIO

Refactoring your Arduino `.ino` sketches into clean C++ modules makes your projects easier to maintain, scale, and reuse. In this guide, learn how to transform a single-file sketch into a structured PlatformIO project using .cpp and .h files, and discover how this modern workflow can make your code more robust and professional.

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.

  • embedded-systems
  • arduino
  • platformio
  • c
  • arduino-programming
  • refactoring
  • modular-code
  • arduino-projects
  • visual-studio-code
  • clean-code

Related articles

View All Articles »

Organizing Multi-File Projects in PlatformIO: Best Practices for Scalability

As embedded projects grow beyond a few hundred lines of code, managing a single main.cpp file becomes a recipe for confusion and bugs. PlatformIO offers a powerful and flexible structure that supports professional development workflows—but only if used effectively. This article explores best practices for organizing multi-file PlatformIO projects, focusing on clean modular design, proper use of src/ and include/, header file management, and long-term scalability. Whether you're refactoring a messy prototype or starting a new embedded system from scratch, a well-organized project layout is key to building reliable, maintainable firmware.

Integrating Git into Your PlatformIO Workflow for Version Control

Version control is no longer just for large software teams—it's a vital tool for anyone building embedded projects with PlatformIO. In this guide, you'll learn how to integrate Git into your PlatformIO workflow to manage code changes, isolate new features, and track firmware releases with precision. From setting up a clean .gitignore to using branches effectively and tagging stable builds, this post walks you through a professional development process tailored for Arduino projects. Whether you're a solo tinkerer or preparing for collaboration, Git will help you write better code and recover faster when things go wrong.

Creating and Managing Custom Board Definitions in PlatformIO

PlatformIO supports hundreds of official development boards out of the box, but what if you're working with a lesser-known Arduino clone or a custom-designed board? In this post, we’ll walk through how to create and manage custom board definitions in PlatformIO using VS Code. You’ll learn how to define board-specific settings like MCU type, upload speed, and memory layout so you can fully integrate unsupported boards into your PlatformIO workflow. Whether you’re building with a cheap ATmega328P-based board from AliExpress or your own PCB design, this guide will help you make it behave like a first-class citizen in PlatformIO.

Debugging Arduino Code with PlatformIO

Learn practical, real-world techniques for debugging your Arduino sketches using PlatformIO—whether you only have your board and a USB cable, or you decide to explore optional hardware debuggers. Discover why the Serial Monitor remains the most powerful tool, how to use LEDs and code structure to your advantage, and what changes when you step into the world of hardware debugging.