Embedded C: Firmware from Scratch
Write the C that runs on microcontrollers. Manipulate registers bit by bit, drive simulated GPIO, timers, ADCs and serial ports, handle interrupts and state machines, and build a complete smart-thermostat firmware. Every peripheral is modeled in plain C so it runs and grades on the server, no hardware required.
10 projects, 250 hands-on levels, run in your browser.
Syllabus
- Bits & Registers: Embedded programming is bit manipulation. A microcontroller's peripherals are controlled by registers, and you configure them by setting, clearing, and testing individual bits. Master the bitwise operators, masks, bit fields, and fixed-width integer types that every later peripheral depends on, then wrap them into a clean control-register abstraction.
- GPIO & Digital I/O: General-purpose I/O pins are how a microcontroller touches the world: lighting an LED, reading a button. We model the three classic registers, DDR (direction), PORT (output), and PIN (input), as bytes, and build the driver layer that reads buttons and drives LEDs. Buttons are active-low, the real-world convention.
- Timers, PWM & Delays: A microcontroller's sense of time comes from hardware counters that tick and overflow. Use them to measure elapsed time (handling wraparound), convert between ticks and real units, generate PWM to dim an LED or position a servo, and build software timers. All integer math, the way embedded code avoids slow floating point.
- ADC & Sensors: An analog-to-digital converter turns a voltage into a number. Convert raw ADC counts to millivolts and to physical units, do fractional math with fixed-point integers (no floating point on a small MCU), and clean up noisy readings with averaging and filters. The thermostat's temperature input is built here.
- Serial Communication & Protocols: Microcontrollers talk to the world over serial links. Work out UART timing and framing, protect data with checksums and a CRC, build and parse packets, and bit-bang the SPI and I2C buses by hand. The thermostat reports its readings with the packet protocol built here.
- Interrupts & Scheduling: Interrupts let hardware events grab the CPU's attention. An interrupt service routine sets a flag; the main loop reads it. Build the flag handling, edge detection, time-based debouncing, and a cooperative scheduler that runs tasks at their own rates, the superloop that will run the thermostat.
- State Machines: Embedded behavior is naturally a finite state machine: a set of states and the events that move between them. Build transition functions, drive a traffic light, drive transitions from a table, decode button-press patterns, and finish with the thermostat's mode controller.
- Constrained Memory: A microcontroller might have a few kilobytes of RAM and no heap. You manage memory by hand: ring buffers for streaming data, fixed pools instead of malloc, bit arrays to pack flags, and explicit byte packing for storage and transmission. The thermostat's reading log is a ring buffer built here.
- Embedded Control: Closing the loop: read a measurement, compare it to a setpoint, and drive an actuator to close the gap. Build on-off control with hysteresis, proportional control, and a full integer PID controller with anti-windup, all in fixed/integer math. This is the brain of the thermostat.
- Capstone: Smart Thermostat Firmware: The finale. Bring every project together into one firmware for a smart thermostat: a Thermostat struct holds the state, the sensing path turns ADC counts into a filtered temperature, the control path runs the PID, the output path drives the heater and reports over serial, and a superloop ties it all into a single firmware step, the complete embedded system, built from scratch in C.
Key concepts
- Active-low: A signal where the asserted state is a logic 0, not 1. A button with a pull-up reads high when idle and low when pressed, so the logical state is the inverse o…
- ADC: Analog-to-Digital Converter: turns a voltage into an integer count (0 to 1023 for a 10-bit ADC). Convert counts to millivolts and to physical units, then filte…
- Bit masking: Operating on individual bits of a register: set with | , clear with & ~ , flip with ^ , test with >> and & 1 . The bedrock skill of embedded C.
- Debouncing: Filtering the electrical bounce of a mechanical switch so one press registers once. A shift register of recent samples confirms a stable level (all 1s or all 0…
- Fixed pool: Pre-allocated static memory handed out by a bump index, replacing malloc on systems with no heap. Allocation returns the next free slot; the whole pool is free…
- Fixed-point: Representing fractional numbers as integers scaled by a power of two (e.g. Q8.8 multiplies by 256), because small microcontrollers have no floating-point unit.…
- GPIO: General-Purpose Input/Output: a pin the firmware can drive high/low (output) or read (input). Modeled with the DDR (direction), PORT (output), and PIN (input)…
- Interrupt (ISR): A hardware event that pauses the main program to run an Interrupt Service Routine. An ISR does the minimum, usually setting a volatile flag, then returns; the…
- PID controller: A feedback controller combining Proportional (current error), Integral (accumulated error), and Derivative (rate of change) terms. Built here in integers with…
- PWM: Pulse-Width Modulation: switching a pin on and off rapidly, varying the fraction of time it is high (the duty cycle) to fake an analog level. Dims LEDs, sets m…
- Register: A small fixed memory location inside the microcontroller that controls or reports a peripheral. You configure hardware by setting and clearing individual bits…
- Ring buffer: A fixed array with head and tail indices that wrap around, used to buffer streaming data without dynamic allocation. The standard structure for UART bytes and…
- Superloop: The while(1) main loop of bare-metal firmware. A cooperative scheduler inside it runs each task when its period has elapsed, no operating system, no preemption.