<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Firmware on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/firmware/</link>
    <description>Recent content in Firmware on TurboVision</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 21 Apr 2026 14:06:12 +0000</lastBuildDate>
    <atom:link href="https://turbovision.in6-addr.net/tags/firmware/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <item>
      <title>Debouncing with Time and State, Not Hope</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/debouncing-with-time-and-state/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:43:31 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/debouncing-with-time-and-state/</guid>
      <description>&lt;p&gt;Button debouncing is one of the smallest problems in embedded systems and one of the most frequently mishandled. That combination makes it a perfect teaching case. Engineers know contacts bounce, yet many designs still rely on ad-hoc delays or lucky timing. These solutions pass demos and fail in real operation. A robust approach treats debouncing as a tiny state machine with explicit time policy.&lt;/p&gt;
&lt;p&gt;Mechanical bounce is not mysterious. On transition, contacts physically oscillate before settling. During that interval, GPIO sampling can see multiple edges. If firmware interprets every edge as intent, one press becomes many events. The correct objective is not “filter noise” in the abstract; it is to infer a human action from unstable electrical evidence with defined latency and false-trigger bounds.&lt;/p&gt;
&lt;p&gt;The naive pattern is edge interrupt plus &lt;code&gt;delay_ms(20)&lt;/code&gt; inside the handler. This feels simple but causes collateral damage: blocked interrupt handling, jitter in unrelated tasks, and poor power behavior. Worse, fixed delays are often too long for responsive UIs and still too short for worst-case switches. Delays treat symptoms while creating scheduling side effects.&lt;/p&gt;
&lt;p&gt;A better pattern separates observation from decision. Observation samples pin state periodically or on edge notifications. Decision logic advances through states: &lt;code&gt;Idle&lt;/code&gt;, &lt;code&gt;CandidatePress&lt;/code&gt;, &lt;code&gt;Pressed&lt;/code&gt;, &lt;code&gt;CandidateRelease&lt;/code&gt;. Each transition is gated by elapsed stable time. This design is cheap, deterministic, and testable. It also composes naturally with long-press and double-click features.&lt;/p&gt;
&lt;p&gt;Sampling frequency matters less than many assume. You do not need MHz polling for human input. A 1 ms tick is usually enough, and even 2–5 ms can be acceptable with careful thresholds. What matters is consistent sampling and explicit stability windows. If a signal remains stable for &lt;code&gt;N&lt;/code&gt; ticks, commit the state transition. If it flips early, reset candidate state.&lt;/p&gt;
&lt;p&gt;Interrupt-assisted designs can reduce average CPU cost without sacrificing rigor. Use GPIO interrupts only as wake hints, then confirm transitions in the debounce state machine on a scheduler tick. This hybrid model balances responsiveness and robustness. It avoids long ISR work while still minimizing idle polling overhead.&lt;/p&gt;
&lt;p&gt;Hardware assists are still useful. RC filters and Schmitt-trigger inputs reduce bounce amplitude and edge ambiguity. But hardware alone rarely removes the need for firmware logic, especially when you support varied switch vendors, cable lengths, or noisy environments. The best systems combine modest front-end conditioning with explicit software state handling.&lt;/p&gt;
&lt;p&gt;Testing debouncers should include adversarial scenarios, not only clean bench presses. Vary supply voltage, inject EMI near harnesses, test with gloved and rapid presses, and capture edge traces from different switch lots. Build a replay harness in firmware that feeds recorded edge sequences into your debounce logic and asserts expected events. This turns “seems fine” into measurable confidence.&lt;/p&gt;
&lt;p&gt;Latency trade-offs should be stated in requirements. If you require sub-20 ms press detection while tolerating noisy switches, design thresholds accordingly and verify under worst-case bounce profiles. Teams often optimize for false-trigger elimination and accidentally create sluggish interfaces. Users notice sluggishness immediately. Good debouncing balances reliability with perceived immediacy.&lt;/p&gt;
&lt;p&gt;State-machine debouncing also scales better for many inputs. Instead of per-button delay hacks, you run a compact table of states and timestamps. This structure keeps complexity linear and enables uniform behavior across keys. It also simplifies telemetry: you can log per-button transition timing and detect degrading switches before field failures escalate.&lt;/p&gt;
&lt;p&gt;Power-conscious designs must integrate debouncing with sleep states. Wake-on-edge can trigger from bounce bursts. Firmware should treat wake events as tentative, verify stable states, and return to low power quickly when no valid action is confirmed. Without this, noisy inputs can destroy battery life while appearing functionally correct in brief lab tests.&lt;/p&gt;
&lt;p&gt;The biggest lesson is methodological. Debouncing rewards explicit models over folklore. Define states. Define thresholds. Define expected outcomes. Then test those outcomes with recorded traces and timing variation. This is the same engineering pattern used for larger systems, just in miniature. If a team is sloppy on debouncing, it is often sloppy elsewhere too.&lt;/p&gt;
&lt;p&gt;So treat button handling as more than boilerplate. It is a compact reliability exercise that improves firmware architecture, testing discipline, and UX quality. Time and state beat hope every time.&lt;/p&gt;
&lt;p&gt;If you are mentoring juniors, debouncing is an ideal first design review topic. It is small enough to reason about completely, yet rich enough to expose habits around requirements, state modeling, timing assumptions, and test quality. Teams that do debouncing well usually do larger stateful systems well too.&lt;/p&gt;
&lt;h2 id=&#34;tiny-reference-implementation-pattern&#34;&gt;Tiny reference implementation pattern&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;raw&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;last_raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;last_change_ms&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;now_ms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;last_raw&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;now_ms&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;last_change_ms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;stable_ms&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;debounced&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;debounced&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nf&#34;&gt;emit_event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;debounced&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;EV_PRESS&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_RELEASE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Simple, explicit, and testable. This pattern is often enough for reliable human-input paths.&lt;/p&gt;
&lt;p&gt;Related reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/microcontrollers/state-machines-that-survive-noise/&#34;&gt;State Machines That Survive Noise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/microcontrollers/timer-capture-without-an-rtos/&#34;&gt;Timer Capture Without an RTOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/ground-is-a-design-interface/&#34;&gt;Ground Is a Design Interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>State Machines That Survive Noise</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/state-machines-that-survive-noise/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:39:14 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/state-machines-that-survive-noise/</guid>
      <description>&lt;p&gt;A lot of embedded bugs are not algorithm failures. They are state-management failures under imperfect signals. Inputs bounce, clocks drift, interrupts cluster, and peripherals report transitional nonsense. Firmware that assumes clean edges and ideal timing eventually fails in the field where noise is normal.&lt;/p&gt;
&lt;p&gt;Robust systems treat noise as a design input, not a test surprise.&lt;/p&gt;
&lt;h2 id=&#34;why-finite-state-machines-still-win&#34;&gt;Why finite state machines still win&lt;/h2&gt;
&lt;p&gt;State machines are sometimes dismissed as &amp;ldquo;old-school&amp;rdquo; in modern embedded stacks. That is a mistake. They remain one of the best tools for making behavior explicit under uncertainty:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;legal transitions are visible&lt;/li&gt;
&lt;li&gt;invalid transitions can be handled deliberately&lt;/li&gt;
&lt;li&gt;timeout behavior is encoded, not implied&lt;/li&gt;
&lt;li&gt;recovery paths are first-class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, state machines force you to name ambiguous phases that ad-hoc boolean logic usually hides.&lt;/p&gt;
&lt;h2 id=&#34;a-practical-pattern-event-queue--transition-table&#34;&gt;A practical pattern: event queue + transition table&lt;/h2&gt;
&lt;p&gt;A resilient architecture separates interrupt capture from policy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ISR captures minimal event.&lt;/li&gt;
&lt;li&gt;Main loop dequeues event.&lt;/li&gt;
&lt;li&gt;Transition function updates state.&lt;/li&gt;
&lt;li&gt;Output actions run from resulting state.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_IDLE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_ARMED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_ACTIVE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_FAULT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;state_t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;typedef&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_EDGE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_TIMEOUT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_CRC_FAIL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_RESET&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;event_t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;state_t&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;step&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;state_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;event_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_IDLE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_EDGE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_ARMED&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_IDLE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_ARMED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_TIMEOUT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_ACTIVE&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_CRC_FAIL&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_FAULT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_ARMED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_ACTIVE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_RESET&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_IDLE&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_ACTIVE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_FAULT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EV_RESET&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ST_IDLE&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_FAULT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ST_FAULT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is intentionally simple. Complexity belongs in explicit transitions, not in hidden timing side effects.&lt;/p&gt;
&lt;h2 id=&#34;debounce-is-a-state-problem-not-just-delay&#34;&gt;Debounce is a state problem, not just delay&lt;/h2&gt;
&lt;p&gt;Naive debounce logic (&lt;code&gt;delay then read&lt;/code&gt;) often passes bench tests and fails with variable load. Better approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;maintain input state&lt;/li&gt;
&lt;li&gt;require stable duration threshold&lt;/li&gt;
&lt;li&gt;transition only when threshold satisfied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This aligns with &lt;a href=&#34;https://turbovision.in6-addr.net/electronics/microcontrollers/debouncing-with-time-and-state/&#34;&gt;Debouncing with Time and State&lt;/a&gt; and extends it into full system behavior.&lt;/p&gt;
&lt;h2 id=&#34;timeouts-are-architectural-not-patchwork&#34;&gt;Timeouts are architectural, not patchwork&lt;/h2&gt;
&lt;p&gt;Every state that waits on external behavior should define timeout semantics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what timeout means&lt;/li&gt;
&lt;li&gt;whether retry is allowed&lt;/li&gt;
&lt;li&gt;max retry budget&lt;/li&gt;
&lt;li&gt;fallback state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Undefined timeout behavior is one of the most expensive firmware ambiguities in production debugging.&lt;/p&gt;
&lt;h2 id=&#34;top-aligned-diagnostics-in-firmware-logs&#34;&gt;Top-aligned diagnostics in firmware logs&lt;/h2&gt;
&lt;p&gt;When logging transitions, keep entries normalized:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ts | old_state | event | new_state | action | error_code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This format turns logs into analyzable traces instead of prose fragments. You can then diff expected transition sequences against observed ones in automated tests.&lt;/p&gt;
&lt;h2 id=&#34;guarding-against-interrupt-storms&#34;&gt;Guarding against interrupt storms&lt;/h2&gt;
&lt;p&gt;Interrupt storms can starve policy logic if ISR work is too heavy. Keep ISR minimal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;capture timestamp&lt;/li&gt;
&lt;li&gt;capture source id&lt;/li&gt;
&lt;li&gt;queue event&lt;/li&gt;
&lt;li&gt;exit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any parsing, retry decisions, or multi-step logic belongs in cooperative main-loop context where execution order is controlled.&lt;/p&gt;
&lt;h2 id=&#34;noise-aware-testing-strategy&#34;&gt;Noise-aware testing strategy&lt;/h2&gt;
&lt;p&gt;A strong test suite includes adversarial input timing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;burst edges near threshold boundaries&lt;/li&gt;
&lt;li&gt;delayed acknowledgments&lt;/li&gt;
&lt;li&gt;missing edges&lt;/li&gt;
&lt;li&gt;duplicate events&lt;/li&gt;
&lt;li&gt;out-of-order event injections&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your machine cannot survive these, it is not ready for hardware reality.&lt;/p&gt;
&lt;h2 id=&#34;cross-references-for-this-design-style&#34;&gt;Cross references for this design style&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/microcontrollers/timer-capture-without-an-rtos/&#34;&gt;Timer Capture Without an RTOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/microcontrollers/spi-signals-that-lie/&#34;&gt;SPI Signals That Lie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/ground-is-a-design-interface/&#34;&gt;Ground Is a Design Interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These pieces describe the same principle at different layers: uncertainty is part of the interface contract.&lt;/p&gt;
&lt;h2 id=&#34;implementation-details-that-pay-off&#34;&gt;Implementation details that pay off&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Keep state enum in one header, shared by firmware and test harness.&lt;/li&gt;
&lt;li&gt;Use explicit &amp;ldquo;unexpected event&amp;rdquo; handler, never silent ignore.&lt;/li&gt;
&lt;li&gt;Version your transition table so behavior changes are reviewable.&lt;/li&gt;
&lt;li&gt;Add build-time switch for transition tracing in debug builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This sounds procedural because reliability is procedural.&lt;/p&gt;
&lt;h2 id=&#34;final-thought&#34;&gt;Final thought&lt;/h2&gt;
&lt;p&gt;Embedded systems do not get judged by elegance under ideal inputs. They get judged by behavior under messy electrical and timing conditions. State machines that survive noise are not conservative design. They are aggressive risk management.&lt;/p&gt;
&lt;p&gt;If you are choosing between adding one more feature and hardening transitions around existing behavior, harden first. Field failures almost always happen at transitions, not in the center of stable states.&lt;/p&gt;
&lt;p&gt;Document each state transition in one sentence that an on-call engineer can understand at 3 AM. If the sentence is unclear, the transition is probably underspecified in code as well.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
