Hardware Engineering

Embedded Hardware
Design

From a napkin sketch to a working PCB — the complete journey of turning requirements into silicon, copper, and firmware.

Prerequisites: Basic electronics (voltage, current, Ohm's law) + curiosity about how devices work. That's it.
10
Chapters
10+
Simulations
0
Assumed Knowledge

Chapter 0: The Design Problem

You've been asked to build a wearable health monitor. The requirements are brutally specific: measure heart rate and blood oxygen saturation (SpO2), transmit data via Bluetooth Low Energy, survive 7 days on a single CR2032 coin cell battery. Budget: $8 total bill of materials. Maximum PCB size: 25mm × 25mm.

Where do you even start? You can't just grab random parts off Digikey and hope they work together. Every component choice cascades through the entire system. Pick the wrong MCU and you blow your power budget in 6 hours. Choose the wrong sensor interface and you run out of pins. Select a wireless chip that needs an external antenna and you've exceeded your board size.

The fundamental tension: Every embedded design is a multi-dimensional optimization problem. You're simultaneously optimizing for power, cost, size, performance, reliability, and manufacturability. Improve one axis, and another suffers. The art is finding the sweet spot for YOUR application.

The design process follows a strict sequence. Skip a step, and you'll pay for it later — with redesigns, blown budgets, or products that don't work in the field.

Requirements
What must the system DO? Measurables only.
Architecture
Block diagram: what talks to what, how.
Component Selection
Pick real parts. Check datasheets.
Schematic
Wire it up logically. Add decoupling, ESD.
PCB Layout
Physical placement. Trace routing. DRC.
Firmware
Bring-up, drivers, application logic.
Test & Validate
Does it meet ALL requirements?

Let's walk through our wearable example. The requirements decompose into concrete engineering constraints:

RequirementEngineering Constraint
Heart rate + SpO2Need PPG sensor (e.g., MAX30102), I²C interface
BLE transmissionNeed 2.4GHz radio + antenna. nRF52 or similar.
7 days on CR2032CR2032 = 225mAh. Budget = 225/168h = 1.34mA average
$8 BOMMCU+radio: $3, sensor: $2, passives+PCB: $3
25mm × 25mmQFN packages, 0402 passives, chip antenna
The power budget is king. 1.34mA average means you cannot have the radio on continuously (BLE TX draws ~8mA). You MUST duty-cycle: measure for 2 seconds every 30 seconds, transmit a BLE packet, then sleep at <5μA. This single constraint drives your entire MCU choice.
Design Flow Explorer

Click each stage to see the key decisions and tradeoffs for our wearable health monitor.

Notice how the power budget calculation immediately told us something crucial: we need aggressive duty cycling. That's the beauty of the requirements-first approach — you discover constraints BEFORE you commit to hardware.

python
# Power budget calculation for wearable health monitor
battery_mAh = 225        # CR2032 capacity
target_days = 7
target_hours = target_days * 24  # 168 hours

avg_current_mA = battery_mAh / target_hours
print(f"Max average current: {avg_current_mA:.2f} mA")
# Output: Max average current: 1.34 mA

# Duty cycle budget breakdown:
sleep_current_uA = 3       # MCU deep sleep + RTC
measure_current_mA = 12   # PPG sensor + ADC active
ble_tx_current_mA = 8    # BLE advertisement packet

# If we measure 2s every 30s and TX for 5ms:
duty_measure = 2 / 30       # 6.7% active
duty_tx = 0.005 / 30      # 0.017% TX
duty_sleep = 1 - duty_measure - duty_tx

avg = (sleep_current_uA/1000 * duty_sleep +
       measure_current_mA * duty_measure +
       ble_tx_current_mA * duty_tx)
print(f"Actual average: {avg:.3f} mA")
# Output: Actual average: 0.803 mA ✓ (under 1.34 mA budget)
Why can't you simply pick the fastest MCU with the most features for this wearable design?

Chapter 1: Communication Buses

Before you can wire components together, you need to understand how digital chips talk to each other. There are four dominant protocols in embedded systems, each with different tradeoffs. Picking the wrong bus for a component means either wasted pins, insufficient speed, or unnecessary complexity.

SPI — Serial Peripheral Interface

SPI uses 4 wires: SCLK (clock), MOSI (master out, slave in), MISO (master in, slave out), and CS (chip select, one per device). It's full-duplex — data flows both directions simultaneously. The master generates the clock, so there's no baud rate negotiation. Speeds easily reach 10-50 MHz.

Use SPI for: displays (ILI9341), external Flash (W25Q128), SD cards, high-speed ADCs. Anything that needs to move lots of data fast.

SPI's cost: Every additional slave needs its own CS pin. With 5 SPI devices, you burn 4 shared wires + 5 CS pins = 9 total GPIO. On a 32-pin MCU, that's nearly a third of your pins.

I²C — Inter-Integrated Circuit

I²C uses just 2 wires: SDA (data) and SCL (clock). Devices are addressed with a 7-bit address, so up to 127 devices can share the same two wires. Standard mode runs at 100 kHz, fast mode at 400 kHz, fast-mode-plus at 1 MHz. It's half-duplex — one direction at a time.

Use I²C for: temperature sensors (TMP102), accelerometers (MPU-6050), EEPROM (AT24C256), pressure sensors (BMP280). Anything that sends small packets infrequently.

I²C's hidden cost: Those 2 wires need external pull-up resistors (typically 4.7kΩ to VCC). Forget them and the bus floats — you'll see garbage data. Also, I²C is open-drain: devices can only pull LOW. The resistors pull the line HIGH when nobody's talking.

UART — Universal Asynchronous Receiver/Transmitter

UART uses 2 wires: TX (transmit) and RX (receive). There's no clock — both sides must agree on a baud rate beforehand (9600, 115200, etc.). It's point-to-point: one transmitter, one receiver. Full-duplex but asynchronous.

Use UART for: GPS modules (NEO-6M), debug printf output, Bluetooth modules (HC-05), cellular modems. Anything that streams text-like data or needs a simple debug port.

PDM — Pulse Density Modulation

PDM uses just 1 data wire + 1 clock wire. It encodes audio as a stream of 1s and 0s at very high frequency (1-3 MHz). More 1s = louder signal. A decimation filter in the MCU converts this to PCM audio. Used exclusively for digital MEMS microphones.

Use PDM for: MEMS microphones (SPH0645, MP34DT05). That's basically it — it's a single-purpose protocol optimized for one-bit sigma-delta audio streams.

ProtocolWiresSpeedTopologyBest For
SPI4 + 1/slave10-50 MHz1 master, N slavesDisplays, Flash, ADCs
I²C2 (shared)100k-1M HzMulti-master, 127 addrSensors, EEPROM
UART2 (P2P)9600-921600 bpsPoint-to-pointGPS, debug, modems
PDM2 (CLK+DAT)1-3 MHzPoint-to-pointMEMS microphones
Bus Waveform Viewer

Select a protocol to watch a byte (0x42 = 0b01000010) transmitted. Observe clock vs data timing.

Look at the SPI waveform: MOSI changes on the falling edge of SCLK, and the slave samples on the rising edge. This is "CPOL=0, CPHA=0" — the most common SPI mode. Notice the CS line goes LOW to select the device.

Now look at I²C: it starts with a start condition (SDA drops while SCL is high), then sends 7 address bits + R/W bit, waits for an ACK from the slave, THEN sends the data byte. More overhead, but only 2 wires for everything.

c
// I2C read example: read temperature from TMP102 at address 0x48
#include "stm32l4xx_hal.h"

uint16_t read_temp(I2C_HandleTypeDef *hi2c) {
    uint8_t buf[2];
    uint8_t addr = 0x48 << 1;  // 7-bit addr shifted left

    // Read 2 bytes from register 0x00 (temperature)
    HAL_I2C_Mem_Read(hi2c, addr, 0x00, I2C_MEMADD_SIZE_8BIT,
                     buf, 2, 100);

    // TMP102 returns 12-bit temp, MSB first
    int16_t raw = (buf[0] << 4) | (buf[1] >> 4);
    // Each LSB = 0.0625°C
    return raw;  // Multiply by 0.0625 for °C
}
Rule of thumb for bus selection: If your data rate exceeds 1 Mbps, use SPI. If you need multiple low-speed sensors on minimal pins, use I²C. If you're talking to one module that speaks ASCII, use UART. If it's a microphone, use PDM.
You need to connect 8 temperature sensors to a single MCU. Which bus minimizes pin usage?

Chapter 2: MCU Selection

The microcontroller is the brain of your system. It runs your firmware, talks to sensors, drives displays, and manages power states. Choosing the right one is the single most consequential decision in your hardware design — get it wrong, and you either overpay, overheat, or run out of resources mid-development.

The ARM Cortex-M Family

Nearly all modern embedded MCUs use ARM Cortex-M cores. The family spans from ultra-low-power to high-performance:

CoreClassClockFeaturesCostExample
Cortex-M0/M0+Ultra-low-power24-64 MHzMinimal gate count, lowest sleep current$0.30-1.00STM32L0, nRF51
Cortex-M4Mainstream DSP64-180 MHzFPU, DSP instructions, SIMD$2-5STM32L4, nRF52840
Cortex-M7High-performance200-600 MHzDouble-precision FPU, cache, TCM$5-15STM32H7, i.MX RT1060
Cortex-M33Security-focused64-160 MHzTrustZone, M4-class + security$3-8STM32U5, nRF5340

The Decision Matrix

Don't pick an MCU by looking at specs in isolation. Build a decision matrix that weights your actual requirements:

1. Peripherals
Do I need USB? CAN? I²S? Ethernet? QSPI? Count interfaces.
2. Power Budget
Sleep current (μA), active current (mA/MHz), wake-up time.
3. Processing
Need FPU? DSP? How many MIPS? Enough for your algorithm?
4. Memory
Flash for code (64KB-2MB), SRAM for data (16KB-1MB).
5. Ecosystem
SDK quality, debugger support, community, documentation.
6. Supply Chain
Can I actually BUY this part? Second source available?

Real Comparison: Four Popular MCUs

ParameterSTM32L4R5ESP32-S3nRF52840RP2040
CoreM4 @ 120MHzXtensa LX7 dual @ 240MHzM4 @ 64MHzM0+ dual @ 133MHz
Flash2MB internal8MB external1MB internalExternal QSPI
SRAM640KB512KB256KB264KB
WirelessNoneWiFi + BLE 5BLE 5, Thread, ZigbeeNone
Sleep current1.0 μA (shutdown)~5 μA (deep sleep)1.5 μA (system OFF)0.8 μA (dormant)
Active (mA/MHz)~0.1~0.3~0.1~0.1
FPUYes (single)Yes (single)Yes (single)No
Price (1k qty)~$5~$2.50~$3.50~$0.80
Best forUltra-low-power sensingWiFi IoT, AI/ML edgeBLE wearablesEducation, cost-sens.
For our wearable: The nRF52840 wins. It has integrated BLE (saves $2+ on external radio), excellent sleep current (1.5μA), enough Flash/SRAM for BLE stack + app code, and the nRF Connect SDK is mature. The ESP32 has WiFi we don't need (and worse sleep current). The STM32L4 lacks built-in radio. The RP2040 has no FPU and no wireless.
MCU Comparison Radar Chart

Click MCU names to toggle them on the radar. Compare performance, power efficiency, cost, peripherals, and memory.

c
// nRF52840: Typical low-power BLE application structure
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "nrf_pwr_mgmt.h"

int main(void) {
    // 1. Init clocks — use 64MHz HFCLK only when active
    nrf_drv_clock_init();

    // 2. Init BLE SoftDevice (handles radio in background)
    nrf_sdh_enable_request();
    ble_stack_init();

    // 3. Start advertising — 1 packet every 1000ms
    advertising_init(ADV_INTERVAL_1000MS);
    advertising_start();

    // 4. Main loop: MCU sleeps between BLE events
    for (;;) {
        nrf_pwr_mgmt_run();  // Enters System ON sleep
        // Wake on BLE event, timer, or GPIO interrupt
    }
}
Your project needs WiFi, a camera interface, and runs an ML model at the edge. Which MCU family is the best fit?

Chapter 3: Memory & Storage

Memory in embedded systems is nothing like a PC where you have gigabytes of unified RAM. In an MCU, you're working with kilobytes, every byte has a specific purpose, and different memory types have wildly different access speeds and power costs. Understanding the memory hierarchy is essential for writing firmware that actually fits and runs fast.

The Four Memory Types

Internal Flash stores your compiled firmware code. It's non-volatile (survives power loss), but slow to write (must erase entire sectors first). Typical sizes: 128KB to 2MB. Your code executes directly from Flash (XIP — execute in place) or gets copied to SRAM for faster execution.

Internal SRAM is your working memory — variables, stack, heap, DMA buffers. It's fast (single-cycle access), volatile (lost on power loss), and precious. Typical sizes: 16KB to 1MB. Every global variable, every buffer, every stack frame lives here.

External QSPI Flash is for bulk storage — fonts, images, audio clips, data logs. Connected via SPI or QSPI (quad-SPI for 4x bandwidth). Typical sizes: 1MB to 128MB. Access is slower than internal Flash, but you can get enormous capacities cheaply.

External SDRAM/PSRAM provides large working memory for frame buffers, ML model weights, or audio processing. Connected via a dedicated memory controller. Typical sizes: 2MB to 64MB. Only found on high-end MCUs (STM32H7, i.MX RT).

The golden rule of embedded memory: Internal memory is fast but small. External memory is large but slow. Your job is to keep hot data (interrupt handlers, critical loops) in internal SRAM, and cold data (fonts, logs, configuration) in external Flash.
TypeSpeedSizeCostVolatile?Use For
Internal Flash~50ns (0 wait @ low clock)128KB-2MBIncludedNoFirmware code
Internal SRAM~6ns (single cycle)16KB-1MBIncludedYesVariables, stack, DMA
External QSPI Flash~100ns (memory-mapped)1-128MB$0.20-2.00NoAssets, logs, OTA images
External SDRAM~10ns (burst)2-64MB$1-5YesFrame buffers, ML weights

Worked Example: Does Our Firmware Fit?

Our wearable has an nRF52840 with 1MB Flash and 256KB SRAM. Let's check if our application fits:

text
Memory Budget — nRF52840 Wearable Health Monitor

═══ FLASH (1024 KB total) ═══
SoftDevice (BLE stack):         152 KB  # Nordic's S140 BLE stack
Bootloader (DFU capable):        24 KB  # For over-the-air updates
Application code:                ~80 KB  # Sensor drivers + BLE services
PPG signal processing (DSP):     ~30 KB  # FFT, peak detection, SpO2 calc
Configuration storage:             4 KB  # User settings, calibration
────────────────────────────────────────
Total used:                     290 KB
Remaining:                      734 KB  ✓ Plenty of headroom

═══ SRAM (256 KB total) ═══
SoftDevice (BLE stack):          ~8 KB  # Connection buffers
Stack (main + ISR):               8 KB  # Conservative
Heap:                             4 KB  # Minimal dynamic allocation
PPG sample buffer (4s @ 100Hz): 1.6 KB  # 400 samples × 4 bytes
FFT working buffer:               4 KB  # 512-point complex FFT
BLE TX buffer:                   ~2 KB  # GATT attribute table
────────────────────────────────────────
Total used:                    ~28 KB
Remaining:                     228 KB  ✓ Very comfortable
We're fine. Both Flash and SRAM have enormous headroom. This is typical for sensor-only applications. You start worrying about memory when you add displays (font tables eat Flash), audio (buffers eat SRAM), or ML models (weights eat everything).
Memory Map Visualizer

Explore the memory layout. Hover over regions to see what lives there and why.

Application Size (KB) 80

Memory-Mapped vs SPI-Accessed

When an external Flash chip is memory-mapped (via QSPI XIP mode), the MCU can read it like internal memory — just dereference a pointer. The QSPI controller transparently fetches data. This is how fonts and images are typically stored: the code just reads from an address in the 0x90000000 range.

Without memory mapping, you must explicitly issue SPI read commands, copy data into SRAM, then use it. This is fine for occasional access but terrible for random reads (each access requires a full SPI transaction).

Effective bandwidth: QSPI @ 80MHz, 4 data lines = 80 × 4 = 320 Mbit/s = 40 MB/s
Your MCU has 256KB SRAM and you need a 320×240 RGB565 frame buffer. How much SRAM does the frame buffer consume?

Chapter 4: Sensor Selection & Signal Conditioning

Sensors are where the physical world meets the digital one. A temperature sensor converts thermal energy into a voltage. An accelerometer converts motion into a capacitance change. An optical sensor converts photons into current. But raw sensor outputs are messy — they're noisy, small, and often at the wrong voltage level. Signal conditioning is the art of cleaning up that mess before your ADC digitizes it.

Choosing a Sensor: The Spec Sheet Checklist

ParameterWhat It MeansExample (BMP280 pressure sensor)
RangeMin/max measurable value300-1100 hPa
ResolutionSmallest detectable change0.16 Pa (20-bit mode)
AccuracyHow close to true value±1 hPa absolute
InterfaceHow it talks to MCUI²C or SPI
Supply voltageWhat power rail it needs1.7V-3.6V
Current drawHow much power in active/sleep2.7μA @ 1Hz, 0.1μA sleep
Output typeDigital (processed) or analog (raw)Digital (compensated)
Digital vs analog sensors: Digital sensors (BMP280, MPU6050, MAX30102) have internal ADCs and send processed values over I²C/SPI. Analog sensors (thermistors, photodiodes, strain gauges) output raw voltages that YOU must amplify, filter, and digitize. Digital sensors cost more but save you PCB space and design effort. For production, always prefer digital unless you need custom signal processing.

Signal Conditioning Chain

When you DO use an analog sensor, the signal path looks like this:

Sensor
Raw output: 10mV/°C thermistor
Amplification
Op-amp gain: ×3.3 to use full ADC range
Filtering
RC low-pass: remove 50/60Hz mains noise
Level Shift
If sensor is 5V but MCU is 3.3V: voltage divider
ADC Sampling
12-bit @ 3.3V ref: resolution = 0.8mV/count

Worked Example: Temperature Measurement

You have an analog temperature sensor that outputs 10mV per °C. Your target range is 0-100°C. Your MCU's ADC reference voltage is 3.3V at 12-bit resolution.

python
# Signal conditioning calculation
sensor_sensitivity = 0.010  # 10mV/°C = 0.01 V/°C
temp_range = (0, 100)       # °C

# Raw sensor output range
v_min = temp_range[0] * sensor_sensitivity  # 0V at 0°C
v_max = temp_range[1] * sensor_sensitivity  # 1.0V at 100°C
print(f"Sensor output: {v_min}V to {v_max}V")

# ADC parameters
adc_ref = 3.3     # Volts
adc_bits = 12
adc_counts = 2**adc_bits  # 4096 levels
adc_lsb = adc_ref / adc_counts
print(f"ADC LSB: {adc_lsb*1000:.2f} mV")  # 0.81 mV

# Without amplification: sensor uses only 1V of 3.3V range
effective_bits_no_amp = 12 - 1.74  # log2(3.3/1.0) = 1.74 bits lost
temp_resolution_no_amp = 100 / (1.0 / adc_lsb)
print(f"Resolution without amp: {temp_resolution_no_amp:.3f} °C")  # 0.081°C

# With amplification: gain = 3.3/1.0 = 3.3x (fill the ADC range)
gain = adc_ref / v_max  # 3.3x
temp_resolution_with_amp = 100 / adc_counts
print(f"Resolution with 3.3x amp: {temp_resolution_with_amp:.3f} °C")  # 0.024°C

# RC low-pass filter: cut off above 10Hz (sensor can't change faster)
import math
f_cutoff = 10  # Hz
R = 10e3       # 10kΩ (typical)
C = 1 / (2 * math.pi * f_cutoff * R)
print(f"Filter cap: {C*1e6:.2f} µF")  # 1.59 µF → use 1.5µF standard
Nyquist reminder: Your ADC sampling rate must be at least 2× the highest frequency in your signal. For a temperature sensor changing at <1Hz, sampling at 10Hz is plenty. For audio (20kHz bandwidth), you need at least 40kHz sampling — which is why audio ADCs run at 44.1 or 48 kHz.
Signal Conditioning Chain

Adjust gain and filter cutoff to clean up the noisy sensor signal. Watch the signal transform from raw to ADC-ready.

Gain 3.3
Filter Cutoff (Hz) 10
Your 12-bit ADC has a 3.3V reference. Your sensor outputs 0-0.5V. What's the temperature resolution if the sensor is 10mV/°C?

Chapter 5: Wireless Communication

Adding wireless to an embedded system is where battery life goes to die — or thrive, depending on your choice. The four dominant wireless technologies each occupy a different niche in the range-power-bandwidth design space. Choose wrong, and you'll either drain your battery in hours or fail to reach your gateway.

WiFi (802.11b/g/n)

WiFi is the obvious choice because it's everywhere — but it's a terrible choice for battery-powered devices. A WiFi TX burst draws 100-300mA. The association handshake alone takes seconds. And the MCU needs a full TCP/IP stack (LWIP), eating 30-80KB of SRAM.

Use WiFi when: you have wall power, need internet access (HTTP, MQTT), high throughput (>1 Mbps), or are building a gateway/hub. Examples: smart plugs, security cameras, home automation hubs.

BLE (Bluetooth Low Energy)

BLE was designed from the ground up for coin-cell devices. A single connection event (TX + RX) takes ~3ms and draws ~8mA peak. Between events, the radio sleeps. With a 1-second connection interval, average current is ~15μA. A CR2032 lasts years.

BLE uses GATT profiles — structured data services. Your heart rate monitor exposes a "Heart Rate Service" with a "Heart Rate Measurement" characteristic. Any BLE-capable phone can read it. Maximum throughput: ~1 Mbps (BLE 5), practical: ~100-200 kbps.

Sub-GHz (LoRa, Sigfox, FSK)

Sub-GHz radios (433 MHz, 868 MHz, 915 MHz) trade bandwidth for range. LoRa achieves 10-15km line-of-sight at ~300 bps. TX current is ~30mA but transmissions are short (50-100ms). Sleep current: <1μA.

Use Sub-GHz when: your sensor is in a field, a building basement, or anywhere far from infrastructure. Examples: agricultural sensors, utility meters, asset trackers, weather stations.

USB (Universal Serial Bus)

USB isn't wireless, but it's the most common wired connection. USB 2.0 Full Speed (12 Mbps) is standard on MCUs. USB provides both data AND power (500mA from USB 2.0 host). For development, USB-CDC gives you a virtual serial port — printf debugging without a separate UART adapter.

TechnologyRangeTX CurrentThroughputSleepBattery Life
WiFi~50m indoor100-300mA1-100 Mbps~5μAHours-days
BLE~30m indoor5-15mA100-200 kbps~1.5μAMonths-years
LoRa2-15 km30-120mA0.3-50 kbps<1μAYears
USB 2.05m cableN/A (bus-powered)12 Mbps (FS)~0.5mA (suspend)N/A (wired)
The power hierarchy: USB (unlimited) > WiFi (wall power only) > BLE (coin cell friendly) > Sub-GHz (field-deployed years). If your device has a battery, eliminate WiFi unless you absolutely need internet connectivity. BLE covers 90% of wearable/consumer IoT use cases.

Power Budget: The Math That Matters

python
# Compare battery life: WiFi vs BLE for sending 20 bytes every 10 seconds
battery_mAh = 225  # CR2032

# WiFi: wake → associate (2s @ 100mA) → send (10ms @ 150mA) → sleep
wifi_assoc_ms = 2000
wifi_tx_ms = 10
wifi_sleep_ms = 10000 - wifi_assoc_ms - wifi_tx_ms
wifi_avg_mA = (100*wifi_assoc_ms + 150*wifi_tx_ms + 0.005*wifi_sleep_ms) / 10000
wifi_hours = battery_mAh / wifi_avg_mA
print(f"WiFi: avg {wifi_avg_mA:.1f} mA → {wifi_hours:.1f} hours")
# WiFi: avg 20.2 mA → 11.2 hours  ← DEAD IN HALF A DAY

# BLE: connection event (3ms @ 8mA) → sleep
ble_tx_ms = 3
ble_sleep_ms = 10000 - ble_tx_ms
ble_avg_mA = (8*ble_tx_ms + 0.0015*ble_sleep_ms) / 10000
ble_hours = battery_mAh / ble_avg_mA
print(f"BLE: avg {ble_avg_mA:.4f} mA → {ble_hours:.0f} hours ({ble_hours/24:.0f} days)")
# BLE: avg 0.0039 mA → 57692 hours (2404 days) ← 6.5 YEARS
That's a 5000x difference. WiFi kills a CR2032 in 11 hours. BLE makes it last 6+ years (limited by battery self-discharge, not current draw). This is why every wearable, fitness tracker, and medical device uses BLE.
Wireless Technology Comparison

Click each technology to highlight it. Compare range vs power consumption vs bandwidth.

You're designing a soil moisture sensor deployed in a 10-acre farm, reporting once per hour. No WiFi infrastructure available. Battery must last 2+ years. Which wireless technology?

Chapter 6: PCB Design (Showcase)

The PCB (Printed Circuit Board) is where your schematic becomes a physical object. Every trace is a wire, every via is a tunnel between layers, and every millimeter of copper matters. A bad PCB layout can introduce noise that corrupts your sensor data, create antenna dead zones, or even cause your board to catch fire if trace widths are too narrow for the current.

Layer Stackup

Most embedded boards use either 2 or 4 layers:

LayersCostUse WhenExample
2-layer$5-15 (qty 5)Simple MCU + sensors, low speed, tight budgetArduino shields, breakout boards
4-layer$15-40 (qty 5)BLE/WiFi (need ground plane), >50MHz signals, USBOur wearable, any wireless device
6+ layer$50+ (qty 5)DDR memory, HDMI, GbE, dense BGA routingSingle-board computers (RPi-class)

A 4-layer board typically stacks as: Signal → Ground → Power → Signal. The internal ground plane provides a low-impedance return path for high-speed signals (critical for BLE antenna performance) and shields top signals from bottom signals.

Trace Width: The IPC-2221 Formula

How wide must a trace be to carry a given current without overheating? The IPC-2221 standard gives us:

I = k · ΔT0.44 · (w · t)0.725

Where: I = current (Amps), k = 0.048 for outer layers (0.024 for inner), ΔT = temperature rise (°C), w = trace width (mils), t = copper thickness (oz, typically 1oz = 1.4 mils).

Rearranging to solve for width:

w = (I / (k · ΔT0.44))1/0.725 / t
python
# IPC-2221 trace width calculator
def trace_width_mils(current_A, temp_rise_C=10, copper_oz=1, internal=False):
    """Calculate minimum trace width in mils (thousandths of an inch)."""
    k = 0.024 if internal else 0.048
    t = copper_oz * 1.378  # mils thickness
    # Area in mil² = (I / (k * dT^0.44))^(1/0.725)
    area = (current_A / (k * temp_rise_C**0.44))**(1/0.725)
    width = area / t
    return width

# Examples:
print(f"100mA signal trace: {trace_width_mils(0.1):.1f} mils")   # 2.2 mils
print(f"500mA power trace: {trace_width_mils(0.5):.1f} mils")   # 10.3 mils
print(f"1A power trace:    {trace_width_mils(1.0):.1f} mils")   # 19.0 mils
print(f"3A motor trace:    {trace_width_mils(3.0):.1f} mils")   # 50+ mils

Component Placement Rules

Placement follows a strict priority order. Getting this wrong means noisy measurements, poor wireless performance, or thermal issues:

1. Antenna
Place at board edge. Keep-out zone: no copper/components within 3mm.
2. Crystal/Oscillator
As close to MCU as possible (<5mm). Short, equal-length traces.
3. Decoupling Caps
100nF on EVERY VCC pin, placed <2mm from pin. Via directly to ground plane.
4. Power Regulator
Near board edge for thermal dissipation. Bulk cap close to output.
5. High-Speed Signals
Short traces, away from analog. Impedance-matched if >50MHz.
6. Everything Else
Connectors at edges, LEDs visible, test points accessible.
The #1 PCB mistake beginners make: Forgetting decoupling capacitors. Every digital IC switches millions of times per second, causing tiny current spikes. Without a 100nF cap within 2mm of each VCC pin, those spikes propagate through the power rail and corrupt sensitive analog measurements. One cap per pin, no exceptions.

Design Rule Check (DRC)

RuleTypical Value (4-layer)Why
Trace/space6/6 mil (0.15mm)Manufacturing minimum for standard PCB fabs
Via drill0.3mm hole, 0.6mm padAnnular ring = (pad - drill)/2 = 0.15mm minimum
Board edge clearance0.3mmRouter bit tolerance during panel depanelization
Solder mask expansion0.05mmPrevents solder bridging between adjacent pads
Antenna keep-out3mm all sidesGround plane and components detune the antenna
Interactive PCB Layout

Click and drag components to place them. Red highlights show DRC violations. Green means correctly placed. Try to place all components following the rules above.

Our wearable PCB: 25mm × 25mm, 4-layer, 6/6 mil rules. nRF52840 QFN (7×7mm) center. Chip antenna (2.4GHz) at top edge with keep-out. MAX30102 (PPG sensor) at bottom (goes against skin). Crystal 32MHz within 3mm of nRF. Seven 100nF decoupling caps. Total: ~40 components on a postage-stamp-sized board.
Why must the BLE chip antenna be placed at the board edge with no ground plane underneath?

Chapter 7: COTS Integration

Not everything needs to be designed from scratch. COTS (Commercial Off-The-Shelf) modules let you buy pre-certified, pre-tested subsystems and drop them onto your board. The key question is always: should I build this circuit myself, or buy a module?

The Buy vs Build Decision

The golden rule: Buy anything that involves RF (antenna design, matching networks, regulatory certification). Build anything where you need to optimize for YOUR specific power/cost/size constraint. RF design is a black art — a $3 ESP32 module has years of antenna engineering baked in. You cannot beat that for $3.
SubsystemBuy (Module)Build (Discrete)Verdict
WiFi/BLE radioESP32 module ($2-4)Custom antenna + balun + matchingBUY — certification alone costs $10k+
GPS/GNSSu-blox NEO-6M ($10)Custom RF front-end + baseband ICBUY — GPS sensitivity requires expert RF
Power managementGeneric PMIC moduleCustom LDO/buck for your batteryBUILD — tailored efficiency saves battery life
IMU/SensorsBreakout board ($5-15)Sensor IC + decoupling ($1-3)BUILD for production, BUY for prototyping
LoRa radioRFM95W module ($6)SX1276 + antenna matchingBUY — Sub-GHz matching is painful
USB-C connectorN/A (always discrete)Connector + ESD diodes + CC resistorsBUILD — trivial circuit, modules don't exist

Popular COTS Modules

ESP32-WROOM-32 ($2.80 @ 1k qty): WiFi + BLE, 4MB Flash, 520KB SRAM, FCC/CE certified. 18mm × 25mm. Includes PCB antenna, crystal, and Flash — just add power and decoupling. The workhorse of IoT.

nRF52840 module (Raytac MDBT50Q) ($4.50 @ 1k): BLE 5 + Thread/Zigbee, 1MB Flash, 256KB SRAM, chip antenna, FCC/CE/IC certified. 10mm × 15mm. Our wearable could use this instead of bare nRF52840 IC to skip antenna design entirely.

u-blox NEO-M9N ($15): Multi-band GNSS (GPS + Galileo + GLONASS). 12mm × 16mm. UART/I²C/SPI interface. 1-meter accuracy. Just connect VCC, GND, TX, RX, and an external antenna.

Integration Checklist

text
Module Integration Checklist:Voltage compatibility
  Module VCC range vs your power rail?
  I/O voltage levels match MCU (3.3V vs 1.8V)?

□ Footprint
  Do you have the KiCad/Altium symbol + footprint?
  Castellated pads or pin headers? Reflow or hand-solder?

□ Antenna keep-out
  Does the module have a PCB antenna? (Most WiFi/BLE do)
  Keep-out zone marked in datasheet — NO copper, no components

□ Power sequencing
  Does it need to power up before/after other chips?
  Does it have an enable pin for power gating?

□ Thermal
  WiFi modules can draw 300mA+ peak. Copper pour for heat sinking?
  Adequate power trace width to module VCC pin?

□ Firmware support
  Does the module need its own firmware (ESP32: yes, GPS: built-in)?
  AT commands? Binary protocol? SDK available?
Cost comparison for our wearable: Using the bare nRF52840 IC + chip antenna + crystal + 5 passives = ~$4.50 BOM but requires antenna matching expertise. Using the MDBT50Q module = ~$5.50 but zero RF design, pre-certified, guaranteed range. For a first product, the $1 premium for a module is a no-brainer. For 100k+ units, switch to bare IC to save $100k.

Antenna Keep-Out Zones

Every wireless module datasheet specifies a keep-out zone around its antenna. This is NOT optional. Violating it causes:

Typical keep-out: 3-5mm on all sides of the antenna area, extending to the board edge. No copper fill, no traces, no components, no solder mask (sometimes).

Module Comparison

Visual comparison of popular COTS modules by size, cost, and capability.

You're building a prototype garden sensor with LoRa. You've never designed an RF matching network. What should you do?

Chapter 8: Firmware & Testing

Your PCB arrives from the fab house. Beautiful purple solder mask, perfectly aligned silkscreen. You plug in USB power. Nothing happens. No LED, no UART output, no BLE advertisement. Now what? Welcome to board bring-up — the most frustrating and most rewarding phase of hardware development.

The Bring-Up Sequence

Never try to run your full application on a fresh board. Bring up subsystems one at a time, in this exact order:

1. Power Rails
Verify 3.3V, 1.8V (if any) with multimeter BEFORE powering MCU.
2. Clock
MCU boots on internal RC. Verify external crystal starts (scope on XTAL pins).
3. Debug Port
Connect SWD/JTAG. Can you halt the CPU? Read device ID?
4. GPIO Toggle
Blink an LED. Proves clock, power, and basic firmware are running.
5. UART Printf
Get serial output working. This is your primary debug channel.
6. Peripherals
I²C scan (find sensors), SPI loopback, ADC read known voltage.
7. Sensor Data
Read real values. Compare against known reference.
8. Wireless
BLE advertise. Can phone see it? Check RSSI.
9. Full Application
All systems go. Measure power. Run overnight.

Debugging Tools

ToolCostWhat It ShowsWhen To Use
Multimeter$20Voltage, resistance, continuityFirst: verify power rails exist
SWD Debugger$20-60CPU registers, memory, breakpointsFirmware crashes, hard faults
Logic Analyzer$10-150Digital signals, protocol decode (I²C, SPI, UART)Bus communication problems
Oscilloscope$300+Analog waveforms, rise times, noiseSignal integrity, power rail noise
Current meter$50-200μA-level current measurementBattery life validation

Common Failure Modes

text
╔═══════════════════════════════════════════════════════════════╗
║  SYMPTOM              │  LIKELY CAUSE           │  FIX       ║
╠═══════════════════════════════════════════════════════════════╣
║  Nothing happens      │  No power / wrong       │  Check VCC ║
║                       │  polarity               │  with DMM  ║
║───────────────────────┼─────────────────────────┼────────────║
║  Debugger won't       │  SWD pins swapped /     │  Check     ║
║  connect              │  no power / reset held  │  wiring    ║
║───────────────────────┼─────────────────────────┼────────────║
║  I²C returns NACK     │  Wrong address / no     │  I²C scan  ║
║                       │  pull-ups / wrong VCC   │  + scope   ║
║───────────────────────┼─────────────────────────┼────────────║
║  UART garbage chars   │  Wrong baud rate /      │  Verify    ║
║                       │  TX/RX swapped          │  with LA   ║
║───────────────────────┼─────────────────────────┼────────────║
║  BLE not advertising  │  Crystal not starting / │  Scope on  ║
║                       │  wrong load caps        │  XTAL pins ║
║───────────────────────┼─────────────────────────┼────────────║
║  ADC reads 0xFFF      │  Floating input pin /   │  Add pull  ║
║  constantly           │  broken trace           │  -down res ║
╚═══════════════════════════════════════════════════════════════╝

Testing Levels

Unit testing firmware means mocking hardware. You can't run a sensor driver test on your PC without faking the I²C bus. Frameworks like CppUTest or Unity let you write tests that verify logic without real hardware.

c
// Unit test: verify SpO2 calculation from PPG data
#include "unity.h"
#include "spo2_algo.h"

void test_spo2_normal_ratio(void) {
    // R = (AC_red/DC_red) / (AC_ir/DC_ir)
    // R = 0.4 → SpO2 ≈ 100% (healthy)
    float spo2 = calculate_spo2(0.4);
    TEST_ASSERT_FLOAT_WITHIN(2.0, 98.0, spo2);
}

void test_spo2_low_ratio(void) {
    // R = 1.0 → SpO2 ≈ 85% (hypoxic)
    float spo2 = calculate_spo2(1.0);
    TEST_ASSERT_FLOAT_WITHIN(3.0, 85.0, spo2);
}

void test_spo2_invalid_ratio(void) {
    // R > 2.0 → invalid reading (sensor not on finger)
    float spo2 = calculate_spo2(2.5);
    TEST_ASSERT_FLOAT_IS_NEG_INF(spo2);  // Sentinel for error
}

Integration testing runs on real hardware. You connect the actual board, exercise each peripheral, and verify outputs against known-good references. Automated test jigs (pogo pins + Raspberry Pi) enable this at scale.

Environmental testing verifies the product works in the real world: temperature cycling (-20 to +60°C), humidity (85% RH), ESD strikes (8kV contact, 15kV air), and vibration. This is where cheap solder joints crack, marginal crystals stop oscillating, and poorly routed traces pick up interference.

Debugging Decision Tree

Click symptoms to trace through the diagnostic flow. Each path leads to the most likely root cause.

The #1 firmware testing lesson: Always have a "known good" board. When something doesn't work on your new revision, swap the firmware to the old board. If it works there, the problem is hardware. If it doesn't, the problem is firmware. This eliminates 50% of debugging guesswork instantly.
Your UART debug output shows garbled characters instead of readable text. What's the most likely cause?

Chapter 9: Mastery & Connections

You now have the complete mental model for embedded hardware design: from requirements decomposition, through component selection, to PCB layout and firmware validation. Let's consolidate with tools and challenges.

Quick Reference: Protocol Selection

Need >1 Mbps?
YES → SPI    NO → ↓
Multiple devices, few pins?
YES → I²C    NO → ↓
Talking to a modem/GPS?
YES → UART    NO → ↓
Digital microphone?
YES → PDM    NO → Re-evaluate

Quick Reference: Wireless Selection

Need internet access?
YES → WiFi    NO → ↓
Range > 100m?
YES → Sub-GHz (LoRa)    NO → ↓
Battery powered?
YES → BLE    NO → WiFi or USB

BOM Cost Estimator

Design Cost Calculator

Adjust component choices to see total BOM cost and power budget.

MCU Class M4 ($3)
Wireless BLE ($0)
Sensors (#) 2

Design Challenge: Soil Moisture Sensor Node

Challenge: Design the complete hardware for a solar-powered soil moisture sensor. Requirements: capacitive soil moisture sensor, temperature/humidity (SHT30), LoRa transmission every 15 minutes to a gateway 2km away, solar panel (5V, 100mA) + LiPo battery (500mAh), survive -10 to +60°C outdoors, IP65 enclosure, $15 BOM target, PCB size 40mm × 30mm.

Here's one valid architecture:

SubsystemComponentInterfaceCost
MCUSTM32L031 (Cortex-M0+, 32KB Flash)$0.90
LoRa RadioRFM95W module (SX1276)SPI$5.50
Temp/HumiditySHT30-DISI²C$2.20
Soil MoistureCapacitive probe (custom PCB)ADC (555 timer freq)$0.50
Solar ChargerBQ25504 (energy harvester)Analog$2.50
BatteryLiPo 500mAh (external)Power$2.00
Passives + PCBCaps, resistors, antenna, PCB fab$1.40
Total BOM$15.00
python
# Power budget for soil sensor node
battery_mAh = 500
solar_charge_mA = 50      # Average (accounting for clouds, night)
sun_hours_per_day = 5     # Conservative
daily_solar_mAh = solar_charge_mA * sun_hours_per_day  # 250 mAh/day

# Power consumption per cycle (every 15 min = 96 cycles/day)
cycles_per_day = 96

# Per cycle: wake (5ms) + read sensors (50ms) + LoRa TX (100ms) + sleep
wake_mA = 5     # MCU active
sensor_mA = 2   # SHT30 measurement
lora_tx_mA = 120 # RFM95W at +20dBm
sleep_uA = 2    # STM32L0 stop mode + RTC

active_ms_per_cycle = 5 + 50 + 100  # 155ms
sleep_ms_per_cycle = 15 * 60 * 1000 - active_ms_per_cycle  # ~900s

avg_active_mA = (wake_mA*5 + sensor_mA*50 + lora_tx_mA*100) / active_ms_per_cycle
daily_active_mAh = avg_active_mA * (active_ms_per_cycle * cycles_per_day / 3600000)
daily_sleep_mAh = sleep_uA/1000 * (sleep_ms_per_cycle * cycles_per_day / 3600000)
daily_total_mAh = daily_active_mAh + daily_sleep_mAh

print(f"Daily consumption: {daily_total_mAh:.2f} mAh")
print(f"Daily solar input: {daily_solar_mAh:.0f} mAh")
print(f"Net daily budget: +{daily_solar_mAh - daily_total_mAh:.1f} mAh")
# Result: solar easily exceeds consumption → runs indefinitely!

What We Covered vs What's Next

This LessonNext Steps
Communication protocols (SPI, I²C, UART, PDM)DMA transfers, interrupt-driven vs polled I/O
MCU selection basicsRTOS (FreeRTOS, Zephyr), real-time constraints
Memory types and sizingMemory protection units (MPU), cache optimization
Sensor signal conditioningKalman filtering, sensor fusion, calibration
Wireless overviewBLE GATT services, LoRaWAN network architecture
PCB design rulesEMC compliance, impedance control, flex PCBs
Firmware bring-upCI/CD for embedded, HIL testing, OTA updates
"Hardware is hard — but that's what makes it rewarding." Every working prototype represents hundreds of micro-decisions, each backed by physics and engineering tradeoffs. The goal isn't to memorize every formula — it's to build the mental model that tells you WHERE to look and WHAT questions to ask. The datasheets have the answers; you just need to know the questions.
For the soil moisture sensor challenge: why choose the STM32L031 (Cortex-M0+) instead of a more powerful nRF52840 (Cortex-M4)?