Event-Based Firmware Example

published: August 5, 2019 — last modified: October 23, 2025

If you read the previous articles about event-based firmware, modularisation and templates, you may wonder how to combine all these concepts in your firmware.

I created a working firmware example, based on an event loop. In contrast to the minimal example code in my articles, this one contains everything you need to start a project.

Requirements and Setup

To test and use the code, you need a SAM-D based microcontroller board; For example an Arduino Zero or Adafruit Feather M0. You also need the latest version of the Arduino IDE to compile and upload the code.

Setup the circuit as shown in the next illustration:

Source Code

Download the source code from the following GitHub repository:

Demo Video

I created a short video demonstrating this simple firmware:

We use two buttons to control the device: a left and a right button. A short press will move the “display” one step to its direction. A long press repeats the command similar to the cursor keys on your keyboard.

Design

I split the firmware into four parts:

  • The logic in the main file. in the main file.
  • The “Buttons” module.
  • The “Display” module.
  • A support library in the src subdirectory. subdirectory.
    • Including the event loop.
    • Duration definitions.
    • Flags.
    • Ring buffer.

I adapted files from my HAL layer for the support library to the Arduino IDE. If you read my posts about C++, you will recognise and understand the functionality provided by the library. In contrast to the examples in my articles, these files contain productive code which is used in real projects.

If you study the design illustration, you notice an important fact:

The “Buttons” module doesn’t access the “Display” module and there is no access in the other direction. This is an important design decision to encapsulate the module functionality. It will not only make your code clean and safe but also make it extensible and reusable.

Initialisation

void setup()
{
    // Initialise all modules.
    Buttons::initialize();
    Display::initialize();
    
    // Register the button press processor.
    event::mainLoop().addPollEvent(&processButtonPresses);
}

We call the initialize() function from each module in the setup() function. With the last line, we add a polling event to process key presses from the “Button” module.

These initialising functions can also add events to the event system, but these events are not executed until our firmware enters the main event loop. In this firmware, the “Button” module will add a repeated event to poll the button states.

Display Module

namespace lr {
namespace Display {
void initialize();
void setNumber(uint8_t number);
void increaseNumber();
void decreaseNumber();
}
}

I created this simple high-level display module to control the displayed number by lighting up the corresponding LED. It is simple to replace the implementation without even touching the logic. You can drive a seven-segment display or even an LCD or OLED display — this interface won’t change.

Imagine, you write the firmware for a product which is produced in three different versions:

  • A cheap version with just a single LED.
  • A medium-priced version with a seven-segment display.
  • An expensive version with a built-in OLED display.

With a design like this, you just provide three different implementations of the Display.cpp file, the interface stays the same. You would name these implementations Display_SingleLed.cpp, Display_SevenSegment.cpp and Display_OLED.cpp and include one of these files in the build process.

The example interface is not perfect: The application logic should control the actual number, not the display. Also, it should not be an abstract number. The value requires a clear meaning, like a percentage value or a value with a defined range or unit.

If the value has a clear meaning, the high-level display module can display this value in the best possible way for the attached hardware.

Buttons Module

namespace lr {
namespace Buttons {
enum Button : uint8_t {
    Left = 0,
    Right,
    None = 0xffu
};
void initialize();
Button getNextButtonPress();
}
}

I created this passive interface to access the buttons of the device. There is no function to register a callback for each button press. You have to poll the getNextButtonPress() function for new button presses.

This looks inconvenient for this simple firmware but makes sense if there are multiple modes or states. Imagine, if you press a button, the display shows a menu where you can select items and adjust values. In this case, another part of your firmware can consume the button presses and translate it to menu actions. Using callbacks would make it really hard to maintain a proper encapsulation of the logic.

Design if the firmware had a work and menu mode.

Main

I kept the example simple and added the application logic directly to the main file of the project. You should move this logic into an “Application” module with a Application.hpp and Application.cpp file.

void processButtonPresses()
{
    switch (Buttons::getNextButtonPress()) {
    case Buttons::Left:
        Display::decreaseNumber();
        break;
    case Buttons::Right:
        Display::increaseNumber();
        break;
    default:
        break;
    }
}

This method is added as a polling event, which is called every time the event loop is processed. It retrieves the next button press from the “Buttons” module and in case there is one, translates this into a display change.

The logic is the glue between modules.

Avoid dependencies between modules on the same level. The dependency graph must be clean, pointing from high-level to low-level modules. The logic is the glue between the modules.

Conclusion

Writing event-based firmware is straightforward and result in clean well maintainable code. It supports a strong encapsulation of your modules. With fewer dependencies, it will allow you to reuse the modules for other projects easily.

Do you have any questions, did you miss information, or simply want to provide feedback? Add a comment below or ask a question on Twitter!

More Posts

Stencils from OSH Stencils Arrived

Posted on 2019-12-23— Projects

I received 3mil polyimide stencils from OSH Stencils for my Swiss precision timer project. They feel flimsy compared to the steel stencils I usually use, but cost much less. I’ll test solder paste application and the first prototype in the coming weeks — please read the full post for results.

Read this post

The Hinges and its Secrets for Perfect PETG Print

Posted on 2023-02-07— 3D Printing, Learn

I'm not a 3D printing guru, but I built and tested three print-in-place hinges optimized for PETG/ASA and explain the small design choices that make them work reliably. I walk you through bridging, fin widths, overhangs, and conical holes — read the full post to learn how to print them well.

Read this post

Candlelight Effect

Posted on 2019-11-25— Projects

I built a compact Neopixel-based candlelight decoration with a DS3231 RTC and share the schematic, firmware, and build tips here. It's best placed behind a screen or in a jar. Read the full post for parts, assembly photos, and configuration notes to recreate it yourself.

Read this post

How and Why to use Namespaces

Posted on 2014-10-31— C++, How and Why

I explain how C++ namespaces prevent name conflicts and why they matter, especially in Arduino projects. I walk through practical patterns—prefixing, selective using-declarations, and importing a namespace—with class and global examples. Read the full post for simple, pragmatic examples to keep your code clearer and conflict-free.

Read this post

It's Time to Use #pragma once

Posted on 2019-07-13— C++, Improve your Code, Learn

I explain why I prefer #pragma once over macro header guards and how it prevents subtle build errors and wasted debugging time. I’ll show examples, common pitfalls, and practical steps to replace guards in existing projects — read on if you want a simple, low-risk improvement.

Read this post

Organizer Boxes Optimised for 3D-Print

Posted on 2020-11-21— 3D Printing, Common, Projects

I've been reorganising my lab and designed stackable organiser boxes optimised for fast 3D printing. They use a 60×60mm raster, stabilising walls, dual label slots and rail grids. I offer a free STL subset and explain print settings—read the full post for downloads and the complete shop set.

Read this post