Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

Event-Based Firmware Example

Posted on 2019-08-052022-09-04 by Lucky Resistor

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
  • Source Code
  • Demo Video
  • Design
  • Main
  • Conclusion
  • Learn More

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:

Event Loop Example

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.

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!

Learn More

Write Less Code using the "auto" Keyword

Write Less Code using the “auto” Keyword

The auto keyword was introduced with C++11. It reduces the amount of code you have to write, reduces repetitive code and the number of required changes. Sadly, many C++ developers are not aware of how ...
Read More
Guide to Modular Firmware

Guide to Modular Firmware

This article is for embedded software developers with a solid working knowledge of C or C++, but who struggle with large and complex projects. If you learn to develop embedded code, e.g. using the Arduino ...
Read More
How to Deal with Badly Written Code

How to Deal with Badly Written Code

Sadly there is a ton of badly written code out in the wild. Hardware related code, seem to suffer more in this regards. I imagine, many developer in this segment are unwillingly to invest time ...
Read More
Event-based Firmware (Part 1/2)

Event-based Firmware (Part 1/2)

You start with small, simple firmware. But with each added feature, the complexity grows and grows. Soon, you need a good design in order to maintain the firmware and ensure the code remains clean and ...
Read More
How and Why to Avoid Preprocessor Macros

How and Why to Avoid Preprocessor Macros

While most naming conflicts in C++ can be solved using namespaces, this is not true for preprocessor macros. Macros cannot be put into namespaces. If you try to declare a new class called Stream, but ...
Read More
How and Why to use Namespaces

How and Why to use Namespaces

Namespaces are a feature of C++ which address the problem of name conflicts. There is a "global" namespace, where everything lives which was declared without namespace. Especially the Arduino environment declares a huge amount of ...
Read More

4 thoughts on “Event-Based Firmware Example”

  1. Zaar Hai says:
    2019-09-17 at 06:25

    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)

    Reply
    1. Zaar Hai says:
      2019-09-17 at 06:27

      Oh, sorry, the code in the comment is not really readable. Here is the link to pastebin: https://pastebin.com/gKq7VQz8

      Reply
    2. Lucky Resistor says:
      2019-09-17 at 18:08

      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.

      Reply
  2. Guy Thomas says:
    2022-01-01 at 09:22

    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.

    Reply

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • Storage Boxes System for 3D Print
  • Use Enum with More Class!
  • Simple Box with Lid for 3D-Print
  • Fan Controller
  • Shop
  • Real Time Counter and Integer Overflow
  • How and Why to use Namespaces
  • The Hinges and its Secrets for Perfect PETG Print
  • Extreme Integers – Doom from Below
  • Build a 3D Printer Enclosure

Latest Posts

  • The Importance of Wall Profiles in 3D Printing2023-02-12
  • The Hinges and its Secrets for Perfect PETG Print2023-02-07
  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.
 

Loading Comments...