<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Microcontrollers on TurboVision</title>
    <link>https://turbovision.in6-addr.net/electronics/microcontrollers/</link>
    <description>Recent content in Microcontrollers 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/electronics/microcontrollers/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>SPI Signals That Lie</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/spi-signals-that-lie/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:09:16 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/spi-signals-that-lie/</guid>
      <description>&lt;p&gt;SPI looks simple on paper: clock, data out, data in, chip select. Four wires, deterministic timing, done. In real projects, SPI failures often appear as &amp;ldquo;sometimes wrong bytes,&amp;rdquo; &amp;ldquo;first transfer fails,&amp;rdquo; or &amp;ldquo;only breaks on production boards.&amp;rdquo; These are the kind of bugs that waste days because the bus seems healthy at first glance.&lt;/p&gt;
&lt;p&gt;The core lesson is that SPI integrity is not just protocol correctness. It is electrical timing, firmware sequencing, and peripheral state management combined.&lt;/p&gt;
&lt;p&gt;Common failure classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clock polarity/phase mismatch masked by forgiving devices&lt;/li&gt;
&lt;li&gt;chip-select timing violations near transaction boundaries&lt;/li&gt;
&lt;li&gt;signal integrity problems at higher edge rates&lt;/li&gt;
&lt;li&gt;peripheral state not reset between commands&lt;/li&gt;
&lt;li&gt;DMA and interrupt races corrupting transfer order&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any one can produce plausible-but-wrong data.&lt;/p&gt;
&lt;p&gt;I start with protocol truth first. Confirm CPOL/CPHA mode from datasheets, then verify with logic analyzer captures of command/response boundaries. Do not rely on &amp;ldquo;it worked with another sensor.&amp;rdquo; Different devices tolerate different mistakes.&lt;/p&gt;
&lt;p&gt;Chip-select discipline is frequently underestimated. Some peripherals require minimum setup/hold time around CS transitions. If firmware toggles CS too quickly under optimization changes, a previously stable transfer can degrade silently. Enforce timing explicitly, not by incidental delays.&lt;/p&gt;
&lt;p&gt;Signal integrity matters earlier than many assume. At modest board lengths and strong GPIO drive settings, ringing and overshoot can create false edges. Scope captures at the receiver pin, not just MCU pin, are essential. What leaves the MCU is not always what arrives at the device.&lt;/p&gt;
&lt;p&gt;Practical board-level mitigations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;series resistors near source on high-edge lines&lt;/li&gt;
&lt;li&gt;clean return paths&lt;/li&gt;
&lt;li&gt;reduced edge rate where available&lt;/li&gt;
&lt;li&gt;controlled trace length matching for sensitive links&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are cheap changes with high payoff.&lt;/p&gt;
&lt;p&gt;On firmware side, transaction framing should be explicit. Wrap transfers in one API that controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CS assert/deassert&lt;/li&gt;
&lt;li&gt;mode and speed selection&lt;/li&gt;
&lt;li&gt;optional guard delays&lt;/li&gt;
&lt;li&gt;retry and timeout policy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scattered raw register writes across drivers create hidden divergence and fragile maintenance.&lt;/p&gt;
&lt;p&gt;DMA introduces its own failure modes. If buffer ownership and completion signaling are unclear, stale or partially updated data appears intermittently. Use strict ownership rules and assert expected transfer length at completion.&lt;/p&gt;
&lt;p&gt;Interrupt interactions can also corrupt sequencing. If high-priority ISRs preempt between CS assert and first clock edge, timing contracts may break. Critical sections around transaction start are often justified in tight timing designs.&lt;/p&gt;
&lt;p&gt;Another subtle trap: mixed-speed peripherals on shared bus. Reconfiguration bugs happen when one driver leaves bus speed or mode altered for the next device. Centralized bus arbitration prevents this class of bug.&lt;/p&gt;
&lt;p&gt;Diagnostic strategy that works well:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;lock one known-good frequency and mode&lt;/li&gt;
&lt;li&gt;disable DMA and run blocking transfers&lt;/li&gt;
&lt;li&gt;validate deterministic test vectors&lt;/li&gt;
&lt;li&gt;reintroduce DMA and concurrency incrementally&lt;/li&gt;
&lt;li&gt;increase bus speed in controlled steps&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When failures reappear, you know which complexity layer introduced them.&lt;/p&gt;
&lt;p&gt;I strongly recommend adding protocol-level self-checks where possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;read-back register after write&lt;/li&gt;
&lt;li&gt;device ID verification at startup&lt;/li&gt;
&lt;li&gt;command echo checks&lt;/li&gt;
&lt;li&gt;CRC where supported&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These catch latent bus corruption before higher-level logic misbehaves.&lt;/p&gt;
&lt;p&gt;Power and reset sequencing also influence SPI reliability. Some peripherals accept clocks before internal state is ready, then remain in undefined mode until hard reset. Ensure boot initialization obeys datasheet timing windows.&lt;/p&gt;
&lt;p&gt;For production robustness, perform variability tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;temperature sweep&lt;/li&gt;
&lt;li&gt;supply voltage corners&lt;/li&gt;
&lt;li&gt;cable/harness variants where applicable&lt;/li&gt;
&lt;li&gt;repeated long-run stress with error counters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If an SPI link passes only nominal lab conditions, it is not finished.&lt;/p&gt;
&lt;p&gt;Logging can help in deployed systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;transaction error counts&lt;/li&gt;
&lt;li&gt;timeout counts&lt;/li&gt;
&lt;li&gt;last failing opcode&lt;/li&gt;
&lt;li&gt;bus-reset events&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These metrics turn rare field failures into diagnosable patterns.&lt;/p&gt;
&lt;p&gt;The big mindset shift: SPI bugs are often systems bugs, not line-by-line coding bugs. You solve them fastest by combining electrical captures, protocol verification, and firmware sequencing analysis, not by focusing on one layer alone.&lt;/p&gt;
&lt;p&gt;If you keep one rule, keep this: trust captured timing and measured waveforms over assumptions. SPI rarely lies; our interpretation of partial evidence does.&lt;/p&gt;
&lt;p&gt;If a design ships to production, add one recovery path too: a bus reinitialization routine that can safely reset peripheral state after repeated transaction failure. Rare field glitches become survivable when recovery is deterministic and observable rather than hidden behind random retries.&lt;/p&gt;
&lt;p&gt;Design for recoverability, then verify it under stress.&lt;/p&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>
    
    <item>
      <title>Timer Capture Without an RTOS</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/timer-capture-without-an-rtos/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:15:51 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/timer-capture-without-an-rtos/</guid>
      <description>&lt;p&gt;One of the most useful embedded skills is measuring external timing accurately without hiding behind a heavy runtime stack. You do not need an RTOS to capture pulse widths, frequency drift, or event latency with high reliability. You need a clear timing model, disciplined interrupt design, and careful data handoff.&lt;/p&gt;
&lt;p&gt;Timer input-capture peripherals are built for this job. They latch counter values on configured edges and let firmware process deltas later. The hardware does the precise timestamping; software handles interpretation.&lt;/p&gt;
&lt;p&gt;A robust architecture starts with three decisions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;counter clock source and prescaler&lt;/li&gt;
&lt;li&gt;edge policy (rising, falling, both)&lt;/li&gt;
&lt;li&gt;overflow handling strategy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If these are vague, accuracy claims will be vague too.&lt;/p&gt;
&lt;p&gt;Choose timer frequency from measurement goals, not convenience. Too slow and quantization error dominates. Too fast and overflow complexity increases, especially on narrow counters. A good target is where one tick is clearly below your required resolution with margin for jitter analysis.&lt;/p&gt;
&lt;p&gt;Input capture ISR design should be minimal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;read captured value&lt;/li&gt;
&lt;li&gt;read/track overflow epoch&lt;/li&gt;
&lt;li&gt;write compact event record into ring buffer&lt;/li&gt;
&lt;li&gt;return&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not compute expensive statistics inside ISR unless absolutely necessary. Deterministic ISR duration keeps timestamping reliable.&lt;/p&gt;
&lt;p&gt;The ring buffer is the bridge between hard realtime edges and softer application logic. Make it explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fixed-size, lock-free where possible&lt;/li&gt;
&lt;li&gt;head/tail updates with clear ownership&lt;/li&gt;
&lt;li&gt;overflow counter for dropped samples&lt;/li&gt;
&lt;li&gt;sequence IDs for gap detection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If sampling can outrun processing, design for graceful loss reporting instead of silent corruption.&lt;/p&gt;
&lt;p&gt;Overflow math is where many implementations become flaky. A 16-bit timer at high clock rate wraps frequently. You need either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;software epoch extension in overflow ISR, or&lt;/li&gt;
&lt;li&gt;wider hardware timer if available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then reconstruct absolute timestamps as &lt;code&gt;(epoch &amp;lt;&amp;lt; counter_bits) | capture_value&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Validate overflow handling with deliberate stress:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;low-frequency signals to force many wraps between edges&lt;/li&gt;
&lt;li&gt;bursty high-frequency signals near ISR capacity&lt;/li&gt;
&lt;li&gt;mixed duty cycles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If only one scenario is tested, hidden edge cases survive to production.&lt;/p&gt;
&lt;p&gt;Debounce and input conditioning matter too. Electrical noise can generate false captures. Hardware filtering, Schmitt inputs, or digital filter settings on capture channels often improve reliability more than post-processing hacks.&lt;/p&gt;
&lt;p&gt;For pulse width measurement, both-edge capture is ideal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;capture rising edge timestamp&lt;/li&gt;
&lt;li&gt;capture falling edge timestamp&lt;/li&gt;
&lt;li&gt;subtract with wrap-safe arithmetic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For frequency measurement, rising-only with period averaging is often cleaner.&lt;/p&gt;
&lt;p&gt;Averaging strategy should reflect signal characteristics. Fixed-window averaging smooths noise but can blur short transients. Exponential filters react faster but need careful coefficient tuning. Choose based on what errors are expensive for your application.&lt;/p&gt;
&lt;p&gt;No RTOS does not mean no scheduling discipline. Use a simple cooperative loop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;drain capture buffer&lt;/li&gt;
&lt;li&gt;update derived metrics&lt;/li&gt;
&lt;li&gt;publish snapshots atomically&lt;/li&gt;
&lt;li&gt;run non-critical tasks opportunistically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This model is predictable and usually enough for single-MCU measurement nodes.&lt;/p&gt;
&lt;p&gt;Atomic publication is important when data is consumed by other contexts (serial output, control loop, diagnostics). Use double-buffered snapshots or short critical sections to avoid torn reads.&lt;/p&gt;
&lt;p&gt;Instrumentation should be built in early:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dropped-sample count&lt;/li&gt;
&lt;li&gt;max ISR latency observed&lt;/li&gt;
&lt;li&gt;max buffer depth reached&lt;/li&gt;
&lt;li&gt;timestamp monotonicity checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without instrumentation, &amp;ldquo;seems stable&amp;rdquo; can hide near-overload behavior.&lt;/p&gt;
&lt;p&gt;Another practical pattern is calibration hooks. If timer clock derives from an internal RC oscillator, drift can distort measurements. Add a calibration path using known references where possible, or at least expose drift estimation telemetry so users understand uncertainty.&lt;/p&gt;
&lt;p&gt;When integrating with control logic, separate measurement confidence from measurement value. For each computed metric, carry metadata:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;valid/invalid&lt;/li&gt;
&lt;li&gt;sample count&lt;/li&gt;
&lt;li&gt;age&lt;/li&gt;
&lt;li&gt;error flags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Control decisions should degrade safely on low-confidence inputs.&lt;/p&gt;
&lt;p&gt;Testing must include real signal generators and ugly signals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clean square waves for baseline&lt;/li&gt;
&lt;li&gt;jittered waveforms&lt;/li&gt;
&lt;li&gt;missing pulses&lt;/li&gt;
&lt;li&gt;slow edges near threshold&lt;/li&gt;
&lt;li&gt;EMI-contaminated lines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Embedded timing code that only passes clean-lab signals is unfinished.&lt;/p&gt;
&lt;p&gt;One reason people reach for RTOS early is fear of concurrency complexity. That fear is understandable. But for focused timing tasks, a disciplined interrupt-plus-buffer model is simpler, faster, and easier to audit. You can always layer a scheduler later if system scope grows.&lt;/p&gt;
&lt;p&gt;A compact bring-up checklist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify edge timestamps with logic analyzer correlation&lt;/li&gt;
&lt;li&gt;force overflow and confirm wrap-safe math&lt;/li&gt;
&lt;li&gt;saturate input rate and observe drop accounting&lt;/li&gt;
&lt;li&gt;validate end-to-end latency from edge to published metric&lt;/li&gt;
&lt;li&gt;confirm behavior after long-duration runs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If all five pass, you have a reliable timing subsystem.&lt;/p&gt;
&lt;p&gt;The deeper lesson is architectural: put precision where it belongs. Let hardware timestamp edges. Let ISR move minimal data. Let foreground logic compute and publish. Clean boundaries produce reliable systems.&lt;/p&gt;
&lt;p&gt;This design style scales from small sensor interfaces to motor control telemetry and protocol timing diagnostics. It also teaches excellent habits: deterministic ISR design, explicit loss accounting, and confidence-aware outputs.&lt;/p&gt;
&lt;p&gt;You do not need an RTOS to do serious timing work. You need explicit constraints, measurable behavior, and the discipline to keep fast paths simple.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>RISC-V on a 10-Cent Chip</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/riscv-on-ch32v003/</link>
      <pubDate>Fri, 30 Jan 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 15:48:47 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/riscv-on-ch32v003/</guid>
      <description>&lt;p&gt;The WCH CH32V003 costs less than a stamp and runs a 32-bit RISC-V core
at 48 MHz. It has 2 KB of RAM, 16 KB of flash, and a surprisingly complete
peripheral set: USART, SPI, I²C, ADC, timers.&lt;/p&gt;
&lt;p&gt;We set up the open-source MounRiver toolchain, flash a UART echo program
over the single-wire debug interface, and measure current consumption in
sleep mode: 8 µA. For battery-powered sensors, this chip is hard to beat.&lt;/p&gt;
&lt;p&gt;The interesting part is not only the price. It is what this device teaches
about writing firmware with hard limits. With 2 KB RAM, every buffer is a
design decision. With 16 KB flash, libraries have to justify their existence.
That pressure tends to produce cleaner code than &amp;ldquo;just add another package.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;bring-up-notes-that-save-time&#34;&gt;Bring-up notes that save time&lt;/h2&gt;
&lt;p&gt;My shortest path to first success:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get a known-good blink or UART echo working first.&lt;/li&gt;
&lt;li&gt;Verify clock configuration before touching peripherals.&lt;/li&gt;
&lt;li&gt;Keep interrupts disabled until polling logic is stable.&lt;/li&gt;
&lt;li&gt;Add one peripheral at a time and re-test power draw.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most early failures are clock, pin mux, or toolchain path problems, not
&amp;ldquo;mystical hardware bugs.&amp;rdquo; If serial output is dead, confirm GPIO mode and
baud assumptions before rewriting half the project.&lt;/p&gt;
&lt;h2 id=&#34;why-this-chip-is-useful-in-practice&#34;&gt;Why this chip is useful in practice&lt;/h2&gt;
&lt;p&gt;CH32V003 is ideal for disposable probes, tiny sensor nodes, and protocol
bridges where BOM cost matters. You can still keep a disciplined structure:
small drivers, explicit init sequence, and one integration test per module.
That gives reliability without heavyweight frameworks.&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/avr-bare-metal/&#34;&gt;AVR Bare-Metal Blinking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/electronics/soldering-smd-by-hand/&#34;&gt;Hand-Soldering 0402 Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>AVR Bare-Metal Blinking</title>
      <link>https://turbovision.in6-addr.net/electronics/microcontrollers/avr-bare-metal/</link>
      <pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 15:48:59 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/electronics/microcontrollers/avr-bare-metal/</guid>
      <description>&lt;p&gt;No Arduino libraries. No HAL. Just registers.&lt;/p&gt;
&lt;p&gt;An ATmega328P has DDRB, PORTB, and a 16-bit timer. We configure Timer1
in CTC mode with a 1 Hz compare match, toggle PB5 (the onboard LED pin)
in the ISR, and end up with a binary that fits in 176 bytes. The Makefile
uses &lt;code&gt;avr-gcc&lt;/code&gt; and &lt;code&gt;avrdude&lt;/code&gt; directly — no IDE required.&lt;/p&gt;
&lt;p&gt;This exercise looks trivial, but it trains the exact muscle many developers
skip: understanding cause and effect between register writes and hardware
behavior. You do not &amp;ldquo;ask an API&amp;rdquo; to blink. You define direction bits, timer
prescalers, compare values, and interrupt masks yourself.&lt;/p&gt;
&lt;h2 id=&#34;minimal-mental-model&#34;&gt;Minimal mental model&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DDRB&lt;/code&gt; configures PB5 as output.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TCCR1A/TCCR1B&lt;/code&gt; define timer mode and prescaler.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OCR1A&lt;/code&gt; sets compare threshold.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIMSK1&lt;/code&gt; enables compare interrupt.&lt;/li&gt;
&lt;li&gt;ISR toggles &lt;code&gt;PORTB&lt;/code&gt; bit for the LED.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When this chain is explicit, debugging gets faster. If timing is wrong, you
inspect clock and prescaler. If the LED is dark, verify direction and pin.
Each symptom maps to a small set of causes.&lt;/p&gt;
&lt;h2 id=&#34;why-still-do-this-in-2026&#34;&gt;Why still do this in 2026&lt;/h2&gt;
&lt;p&gt;Bare-metal AVR is still a great teaching platform because feedback is fast
and tooling is mature. You can compile, flash, and verify behavior in a few
seconds, then iterate. Even if your production target is different, this
discipline transfers directly to RISC-V, ARM, and RTOS-based projects.&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/riscv-on-ch32v003/&#34;&gt;RISC-V on a 10-Cent Chip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/musings/why-constraints-matter/&#34;&gt;Why Constraints Matter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
