Principia Embedded: More Musings on State-Time

Principia Embedded: More Musings on State-Time

Reflections

There’s something magical that happens during a period of reflection.

The dogs soundly sleeping. Your partner — in this case, the love of my life — going on about her work day, as she too has much on her plate. Slowly, that warm vapor melts into the atmosphere from a freshly brewed cup of tea.

I’ve thought about time in the embedded space before, though I admit, only briefly. I think there are volumes that could be said. And so, in this space, these are simple short musings I hope to someday expand.

In “state-time,” state becomes a map of sequential actions. A recipe of sorts.

I reflect on my experience, the myriad of ways I’ve expressed state through code, and the consequences involved. I always return to one idea: embedded engineering is equal parts art and science.

Science becomes extremely binary. Art offers nuance. Nuance, however, isn’t without consequence. The wisdom born of time allows us to make what we believe are the best decisions for a given application.

As I continue pondering the relationship between state and time, three pillars keep resurfacing.

Three Pillars

1. Conservation of Complexity

Complexity is conserved. We can’t eliminate it; we can only move it.

When we make a system easier for a user, we often shift that complexity onto ourselves. Complexity never disappears. It simply changes form.

2. Cognitive Loading

In designing, maintaining, or using any system, there’s a load placed somewhere on human cognition.

John Sweller discussed this in 1988. Human memory has limits. Overburden those limits and reasoning begins to collapse.

Systems become harder to hold in your head. Harder to evolve. Harder to trust.

3. Entropy

To paraphrase Rudolf Clausius, entropy in a closed system does not decrease. It tends to increase over time.

The second law of thermodynamics applies surprisingly well to the architectures we choose. As we code, improve, and maintain, systems naturally become more entropic.

In software, we talk about refactoring. In embedded systems, I’ve rarely seen the practice applied with the same rigor.

I could argue that it’s one of the only ways to compensate for natural decay. One of the only ways to extend the lifecycle of a solid codebase. One of the only ways to introduce order back into the system.

Architecture Ages

I’ve realized that the architecture we choose has a profound effect on how a system ages, along with the implications imposed on the initial design.

Some models place enormous cognitive burden on the developer early. Others delay it, where it later reappears as synchronization problems, exclusionary difficulties, and maintenance overhead.

Complexity never disappears. It simply changes form. And over time, that complexity expresses itself as entropy.

Across scheduling models, super loops and preemptive systems tend to concentrate burden at opposite extremes, while cooperative scheduling may lie near a local minima.

Time — the lifecycle of a codebase — adds yet another dimension. The overall shape remains the same, though distorted by evolution and age.

The Traditional Super Loop

Maximum control. Maximum responsibility.

The developer is the scheduler. Every addition, every branch, every timing relationship becomes part of the cognitive burden carried by the engineer.

The Fully Preemptive Model

The scheduling burden shifts toward the RTOS designer. But complexity reappears elsewhere:

No thread is truly atomic. The complexity has shifted, but it certainly hasn’t vanished.

Cooperative Scheduling

There’s still complexity. Nothing is free. But perhaps it exists at a local minima.

The scheduler relieves the burden of maintaining a super loop. Protothreads mimic many of the encapsulation benefits of preemption, but peer threads don’t interrupt each other mid-thought.

The synchronization burden between peer threads is greatly reduced. One does have to remember to behave cooperatively. One must yield. One must be a good neighbor to the other threads in the system.

There are still challenges. A task that never yields is the cooperative equivalent of blocking the room. But unlike many of their preemptive siblings, the cooperative nature often makes these problems easier to find.

I’m inclined to believe that this minima exists because these systems are easier to reason about, maintain, and evolve over time.

The State Space

In the state space, this is also true. Compounded if-else phrases fry the brain. They hit astounding levels of entropy at an alarming rate.

Switch-case or table-driven state machines are better, but by what degree? Years ago, I reviewed a state machine for a lithium-ion battery charger. It spanned well over ten pages.

I used to bring it into interviews just to see how quickly applicants could wrap their heads around it. Truth be told, no one could.

To be fair, everyone walks into an interview wired up. As the greybeard in the room, I always tried to keep things light. But there was a lesson in it: the surest way to lose control of a system is to construct something no one can wrestle with anymore.

If you can’t explain something simply, you probably don’t understand it well enough.

And so, this is where we begin to enter protothreads. In many ways, they are the state-time equivalent of cooperative scheduling.

They allow us to express the art of nuance without losing the science of the binary.

Originally published on LinkedIn as “Principia Embedded: More Musings on State-Time”.

Need help structuring embedded firmware around state, time, and real-world behavior?
Mesa Technologies provides senior embedded systems consulting for firmware architecture, board bring-up, debugging, project recovery, and risk reduction.

Schedule a technical call