-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirmware-control.html
More file actions
221 lines (185 loc) · 11.2 KB
/
firmware-control.html
File metadata and controls
221 lines (185 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Phase 4: Firmware Control — VoltForge</title>
<!-- Open Graph -->
<meta property="og:title" content="Firmware Control — VoltForge">
<meta property="og:description" content="Phase IV: Zero-cost abstraction firmware architecture using Rust no_std type-state FSM targeting STM32G4 to enforce safety invariants at compile time.">
<meta property="og:image" content="https://voltforge-mocha.vercel.app/images/og-firmware-control.svg">
<meta property="og:url" content="https://voltforge-mocha.vercel.app/papers/firmware-control.html">
<meta property="og:type" content="article">
<meta property="og:site_name" content="VoltForge">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Firmware Control — VoltForge">
<meta name="twitter:description" content="Phase IV: Zero-cost abstraction firmware architecture using Rust no_std type-state FSM targeting STM32G4 to enforce safety invariants at compile time.">
<meta name="twitter:image" content="https://voltforge-mocha.vercel.app/images/og-firmware-control.svg">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
</head>
<body class="paper-page">
<nav class="nav">
<div class="nav-inner">
<a href="../index.html" class="nav-logo"><span class="volt">Volt</span>Forge</a>
<button class="nav-toggle" aria-label="Toggle navigation">
<span></span><span></span><span></span>
</button>
<ul class="nav-links">
<li><a href="../index.html">Home</a></li>
<li><a href="../index.html#papers">All Papers</a></li>
<li><a href="thermal-management.html">Prev: Phase 3</a></li>
<li><a href="synthesis-rtos.html">Next: Phase 5</a></li>
</ul>
</div>
</nav>
<header class="paper-header">
<span class="paper-phase">Phase 4</span>
<h1>Type-Safe Embedded Firmware for Real-Time Power Regulation in Rust</h1>
<div class="paper-meta">
<p>Matthew Long — The YonedaAI Collaboration, YonedaAI Research Collective, Chicago, IL</p>
<p>March 21, 2026</p>
</div>
</header>
<main class="paper-content">
<div class="paper-notice">
This is the HTML summary version. <a href="../pdf/firmware-control.pdf">Download the full PDF</a> for complete Rust code listings, state machine diagrams, and HIL test results.
</div>
<h2>Abstract</h2>
<p>
We present the firmware control layer of VoltForge, implemented in Rust under <code>#![no_std]</code> constraints targeting the STM32G474 (ARM Cortex-M4F). The firmware achieves memory safety without garbage collection and zero-cost abstractions for register-level peripheral access. A type-state machine enforces valid adapter state transitions at compile time. The control loop executes a fixed-point PID regulator at 50 kHz with worst-case execution time under $12\,\mu\text{s}$ on a 170 MHz core. Battery pack identification employs a typed protocol abstraction supporting SMBus, I2C, one-wire, and resistor ID schemes.
</p>
<h2>1. Introduction</h2>
<p>
The choice of Rust is deliberate: in a system handling 30 A at 60 V, a firmware bug is not merely an inconvenience — it is a fire hazard. Rust eliminates null pointer dereferences, buffer overflows, data races, and use-after-free errors through its ownership model while imposing zero runtime overhead.
</p>
<p>Specifically, the Rust firmware provides:</p>
<ul>
<li><strong>Memory safety without garbage collection</strong> — the borrow checker prevents data races in interrupt handlers without GC pauses</li>
<li><strong>Zero-cost abstractions</strong> — generic types and monomorphization produce code equivalent to hand-written register manipulation</li>
<li><strong>Type-state patterns</strong> — peripherals transition through configuration states as zero-sized types, making misconfiguration a compile error</li>
<li><strong>Algebraic types for exhaustive error handling</strong> — <code>Result<T, E></code> forces handling of every error path</li>
</ul>
<h2>2. Background</h2>
<h3>Real-Time Constraints</h3>
<p>
At 100 kHz switching with 50 kHz control rate, the control loop has a hard deadline of $T_{\text{ctrl}} = 1/50{,}000 = 20\,\mu\text{s}$. On a 170 MHz Cortex-M4F, this corresponds to 3,400 clock cycles.
</p>
<h3>The Rust no_std Ecosystem</h3>
<p>
The embedded Rust ecosystem provides <code>embedded-hal</code> traits for hardware abstraction, PAC (Peripheral Access Crate) for register-level access, and HAL crates for STM32 peripherals.
</p>
<h3>Type-State Programming</h3>
<p>
Type-state programming encodes state machine transitions in the type system. An <code>Adc<Unconfigured></code> cannot be read; it must first be converted to <code>Adc<Configured></code> through a method that returns the new type. Invalid states become compile errors.
</p>
<h2>3. Hardware Platform: STM32G474</h2>
<table>
<thead><tr><th>Feature</th><th>Specification</th></tr></thead>
<tbody>
<tr><td>Core</td><td>ARM Cortex-M4F, 170 MHz</td></tr>
<tr><td>Flash / SRAM</td><td>512 KB / 128 KB</td></tr>
<tr><td>ADC</td><td>5x 12-bit, 4.27 Msps</td></tr>
<tr><td>Timer</td><td>HRTIM (5.44 GHz resolution)</td></tr>
<tr><td>PWM</td><td>Advanced, dead-time insertion</td></tr>
<tr><td>Comms</td><td>I2C, SPI, UART, CAN-FD</td></tr>
</tbody>
</table>
<h2>4. Type-Safe State Machine</h2>
<h3>State Transition Diagram</h3>
<p>The adapter progresses through a well-defined state machine:</p>
<pre><code>Init -> SelfTest -> DetectBattery -> VerifyChemistry ->
VerifyTool -> Precharge -> Regulating -> Derate -> Shutdown</code></pre>
<p>
Each state is a distinct Rust type. Transitions consume the old state and produce the new one, making invalid transitions impossible:
</p>
<pre><code>impl Adapter<SelfTest> {
fn detect_battery(self) -> Result<Adapter<DetectBattery>, Adapter<Fault>> {
// self is consumed — cannot use SelfTest state after this
}
}</code></pre>
<h3>Compile-Time Enforcement</h3>
<p>
The type-state pattern ensures you cannot call <code>regulate()</code> on an adapter in <code>SelfTest</code> state — the method simply does not exist on that type. This eliminates an entire class of runtime errors.
</p>
<h3>Fault Transitions</h3>
<p>
Every state can transition to <code>Fault</code>, but <code>Fault</code> can only transition to <code>Init</code> (power cycle) or <code>Shutdown</code>. The fault type carries an error code for diagnostics.
</p>
<h2>5. Real-Time Control Loop</h2>
<h3>Architecture</h3>
<p>
The control loop runs as a timer interrupt at 50 kHz. Within each $20\,\mu\text{s}$ period: read ADC (voltage, current, temperature), compute PID output, update PWM duty cycle, check safety limits.
</p>
<h3>Fixed-Point PID Implementation</h3>
<p>
Q16.16 fixed-point arithmetic for deterministic execution. The PID gains are stored as fixed-point values with saturation arithmetic to prevent overflow.
</p>
<h3>Worst-Case Execution Time</h3>
<table>
<thead><tr><th>Operation</th><th>Cycles</th><th>Time</th></tr></thead>
<tbody>
<tr><td>ADC read (3 channels)</td><td>~200</td><td>1.2 $\mu$s</td></tr>
<tr><td>PID computation</td><td>~350</td><td>2.1 $\mu$s</td></tr>
<tr><td>Safety checks</td><td>~150</td><td>0.9 $\mu$s</td></tr>
<tr><td>PWM update</td><td>~50</td><td>0.3 $\mu$s</td></tr>
<tr><td>Logging</td><td>~100</td><td>0.6 $\mu$s</td></tr>
<tr><td><strong>Total</strong></td><td><strong>~850</strong></td><td><strong>5.0 $\mu$s</strong></td></tr>
</tbody>
</table>
<p>WCET of $\sim 12\,\mu\text{s}$ (with margin for cache misses and interrupt latency) well within the $20\,\mu\text{s}$ deadline.</p>
<h2>6. Battery Communication Protocol</h2>
<h3>Multi-Protocol Support</h3>
<p>
A trait-based abstraction supports SMBus (DeWalt, Milwaukee), I2C, one-wire, and resistor ID schemes. The <code>BatteryIdentifier</code> trait unifies all protocols behind a common interface.
</p>
<h3>Compatibility Verification</h3>
<p>
Battery identification produces a zero-sized proof token (<code>CompatibilityProof</code>) that is required by the type system to initiate regulation. Without valid identification, the firmware cannot compile a path to the regulating state.
</p>
<h2>7. Hardware-in-the-Loop Testing</h2>
<ul>
<li><strong>DAC-based signal injection:</strong> Simulated voltage, current, and temperature signals via DAC outputs</li>
<li><strong>Automated test sequences:</strong> Sweep through operating conditions, verify control response</li>
<li><strong>Fault injection:</strong> Simulated overcurrent, overtemperature, communication failure</li>
<li><strong>Coverage metrics:</strong> All state transitions exercised, all fault paths validated</li>
</ul>
<h2>8. Safety and Reliability</h2>
<h3>Defense-in-Depth</h3>
<p>Multiple independent protection layers: hardware comparators (fastest), firmware PID limits (fast), safety supervisor task (monitored), watchdog timer (last resort).</p>
<h3>Watchdog Timer</h3>
<p>Independent watchdog (IWDG) with 10 ms timeout. If the control loop misses its deadline or the firmware hangs, the watchdog resets the system to a safe state.</p>
<h3>Stack Overflow Protection</h3>
<p>MPU (Memory Protection Unit) configured to detect stack overflow, triggering a hard fault that enters safe shutdown.</p>
<h2>9. Diagnostics and Logging</h2>
<p>
<code>defmt</code>-based zero-copy logging framework with structured error codes, runtime telemetry (voltage, current, temperature, duty cycle at configurable rates), and post-mortem diagnostics stored in retained SRAM.
</p>
<h2>10. Composition into Phase 5 (RTOS)</h2>
<p>
The firmware modules compose into Phase 5 as Embassy async tasks: the control loop as a high-priority timer task, thermal monitoring as a periodic task, battery identification as an on-demand task, and diagnostics as a low-priority background task.
</p>
<div class="insight-box">
<p><strong>Phase 5 Interface:</strong> Each firmware module exposes an <code>async fn run()</code> entry point that the Phase 5 RTOS executor schedules according to the priority hierarchy.</p>
</div>
<nav class="paper-nav">
<a href="thermal-management.html">← Phase 3: Thermal Management</a>
<a href="../index.html">Home</a>
<a href="synthesis-rtos.html">Phase 5: RTOS Synthesis →</a>
</nav>
</main>
<footer class="footer">
<div class="footer-inner">
<ul class="footer-links">
<li><a href="https://github.com/mlong/voltforge" target="_blank" rel="noopener">GitHub</a></li>
<li><a href="https://yonedaai.com" target="_blank" rel="noopener">YonedaAI</a></li>
</ul>
<span class="footer-copy">© 2026 VoltForge — Matthew Long / YonedaAI</span>
</div>
</footer>
<script src="../js/main.js"></script>
</body>
</html>