Skip to content

Embedded Systems: Tiny Computers With Deadlines

The computers that run the world are mostly invisible: a few kilobytes of memory, a few milliamps of power, one job, and a deadline. Embedded engineering is the craft of making them read the real world, act on it in time, and last for years on a coin.

14 min readReadInteractive

Count the computers in a car. Not the screen: all of them. One reads the throttle pedal, one meters fuel into the engine, one decides when an airbag fires, one runs a door lock. Counts depend on what you call a computer. One industry whitepaper (TE Connectivity) puts a modern car at 70 to 100 electronic control units, each with its own processor, and one analyst quoted on Wikipedia says up to 150 in a luxury model.

None of these is a general-purpose computer. Each does one job, forever, with a deadline: an airbag controller that thinks for a moment too long has failed. A washing machine, a thermostat or a remote hides the same thing: a small chip reading a button or sensor and driving a motor, a light or a radio.

The course in one box

Who takes it: electrical and computer engineering students, usually in the third or fourth year, often as a lab course with a development board.

What it's for: building a small computer that senses, decides and acts within a fixed time and power budget.

Comes after: Digital Logic Design (34/36), Computer Architecture (34/25) and Programming Fundamentals (34/09).

Leads to: Systems Engineering & the Capstone (34/22), where it is one subsystem among many, and it sits next to Control Systems (34/20), whose feedback loops usually run on exactly these chips.

Honest line: you won't be able to bring up a new board after this piece. You will see why a button must be filtered, what a sensor reading really resolves, how a motor is dimmed, and how long a coin cell lasts. Easier first reads: Digital Logic Design (34/36) for the counters and state machines inside every peripheral, Processors: How a CPU Thinks (29/11), and Batteries (29/08).

A computer on one chip, with pins that touch the world

A microcontroller (MCU) puts the processor, the program memory (flash), the working memory (RAM) and the peripherals (timers, converters, serial ports) on one chip. Power it and it runs; no operating system, no external memory. The chip on the classic Arduino Uno, the ATmega328P, has a 16 MHz clock, 32 KB of flash, 2 KB of RAM and 1 KB of EEPROM. A phone's processor runs billions of ticks a second with gigabytes of memory beside it. The gap buys a chip that is cheap, sips power, and has nothing between your code and the pin.

MicrocontrollerApplication processor
ExampleATmega328P (Arduino Uno)Phone or laptop chip
Clock16 MHzBillions of ticks per second
Memory2 KB RAM, 32 KB flash on the chipGigabytes, off the chip
SoftwareYour program, alone ("bare metal")An operating system and apps
Built inTimers, ADC, UART, I2C, SPIMostly outside the chip
PowerMilliwatts, microwatts asleepWatts

The door to the world is the GPIO pin (general-purpose input/output): a pin tied to one bit in a register. Write a 1 and the pin goes high, perhaps lighting an LED; read it and you learn whether it is high or low. To read a button, wire it between the pin and ground and turn on a small pull-up resistor inside the chip: the pin idles at 1, and a press pulls it to 0 ("active low").

Then the real world bites. The metal contacts of a button spring against each other, so one press makes the line flicker between 0 and 1 several times before it settles. Jack Ganssle's well-known test of many switches, as summarised by EE Journal, found an average bounce of about 1.6 ms and a longest of about 6.2 ms among the ordinary ones, with one switch bouncing for 157 ms. To a chip that reads a pin every microsecond, a single press looks like a burst of presses. The fix is debouncing: accept a change only after the pin has held its new value for longer than the worst bounce.

Interactive · One press, many edges
Worst-case bounce of this switch
Debounce window: line must hold this long

Red = the raw pin, blue = the debounced signal (checked every 1 ms). First 40 ms drawn; the button is then held. Each press uses a new random bounce pattern.

With a 6 ms bounce and a 10 ms window, every press counts exactly once, while the raw pin shows three to five falling edges. Now drag the window down to 1 or 2 ms: the filter is shorter than the gaps between bounces, so it accepts some of them as real presses, and the count is often wrong again. The rule: the window must be longer than the longest bounce gap and shorter than anything a human can do (tens of milliseconds is common).

ADC and DAC: turning voltage into numbers and back

The world is analog and the chip is digital, so an analog-to-digital converter (ADC) measures a voltage and reports a whole number. An N-bit ADC slices the range from 0 V up to its reference voltage into 2N equal steps, and reports which step the input falls in. The rule graduates remember is the size of one step, the "least significant bit":

1 LSB = Vref ÷ 2N   ·   error ≤ ½ LSB   ·   ideal SNR ≈ 6.02N + 1.76 dB

Worked: a 10-bit ADC on a 3.3 V reference has 1,024 steps of 3.22 mV; a 12-bit one has 4,096 steps of 0.806 mV. A sensor that outputs 10 mV per degree is then resolved to about 0.32 °C by the 10-bit converter, and no software can recover detail below that. Each extra bit halves the step and adds about 6 dB in the ideal case; real converters add noise, so a "12-bit" chip often delivers fewer effective bits.

The converter also samples in time: it takes a reading, holds it, and moves on. Sample slower than twice the fastest wiggle and the shape is lost (the sampling theorem, which Signals & Systems, 34/37, develops). A DAC runs the same ladder backwards, turning a number into a voltage.

Interactive · Quantise a sine wave
Resolution
Samples per cycle

Dotted = true 0 to 3.3 V sine; orange = what the ADC reports, each sample held until the next. Two cycles shown.

At 1 bit the output is a square wave, because the converter only knows "above or below half". At 4 bits you see 16 stairs. At 10 bits the steps vanish into the line. The second slider shows the other cost: with 3 samples per cycle the wave is barely a triangle.

Timers and PWM: counting ticks to dim an LED

A timer is the counter from Digital Logic Design, fed by the chip's clock divided by a prescaler, which can raise a flag, trigger an interrupt, or flip a pin when it reaches a chosen value. Every wait and every precise pulse comes from a timer; the CPU does not count.

Most pins can only be fully on or fully off, so to dim an LED or slow a motor you switch very fast and vary the fraction of each period the pin is on. This is pulse-width modulation (PWM). A motor, or an eye, is too slow to follow the flicker and responds to the average:

Vavg = duty × Vcc   ·   fPWM = fclock ÷ (prescaler × 2bits)

Worked: a 16 MHz clock, prescaler 64, and an 8-bit timer (256 counts) gives 16,000,000 ÷ (64 × 256) = 976.6 Hz, the "about 980 Hz" Arduino documents for pins 5 and 6, which run on the same timer that keeps millis(). That timer overflows every 1.024 ms. The "about 490 Hz" pins count up and then down, so one cycle takes twice as long. The cost of more resolution is lower frequency: a 16-bit timer at the same settings gives only 3.8 Hz, which would visibly flicker.

Interactive · A timer-driven PWM pin

Clock

Prescaler

Timer width

Duty (set by the compare value)

With the defaults (16 MHz, 64, 8-bit), 25% duty is compare value 64 of 255 and averages 1.25 V from a 5 V pin: an LED at that setting looks dim, though the eye's response is not linear, so it looks brighter than a quarter. Switch to prescaler 1 and 8 bits and the same code runs at 62.5 kHz, too fast for some motor drivers to follow.

Interrupts versus polling: ask, or be told

A chip must also notice events. Polling means the main loop keeps asking "has the button been pressed?". It is simple, but the answer can only be as fresh as one trip around the loop, a short pulse between two looks is never seen, and the CPU can never sleep. An interrupt turns it round: the hardware stops whatever the code is doing, saves where it was, jumps to a short handler (the ISR), and returns afterwards. On an ARM Cortex-M3 this takes 12 clock cycles with zero-wait-state memory (ARM's documentation); at 48 MHz that is 0.25 µs.

Interactive · Polling loop against interrupt
One trip around the main loop takes
Each event lasts

Ten events in one second (green = polling saw it, red = missed). The loop looks at the pin once per trip. The interrupt line assumes a 48 MHz Cortex-M3 and ignores handler length.

At 60 ms per loop and 20 ms events, polling misses seven of the ten, and the three it catches arrive 6 to 18 ms late. The interrupt catches all ten in a quarter of a microsecond. Slide the loop to 1 ms and polling looks perfect, but only because it now does nothing else.

Serial buses: UART, I2C and SPI

Chips talk to sensors and to each other over a few wires, one bit at a time. Three buses cover most of it. UART (the "serial port") uses two wires and no shared clock: each side agrees on a speed (the baud rate) beforehand. The line idles high; a byte is sent as a start bit (0), eight data bits least-significant first, and a stop bit (1): ten bit-times for eight bits of data.

I2C uses two shared wires, a clock (SCL) and data (SDA), for many devices at once. Every device has an address; the controller (older texts say "master") sends the address, then data bytes, and the target acknowledges each byte with one extra bit, so a byte costs nine clocks. NXP's specification sets 100 kbit/s for standard mode and 400 kbit/s for fast mode. SPI uses four wires (clock, data out, data in, and one chip-select per device) and has no addresses or acknowledgements, which makes it simple and fast, often megabits per second.

Interactive · A UART frame, and how long a transfer takes
Bytes to move (e.g. reading a sensor)

I2C speed

SPI clock

Model: UART 10 bits per byte; I2C 9 clocks per byte including an address byte, ignoring start and stop; SPI 8 clocks per byte, no overhead. Real transfers add gaps and command bytes.

The letter "A" is 0x41, which is 01000001 in binary; sent least significant bit first it is 1 0 0 0 0 0 1 0, framed by a start 0 and a stop 1. At 9,600 baud one bit lasts 104 µs and one byte 1.04 ms. For six bytes from an accelerometer, a typical amount, that is 6.25 ms at 9,600 baud against 48 µs for SPI at 1 MHz.

Real time: deadlines, and the watchdog behind them

"Real-time" does not mean fast; it means guaranteed on time. In a hard real-time system (an airbag, a pacemaker, an engine controller), a late answer is a wrong answer. In a soft one (video playback), a late answer is merely worse. With several jobs sharing one core, a scheduler must decide who runs. A classic rule, proved by Liu and Layland in 1973, is rate-monotonic scheduling: give the highest priority to the task with the shortest period. Their test adds up each task's utilisation, its run time divided by its period:

U = Σ Ci ÷ Ti   ·   guaranteed if U ≤ n(21/n − 1)

For three tasks the bound is 0.78, and it falls toward 0.69 as tasks are added. The test is sufficient, not necessary: above the line the deadlines may still be met, and exact analysis is needed to know. The simulator below runs three tasks with periods of 10, 20 and 50 ms.

Interactive · Three tasks, one core
Task 1 (every 10 ms) needs
Task 2 (every 20 ms) needs
Task 3 (every 50 ms) needs

Fixed priorities, shortest period first; a higher task preempts a lower one. Red mark = a job still unfinished when its next one arrives. 100 ms is one full cycle for these periods.

Try "Above the bound, still fine": U is 0.79, over the 0.78 line, yet the simulation meets every deadline. Then "Under 100%, yet it fails": U is 0.94, and the slowest task still misses at 50 ms, because the fast tasks keep interrupting it. Utilisation under 1 is necessary, not sufficient.

When a deadline is missed or code hangs, the last defence is the watchdog: a hardware timer that resets the chip unless the software "kicks" it before it runs out. A healthy program kicks it as a matter of routine; a hung one cannot, and after one timeout the chip restarts. Mars Pathfinder (landed July 1997) showed both sides. Its computer began resetting itself on Mars. The cause, found by reproducing it in a lab, was priority inversion: a low-priority weather task held a lock the high-priority bus task needed, a medium-priority task kept the weather task from finishing, and the bus task missed its deadline. Glenn Reeves of JPL reports that the high-priority scheduling task noticed the missed deadline and forced the reset; other accounts say a watchdog did. Either way, the fix was a patch sent from Earth that turned on priority inheritance for the lock.

The coin-cell budget: living on microamps

Many embedded devices must run for years on a battery they will never see again. The discipline is a budget. A chip is usually in one of two states: active, drawing milliamps while it works, and asleep, drawing microamps while a timer waits. The average current is the weighted mix, and the battery's capacity divided by that average is its life:

Iavg = (Iactive · tactive + Isleep · tsleep) ÷ T   ·   life = capacity ÷ Iavg

Energizer's CR2032 sheet gives a typical 235 mAh, measured down to 2.0 V under a 15 kΩ load; capacity is lower under heavy pulses, in the cold, or after years on a shelf. Worked: a chip that draws 5 mA and never sleeps drains 235 mAh in 47 hours. The same chip waking for 20 ms every 10 s, and sleeping at 2 µA in between, averages about 12 µA: 235 ÷ 0.012 is roughly 19,600 hours, or 2.2 years. A coin cell is also a weak source: some makers' sheets recommend pulses of only 15 to 20 mA, so radio bursts often need a capacitor beside the cell.

Interactive · How long does the battery last?
Current while active
Current while asleep
Wakes per hour
Awake for, each time

The derated cell is an assumed two-thirds of the datasheet figure, for pulses, cold and age. Ignores the cell's own slow self-discharge and voltage droop.

With the defaults (5 mA, 2 µA, 360 wakes an hour, 20 ms) the life is the 2.2 years above. Now raise the awake time: at 200 ms the life drops to about three months (96 days), because the active part dominates the average. Lower the sleep current from 2 to 0.1 µA and life only improves from 2.2 to 2.7 years: sleep current matters once the chip sleeps nearly all the time, and not before. The art is to wake rarely, work fast, and go back to sleep.

What a graduate just sees

  • A microcontroller is a whole computer on a chip, chosen for cost, power and predictability, not speed.
  • A button is a bouncing wire: any signal from the physical world needs filtering before it is a fact.
  • An N-bit converter gives 2N steps of Vref ÷ 2N; resolution is a budget, and so is sample rate.
  • Switching fast and varying the on-time is how a pin that can only be on or off produces a middle value.
  • Polling asks and interrupts tell; short pulses and low power both favour interrupts.
  • Buses trade wires against speed: UART needs no clock, I2C shares two wires among many, SPI is fast and needs a select line per device.
  • Real-time means a guaranteed deadline, and total utilisation under 100% does not guarantee it.
  • Battery life is set by the average current, which is set by how much of the time the chip is asleep.

Where it shows up next

  • Control Systems (34/20): a digital controller is a timer interrupt that reads an ADC, computes, and writes a PWM output; the sample period you choose here sets how stable the loop can be.
  • Systems Engineering & the Capstone (34/22): timing and power budgets are allocated across subsystems exactly as above, and a missed budget is a system-level failure.
  • Operating Systems (34/26): a real-time operating system is a small scheduler plus interrupts, with the priority rules from the real-time section.
  • Signals & Systems (34/37) and Power Systems & Electric Machines (34/39): sampling theory limits the ADC, and PWM drives the inverters that run motors from the grid to the electric car.

If you want to go deeper

  • 📘 Textbook: Jonathan Valvano, Embedded Systems: Introduction to Arm Cortex-M Microcontrollers (Valvano, 5th ed.). The University of Texas course text: ports, timers, ADC, interrupts and serial buses on one real chip.
  • 📘 Reference: Joseph Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors (3rd ed., Newnes, 2013). How the interrupt system and core of the most common microcontroller family work.
  • 📘 Practical and gentler: Elecia White, Making Embedded Systems (2nd ed., O'Reilly, 2024). Design patterns for firmware, written for people who have programmed before.
  • 🌐 Free course and book: Valvano's free online e-book, "Embedded Systems - Shape The World", which accompanies the UT Austin course on edX (check whether the current edX run is open).
  • 🌐 Debouncing: Jack Ganssle, "A Guide to Debouncing", with oscilloscope traces of real switches.
The takeaway

An embedded system is a small computer whose problems are physical: noisy inputs, a deadline, and an energy budget. Filter what you read, choose your resolution and your timer frequency deliberately, let hardware tell you about events instead of asking, prove your deadlines instead of hoping, and spend your life asleep.

Try this: tap a button on a microwave or a remote twice as fast as you can. Notice how fast you can go before the second tap is ignored. That limit is a debounce window or a polling loop at work.

Sources & Further Reading

Sources. ECU counts: TE Connectivity, Electromagnetic Compatibility in Connected and Electrified Vehicles (whitepaper, 70 to 100); Wikipedia, "Electronic control unit" (up to 150, IHS Markit analyst); figures differ widely by definition. ATmega328P: Microchip/Atmel datasheet (32 KB flash, 2 KB SRAM, 1 KB EEPROM, 10-bit ADC); Arduino Uno clock and EEPROM: Arduino documentation. Switch bounce: Ganssle, "A Guide to Debouncing", as summarised by EE Journal ("Ultimate Guide to Switch Debounce", part 4). Ideal ADC SNR of 6.02N + 1.76 dB: standard result (Analog Devices tutorial MT-001). Arduino PWM frequencies (about 490 Hz and 980 Hz): Arduino analogWrite() reference. Cortex-M3 interrupt latency of 12 cycles with zero-wait-state memory: Arm Community, "Beginner guide on interrupt latency". I2C 100 and 400 kbit/s: NXP UM10204. Rate-monotonic bound: Liu and Layland, Journal of the ACM 20(1), 1973. Mars Pathfinder: Glenn Reeves' account (JPL) and Mike Jones' earlier message; the two differ on whether a watchdog or the scheduling task triggered the reset. CR2032 capacity and test load: Energizer CR2032 datasheet (235 mAh typical, 15 kΩ, to 2.0 V); pulse limits of 15 to 20 mA from other makers' sheets. All interactive numbers were computed in code.