Embedded Systems & IoT
Embedded software runs on hardware that has no operating system to hide behind, or a very small one. A typical microcontroller gives you tens of kilobytes of RAM, no memory protection, no heap you'd want to use, a watchdog that reboots you if you stall, and a power budget measured in microamps. Bugs that a server would survive — a leak, a blocked thread, an unhandled edge case — become a field failure on a device someone has to physically visit.
IoT adds a network to that picture, and with it the full set of distributed systems problems under much worse conditions: intermittent connectivity, constrained bandwidth, no user to click "retry," and a device population that may include five years of firmware versions running simultaneously. The two disciplines that matter most as a result are OTA update design and device security — everything else is recoverable, and those two are not.
TL;DR
- Embedded code targets constrained hardware: little RAM, no MMU, hard deadlines, and strict power budgets. See Microcontrollers.
- C is still the lingua franca; embedded Rust brings memory safety without a runtime.
- An RTOS buys you tasks, priorities, and deterministic scheduling — a superloop is often enough until it isn't.
- MQTT is the default IoT messaging protocol: pub/sub, tiny frames, QoS levels, and last-will messages.
- OTA updates with A/B partitions and rollback are non-negotiable for any fleet you can't physically touch.
- IoT security starts at the hardware: secure boot, unique per-device keys, and signed firmware.
- Avoid dynamic allocation in long-running firmware; fragmentation eventually wins.
The Constraint Ladder
Featured Topics
Hardware & Foundations
- Microcontrollers — MCUs vs MPUs, peripherals, GPIO, interrupts, and datasheets
- Raspberry Pi & Embedded Linux — When you want a real OS on the device
- ESP32 — The connected-MCU workhorse: Wi-Fi, BLE, and ESP-IDF
Firmware Programming
- Embedded C — Volatile, registers, fixed-width types, ISRs, and static allocation
- Embedded Rust —
no_std, HALs, and memory safety on bare metal - Real-Time Operating Systems — Tasks, priorities, preemption, and priority inversion
Connectivity & Fleet Operations
- MQTT — Pub/sub messaging for constrained devices
- OTA Firmware Updates — A/B partitions, signing, staged rollout, and rollback
- IoT Security — Secure boot, provisioning, key storage, and the attack surface of a shipped device
Common Questions
Do I need to learn C, or can I use something else?
Learn C. It's what the vendor SDKs, HALs, datasheets, and the overwhelming majority of existing firmware are written in, and you'll be reading it regardless of what you write. Embedded Rust is a genuinely good production choice now — no_std works, HALs exist for common families, and the borrow checker eliminates an entire class of field bugs — but it sits on top of an ecosystem that assumes C literacy. MicroPython and CircuitPython are excellent for prototyping and education; they're rarely the answer for power-constrained or hard real-time products.
When do I need an RTOS?
When you have several activities with genuinely different timing requirements and a superloop starts requiring careful manual interleaving. A single-purpose device that samples a sensor and blinks an LED does not need one. A device that services a radio, runs a control loop at a fixed rate, and handles a UI needs either an RTOS or a hand-rolled scheduler that will slowly become a worse RTOS. Note that "real-time" means deterministic, not fast — a guaranteed 10 ms response beats an average 1 ms with occasional 50 ms outliers.
How is IoT different from ordinary backend development?
The device side inverts most assumptions. Bandwidth is expensive, so protocols are binary and terse. Connectivity is intermittent, so devices must buffer and resume rather than fail. There's no operator to intervene, so every failure path must self-heal. Fleets are heterogeneous, so the cloud side must support many firmware versions at once — meaning message schemas need versioning discipline from the first release. And the device is physically in an attacker's hands, so anything embedded in the firmware is effectively public. See IoT Security.
What's the most common serious mistake?
Shipping without a working, tested OTA update path. Every other design flaw is fixable if you can push new firmware; without that, a bug means a recall. Close behind: using malloc in long-running firmware (fragmentation causes failures weeks into uptime), doing real work inside interrupt handlers, and putting the same key or credential on every device in the fleet.
How do I debug something with no console?
With hardware. A debug probe (SWD/JTAG) giving breakpoints and memory inspection is the baseline tool, and a logic analyzer or oscilloscope is how you see what's actually on the wire when the bus "should" be working. Beyond that: a serial log with a ring buffer, blinking-LED error codes for the field, and — for the failures you can't reproduce — a persistent crash log written to flash on fault, uploaded on next boot.
Learning Path
Beginner
Get an ESP32 or STM32 board. Blink an LED, read a sensor over I²C, and handle an interrupt in C. Learn to read a datasheet.
Intermediate
Add an RTOS, publish sensor data over MQTT, manage power modes, and use a debug probe properly.
Advanced
Secure boot and provisioning, OTA with rollback, timing analysis, embedded Rust, and fleet observability.
Related Hubs
- Programming Languages — C, C++, Rust, and systems programming
- Networking — TCP/IP and protocol fundamentals under MQTT
- Security — Encryption and secrets management on constrained devices
- Cloud Computing — The fleet's backend and edge computing