Embedded Systems Design

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.

Microcontroller Selection

CategoryExamplesFlash/RAMPerformanceUse when
8-bit AVRATmega328P, ATtiny852–256KB / 0.5–32KB16 MHz, 16 MIPSSimple I/O control, Arduino ecosystem, low cost
8-bit PICPIC12F, PIC16F, PIC18F1–128KB / 128B–4KB1–64 MHzIndustrial, automotive, Microchip-centric supply chains
ARM Cortex-M0/M0+STM32F0, SAMD21, RP204016KB–1MB / 4–264KB48–133 MHzMid-complexity, low power, DMA-capable peripherals
ARM Cortex-M4/M7STM32F4, STM32H7, SAME70Up to 2MB / up to 1MB168–600 MHz, FPUDSP, motor control, USB, Ethernet, real-time control
ARM Cortex-M33STM32L5, nRF9160Various64–64 MHzSecurity features (TrustZone), low-power IoT
Application processorRaspberry Pi, BeagleBone, iMX6512MB–4GB RAM1–2+ GHzLinux OS required, networking, complex UI, camera

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.

Communication Interfaces

InterfaceWiresSpeedMulti-deviceHalf/Full duplexBest for
UART2 (TX, RX)Up to ~12 MbpsNo (point to point)FullDebug, GPS, BT modules, PC communication
SPI4 (MOSI, MISO, SCK, CS per device)Up to 100+ MbpsYes (one CS per device)FullDisplays, SD cards, ADCs/DACs, SPI Flash
I2C2 (SDA, SCL)100k–3.4 MbpsYes (127 devices, 7-bit addr)HalfSensors, EEPROMs, RTC, BME280, MPU-6050
1-Wire1 (+ GND)~15 kbpsYes (ROM address per device)HalfDS18B20 thermometer, iButton authentication
CAN2 (CAN_H, CAN_L differential)Up to 1 Mbps (CAN FD: 8 Mbps)Yes (multi-master)HalfAutomotive, industrial — high noise immunity
USB4 (D+, D−, VBUS, GND)1.5–480 MbpsYes (host-controlled)HalfPC connectivity, HID, CDC (virtual COM port)
EthernetTwisted pair + magnetics10/100/1000 MbpsYes (IP addressing)FullInternet connectivity, industrial, Modbus/TCP

I2C Pull-up Resistors

I2C Pull-up Resistors Wiring diagram of an I2C bus showing the MCU and a Sensor connected to SDA and SCL lines, with pull-up resistors to VCC. VCC (3.3V or 5V) 4.7kΩ 4.7kΩ SDA (Data) SCL (Clock) MCU (Master) Sensor (Slave)
Both SDA and SCL lines require pull-up resistors to VCC, as I2C devices only pull the lines low (open-drain).
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

Real-Time Considerations

ApproachWhen to useProsCons
Bare metal (super-loop + ISR)Simple, single-function systems; low resource costMinimal overhead, deterministic, easy to understandComplex tasks with multiple timings become difficult to manage
Cooperative multitaskingMultiple tasks, all well-behaved (yield voluntarily)Low overhead, no preemption surprisesOne task hanging can block everything
RTOS (FreeRTOS, Zephyr)Multiple tasks with priority and timing requirementsTask isolation, priority preemption, timing guaranteesContext switch overhead, complexity, stack usage per task

FreeRTOS Basics

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

Power Management for Battery Devices

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
StrategySavingsNotes
Deep sleep / hibernateHigh (µA vs mA)Wake on GPIO interrupt or RTC alarm. Shortest wake time for longest battery life.
Disable peripheral clocksModerateUnused UART, SPI, I2C blocks burn clock power even idle
Lower clock frequencyModerateDynamic power ∝ f × C × V². Minimum clock for required throughput.
Reduce supply voltageHighDynamic power ∝ V². 1.8V vs 3.3V saves (1.8/3.3)² ≈ 30% of dynamic power
Power gate peripheralsHighEnable pin or MOSFET switch for sensors, radios, displays — off between uses
Use DMA for I/OModerateCPU sleeps while DMA transfers data — reduces active time

Debugging Tools

ToolWhat it doesCostNotes
SWD / JTAG debuggerProgram, breakpoints, register/memory inspection$10–$500ST-Link V2 (~$3 clone) works for STM32. CMSIS-DAP for universal ARM.
Logic analyserCapture and decode SPI, I2C, UART, CAN, etc.$10–$400Cheap Cypress FX2 clones work with PulseView (open source). Saleae for professional use.
OscilloscopeAnalog waveforms, timing, noise, power rails$100–$50,000Rigol DS1054Z (~$350) is excellent entry-level. 4 channels, 50MHz.
Serial debug (printf via UART)Print state, variable values, timestamps to terminalFreeUSB-UART bridge (CH340, CP2102) + terminal (PuTTY, screen, minicom)
ITM/SWO tracingprintf-style output via SWD debug port (no UART needed)Free (if using SWD)Available on Cortex-M3/M4/M7/M33. Supported in OpenOCD and J-Link.
LED indicatorsToggle pin to show code reaching a pointFreeThe oldest and most reliable debugging technique. Never underestimate it.

Common Embedded Bugs

Bug typeSymptomRoot cause and fix
Stack overflowRandom crashes, corrupted variables, hard faultTask or ISR stack too small. Increase stack, reduce local variable size, use heap for large buffers.
Race conditionData corruption, unpredictable behaviourShared variable modified in ISR and main. Use volatile, critical section, or atomic access.
Interrupt priority inversionHigh-priority task blocked by low-priorityUse priority inheritance mutexes (FreeRTOS mutexes have this); avoid blocking ISRs.
Watchdog timeoutSystem resets periodicallyPet the watchdog more frequently, or identify what's blocking execution.
I2C hang (bus stuck)MCU hangs in I2C transfer, no timeoutPower cycle the I2C bus (or clock it manually), add timeout to all blocking I2C calls.
Floating GPIOIntermittent spurious interrupts or readingsEvery GPIO input must have a defined state — pull-up or pull-down to VCC or GND.

References