<!--date=20250227 -->


<h1>Embedded Systems Design</h1>
<p>An embedded system is a computer built into a device to do a specific job. The controller in a thermostat, the engine management unit in a car, the processor in a camera — all embedded systems. The design decisions differ fundamentally from software on a general-purpose computer: resources are constrained, real-time behaviour often matters, and the software usually can't be patched in the field. Getting the architecture right early matters far more than it does for desktop software.</p>

<h2>Microcontroller Selection</h2>

<table>
  <thead><tr><th>Category</th><th>Examples</th><th>Flash/RAM</th><th>Performance</th><th>Use when</th></tr></thead>
  <tbody>
    <tr><td>8-bit AVR</td><td>ATmega328P, ATtiny85</td><td>2–256KB / 0.5–32KB</td><td>16 MHz, 16 MIPS</td><td>Simple I/O control, Arduino ecosystem, low cost</td></tr>
    <tr><td>8-bit PIC</td><td>PIC12F, PIC16F, PIC18F</td><td>1–128KB / 128B–4KB</td><td>1–64 MHz</td><td>Industrial, automotive, Microchip-centric supply chains</td></tr>
    <tr><td>ARM Cortex-M0/M0+</td><td>STM32F0, SAMD21, RP2040</td><td>16KB–1MB / 4–264KB</td><td>48–133 MHz</td><td>Mid-complexity, low power, DMA-capable peripherals</td></tr>
    <tr><td>ARM Cortex-M4/M7</td><td>STM32F4, STM32H7, SAME70</td><td>Up to 2MB / up to 1MB</td><td>168–600 MHz, FPU</td><td>DSP, motor control, USB, Ethernet, real-time control</td></tr>
    <tr><td>ARM Cortex-M33</td><td>STM32L5, nRF9160</td><td>Various</td><td>64–64 MHz</td><td>Security features (TrustZone), low-power IoT</td></tr>
    <tr><td>Application processor</td><td>Raspberry Pi, BeagleBone, iMX6</td><td>512MB–4GB RAM</td><td>1–2+ GHz</td><td>Linux OS required, networking, complex UI, camera</td></tr>
  </tbody>
</table>

<p>The RP2040 (Raspberry Pi Pico, $1–4) stands out for its Programmable I/O (PIO) — 8 small state machines that implement custom serial protocols in hardware without touching the CPU. It can bit-bang SPI, UART, WS2812B LED protocols, and VGA signals in hardware at high speed.</p>

<h2>Communication Interfaces</h2>

<table>
  <thead><tr><th>Interface</th><th>Wires</th><th>Speed</th><th>Multi-device</th><th>Half/Full duplex</th><th>Best for</th></tr></thead>
  <tbody>
    <tr><td>UART</td><td>2 (TX, RX)</td><td>Up to ~12 Mbps</td><td>No (point to point)</td><td>Full</td><td>Debug, GPS, BT modules, PC communication</td></tr>
    <tr><td>SPI</td><td>4 (MOSI, MISO, SCK, CS per device)</td><td>Up to 100+ Mbps</td><td>Yes (one CS per device)</td><td>Full</td><td>Displays, SD cards, ADCs/DACs, SPI Flash</td></tr>
    <tr><td>I2C</td><td>2 (SDA, SCL)</td><td>100k–3.4 Mbps</td><td>Yes (127 devices, 7-bit addr)</td><td>Half</td><td>Sensors, EEPROMs, RTC, BME280, MPU-6050</td></tr>
    <tr><td>1-Wire</td><td>1 (+ GND)</td><td>~15 kbps</td><td>Yes (ROM address per device)</td><td>Half</td><td>DS18B20 thermometer, iButton authentication</td></tr>
    <tr><td>CAN</td><td>2 (CAN_H, CAN_L differential)</td><td>Up to 1 Mbps (CAN FD: 8 Mbps)</td><td>Yes (multi-master)</td><td>Half</td><td>Automotive, industrial — high noise immunity</td></tr>
    <tr><td>USB</td><td>4 (D+, D−, VBUS, GND)</td><td>1.5–480 Mbps</td><td>Yes (host-controlled)</td><td>Half</td><td>PC connectivity, HID, CDC (virtual COM port)</td></tr>
    <tr><td>Ethernet</td><td>Twisted pair + magnetics</td><td>10/100/1000 Mbps</td><td>Yes (IP addressing)</td><td>Full</td><td>Internet connectivity, industrial, Modbus/TCP</td></tr>
  </tbody>
</table>

<h3>I2C Pull-up Resistors</h3>
<pre>
Required: 4.7kΩ pull-up to VCC on both SDA and SCL

For fast I2C (400kHz) or many devices:
  Use 2.2kΩ pull-ups (stronger pull, faster edges, more current)
  
For long cables or 3.3V systems:
  Use 4.7kΩ → 10kΩ (weaker pull, slower, but lower current and less ringing)

Maximum pull-up resistance: R_max = 0.8V / I_ol(min) for standard mode
  At 100kHz: V_OH = 0.8V, I_ol min = 3mA → R_max ≈ 267Ω (absolute minimum)
  Practical: 2.2kΩ – 10kΩ for most designs
</pre>

<h2>Real-Time Considerations</h2>

<table>
  <thead><tr><th>Approach</th><th>When to use</th><th>Pros</th><th>Cons</th></tr></thead>
  <tbody>
    <tr><td>Bare metal (super-loop + ISR)</td><td>Simple, single-function systems; low resource cost</td><td>Minimal overhead, deterministic, easy to understand</td><td>Complex tasks with multiple timings become difficult to manage</td></tr>
    <tr><td>Cooperative multitasking</td><td>Multiple tasks, all well-behaved (yield voluntarily)</td><td>Low overhead, no preemption surprises</td><td>One task hanging can block everything</td></tr>
    <tr><td>RTOS (FreeRTOS, Zephyr)</td><td>Multiple tasks with priority and timing requirements</td><td>Task isolation, priority preemption, timing guarantees</td><td>Context switch overhead, complexity, stack usage per task</td></tr>
  </tbody>
</table>

<h3>FreeRTOS Basics</h3>
<pre>
Tasks: each has own stack, priority, and function
Queues: pass data between tasks safely (thread-safe FIFO)
Semaphores: synchronise tasks (binary: signal/wait)
Mutexes: protect shared resources from concurrent access
Timers: callback after delay or periodically (timer task)

Task priorities: higher number = higher priority
  Idle task runs at priority 0
  Your tasks: 1 (low) to configMAX_PRIORITIES-1 (high)

Stack sizing: each task needs its own stack
  Start with configMINIMAL_STACK_SIZE × 2
  Use uxTaskGetStackHighWaterMark() to check if stack is adequate
</pre>

<h2>Power Management for Battery Devices</h2>
<pre>
Battery life = Battery_capacity / Average_current

Example:
  2000 mAh Li-ion cell
  Active: 20mA for 10ms every 10 seconds
  Sleep: 5µA for the rest

  Avg current = (20mA × 10ms + 5µA × 9990ms) / 10,000ms
              = (0.2 mAs + 49.95 mAs) / 10,000ms
              = 50.15 µA average

  Battery life = 2000 mAh / 0.05015 mA ≈ 39,872 hours ≈ 4.5 years
</pre>

<table>
  <thead><tr><th>Strategy</th><th>Savings</th><th>Notes</th></tr></thead>
  <tbody>
    <tr><td>Deep sleep / hibernate</td><td>High (µA vs mA)</td><td>Wake on GPIO interrupt or RTC alarm. Shortest wake time for longest battery life.</td></tr>
    <tr><td>Disable peripheral clocks</td><td>Moderate</td><td>Unused UART, SPI, I2C blocks burn clock power even idle</td></tr>
    <tr><td>Lower clock frequency</td><td>Moderate</td><td>Dynamic power ∝ f × C × V². Minimum clock for required throughput.</td></tr>
    <tr><td>Reduce supply voltage</td><td>High</td><td>Dynamic power ∝ V². 1.8V vs 3.3V saves (1.8/3.3)² ≈ 30% of dynamic power</td></tr>
    <tr><td>Power gate peripherals</td><td>High</td><td>Enable pin or MOSFET switch for sensors, radios, displays — off between uses</td></tr>
    <tr><td>Use DMA for I/O</td><td>Moderate</td><td>CPU sleeps while DMA transfers data — reduces active time</td></tr>
  </tbody>
</table>

<h2>Debugging Tools</h2>

<table>
  <thead><tr><th>Tool</th><th>What it does</th><th>Cost</th><th>Notes</th></tr></thead>
  <tbody>
    <tr><td>SWD / JTAG debugger</td><td>Program, breakpoints, register/memory inspection</td><td>$10–$500</td><td>ST-Link V2 (~$3 clone) works for STM32. CMSIS-DAP for universal ARM.</td></tr>
    <tr><td>Logic analyser</td><td>Capture and decode SPI, I2C, UART, CAN, etc.</td><td>$10–$400</td><td>Cheap Cypress FX2 clones work with PulseView (open source). Saleae for professional use.</td></tr>
    <tr><td>Oscilloscope</td><td>Analog waveforms, timing, noise, power rails</td><td>$100–$50,000</td><td>Rigol DS1054Z (~$350) is excellent entry-level. 4 channels, 50MHz.</td></tr>
    <tr><td>Serial debug (printf via UART)</td><td>Print state, variable values, timestamps to terminal</td><td>Free</td><td>USB-UART bridge (CH340, CP2102) + terminal (PuTTY, screen, minicom)</td></tr>
    <tr><td>ITM/SWO tracing</td><td>printf-style output via SWD debug port (no UART needed)</td><td>Free (if using SWD)</td><td>Available on Cortex-M3/M4/M7/M33. Supported in OpenOCD and J-Link.</td></tr>
    <tr><td>LED indicators</td><td>Toggle pin to show code reaching a point</td><td>Free</td><td>The oldest and most reliable debugging technique. Never underestimate it.</td></tr>
  </tbody>
</table>

<h2>Common Embedded Bugs</h2>

<table>
  <thead><tr><th>Bug type</th><th>Symptom</th><th>Root cause and fix</th></tr></thead>
  <tbody>
    <tr><td>Stack overflow</td><td>Random crashes, corrupted variables, hard fault</td><td>Task or ISR stack too small. Increase stack, reduce local variable size, use heap for large buffers.</td></tr>
    <tr><td>Race condition</td><td>Data corruption, unpredictable behaviour</td><td>Shared variable modified in ISR and main. Use volatile, critical section, or atomic access.</td></tr>
    <tr><td>Interrupt priority inversion</td><td>High-priority task blocked by low-priority</td><td>Use priority inheritance mutexes (FreeRTOS mutexes have this); avoid blocking ISRs.</td></tr>
    <tr><td>Watchdog timeout</td><td>System resets periodically</td><td>Pet the watchdog more frequently, or identify what's blocking execution.</td></tr>
    <tr><td>I2C hang (bus stuck)</td><td>MCU hangs in I2C transfer, no timeout</td><td>Power cycle the I2C bus (or clock it manually), add timeout to all blocking I2C calls.</td></tr>
    <tr><td>Floating GPIO</td><td>Intermittent spurious interrupts or readings</td><td>Every GPIO input must have a defined state — pull-up or pull-down to VCC or GND.</td></tr>
  </tbody>
</table>

<h2>References</h2>
<ul>
  <li>White — <em>Making Embedded Systems</em> (O'Reilly) — practical and direct</li>
  <li>Barr &amp; Massa — <em>Programming Embedded Systems</em></li>
  <li><a href="https://www.freertos.org/Documentation/RTOS_book.html" target="_blank" rel="noopener">FreeRTOS Reference Manual (free PDF) ↗</a></li>
  <li><a href="https://interrupt.memfault.com/" target="_blank" rel="noopener">Interrupt Blog (Memfault) — embedded systems deep dives ↗</a></li>
  <li><a href="https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html" target="_blank" rel="noopener">STM32 reference manuals and application notes ↗</a></li>
</ul>
