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.
- The “Buttons” module.
- The “Display” module.
- A support library in the
src
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.

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!
Learn More

Event-based Firmware (Part 1/2)

It’s Time to Use #pragma once

Guide to Modular Firmware

How and Why to use Namespaces

Write Less Code using the “auto” Keyword

Hello again and thanks for another great article. I’m working on arduino-based lights controller for my girl’s doll house and trying to figure out the best model for IR receiver responses. The functionality is simple: there are several buttons that represent room selection. Then there are several buttons to adjust colour HSV values (for the selected room). Hence my events have type & payload. I would like to do something similar you do in processButtonPresses(), i.e. check event type and dispatch payload to relevant handler.
In Python I would do either type/opaque_payload class [1] or just a base class with “type” and then extend it for each payload type [2]
I understand that both approaches can be done with C++ 11, but I wonder if I’m not missing anything by trying to translate high-level language thinking into embedded world. What do you think? (this is not performance critical app, but I’d like to learn doing it the “right way”).
Overall the type/opaque_payload approach looks a bit cleaner IMHO, since the actual handler does not need to know about event – they only care about payload they need to act upon (in reality they will be just method of LedStripsManager)
#### Some Python code
from dataclasses import dataclass
from enum import Enum
from typing import Union
class ButtonType(Enum):
color_sel = 1
room_sel = 2
class HSV(Enum):
h = 1
s = 2
v = 3
# [1] Opaque payload
@dataclass
class HSVColorPayload:
type: HSV
value: int
@dataclass
class RoomPayload:
room_no: int # Enum in actual life
@dataclass
class ButtonEvent:
btype: ButtonType
payload: Union[HSVColorPayload, RoomPayload]
def do_color_sel(p: HSVColorPayload): pass
def do_color_sel(p: RoomPayload): pass
def process_button_event(e: ButtonEvent) -> None:
handlers = {
ButtonType.color_sel: do_color_sel,
ButtonType.room_sel: do_room_self,
}
handler = handler[e.btype]
handler(e.payload)
# [1] Subclassing
@dataclass
class ButtonEvent:
btype: ButtonType
@dataclass
class ColorEvent(ButtonEvent):
type: HSV
value: int
@dataclass
class RoomEvent(ButtonEvent):
room_no: int
def do_color_sel(e: ColorEvent): pass
def do_color_sel(e: RoomEvent): pass
def process_button_event(e: ButtonEvent) -> None:
handlers = {
ButtonType.color_sel: do_color_sel,
ButtonType.room_sel: do_room_self,
}
handler = handler[e.btype]
handler(e)
Oh, sorry, the code in the comment is not really readable. Here is the link to pastebin: https://pastebin.com/gKq7VQz8
Split the problem in separate modules. One for the input, one to control the lights and an application logic which acts like the glue between the modules, processing the input and controlling the lights.
Thanks Lucky Resistor. This series of articles have helped me a tremendous amount in getting an ornery code base under control. I now have an extensible platform to build on thanks in a large part to you.