DEV Community

standarddeviant
standarddeviant

Posted on

Firmware Architecture with StateSmith

StateSmith is a tool that can transform UML into Code+Diagrams. Besides UML, other valid inputs are yED diagrams and draw.io diagrams.

Importantly StateSmith...

  • is well tested
  • has rich features
  • has multi-language support
  • is open source
  • is light-weight
  • is easy to use
  • has simple integration with VSCode for visualization
  • produces auto-generated code that is easily readable

I'm an embedded engineer with almost 20 years of experience. I have encountered many buggy state machines written in C. Sadly, I have written some buggy state machines in C. I started using StateSmith on an important project written in C on a microcontroller.

I call StateSmith a "career changer" because now:

I will never write another complex state machine by hand.

Paraphrasing Adam, the creator of StateSmith, the value in tools like this is that once you create the UML diagram, you get:

  1. source code that does exactly what you specified
  2. a visual diagram of the state machine behavior

The visual diagram is extremely helpful when debugging an issue or changing behavior of the code.

This lets us:

Fix the code by fixing the picture.

StateSmith doesn't remove the need for care and consideration when designing system behavior, but it enables a much more rapid debug cycle by providing source code and a diagram that are effectively guaranteed to match each other.

If all you need is a single state machine, then dive into the StateSmith docs and you should be all set.

However...

Multiple state machines that interact with each other is necessary to handle the complexity of many projects. I wanted a simple and robust way to test overall system behavior with Hardware-In-Loop testing and realized I wanted the ability to "inject" events into any state machine and get a "report" of each issued event and state change for all involved state machines.

The test software only has to concern itself with syntax for two things:

  • injection of events
  • reporting of events and state changes

For simplicity, I use a single message type that contains three fields of information:

  1. event-or-state-change
  2. subsystem ID, where each state machine has its own integer ID
  3. subsystem value, an integer ID to represent individual events or state changes

Fields 1 and 2 are just static, manually created enumerations. Field 3 is nicely handled by StateSmith infrastructure per SomeSm_StateID_... and SomeSm_EventID_... for a single state machine named SomeSm

The C version of this data type is

typedef struct {
  uint8_t mtype;      // event=0 or state=1
  uint8_t subsys_id;  // manually created enum
  uint8_t subsys_val; // event IDs or state IDs generated via StateSmith
} inj_rep_t;
Enter fullscreen mode Exit fullscreen mode

Per the type name, this single data type can be "injected into" or "reported from" any state machine.

Whether a message is "injected" or "reported" is determined by the text syntax over a text-based serial port (like UART) or implied per function calls in the firmware.

Another important idea I stumbled upon is using a "content-based message router" that receives all reported events and state changes, and then potentially injects new events based on reported events and state changes.

This enables modular and flexible logic for the overall system behavior. It also opens the door for flexible Hardware-In-Loop testing!

A diagram of injected and reported events for HIL testing is shown below:

HIL Capable Firmware Architecture with StateSmith

Importantly, I'm using Zephyr RTOS and chose to use zbus, a pub/sub implementation built into Zephyr RTOS. My usage of zbus is very simple; it just takes all reported events and routes a copy to the content-based message router and (optionally) a copy to the UART based reporting feature. This second copy to the "reporter" is extremely useful for HIL testig to verify the system is in a certain state or that certain events have happened.

With this approach:

  • No state machine has to know about any other state machines
    • But HAL-based query functions may enable simpler state machines
  • All "integration logic" is in one place, the content-based router
    • This accelerates development and changing overall system behavior
  • A text message of inj A B C is virtually the same as an actual hardware event
    • This helps enable all kinds of automated HIL testing!
  • A "reporter" script that ingests all the rep X Y Z messages is very easy to write and maintain

After a bit of googling and asking, I think it's accurate to say this approach is

the "Actor Model" leveraging a "Content-Based Message Router"

This approach is probably not appropriate for many situations. However, for a product with multiple different types of hardware that need to interact with each other, it seems to be a very efficient way to manage the necessary complexity of different hardware interactions. Good examples of different hardware it can integrate in a firmware project are:

  • Buttons
  • Lights
  • Displays
  • Speakers
  • Motors
  • Wireless Radio Connections, i.e. Bluetooth
  • Battery charger chips or Power-Management ICs (PMICs)
  • and more!

I've been rather pleased with what I've been able to achieve with this approach in about 6 months of development time. Please feel free to leave suggestions, critiques, and alternative approaches in the comments.

Top comments (0)