Platforms and Toolchains
Choosing a microcontroller family and board, development tools, the build and flash cycle, and a first project that works.
What a microcontroller is
A microcontroller packs a processor core, flash memory for the program, SRAM for data, and peripherals (GPIO pins, timers, ADC, serial ports) on one chip. It starts running its program the moment power arrives, with no operating system, no screen and no file system unless you add one. Because everything is on the chip, it is cheap (cents to a few dollars), low-power and predictable in timing. Compared with a single-board computer such as a Raspberry Pi, which runs Linux on a fast processor and gigabytes of memory, a microcontroller is the right tool for hard real-time tasks (a motor loop that must run every 100 µs), for battery life, and for cost.
| Microcontroller | Single-board computer (Linux) | |
|---|---|---|
| Memory | KB to a few MB | Hundreds of MB to GB |
| Boot time | Milliseconds | Many seconds |
| Timing | Deterministic; microsecond control | Scheduling jitter of milliseconds |
| Power | Microamps asleep, milliamps awake | Watts |
| Typical job | Control loop, sensor node, motor driver | Network service, vision, database, user interface |
| Programming | C or C++ (also MicroPython, Rust); no OS or a small RTOS | Any language on Linux |
The main families and boards
| Platform | Typical specs | Strengths | Watch out for |
|---|---|---|---|
| Arduino Uno (ATmega328P) | 8-bit AVR, 16 MHz, 32 KB flash, 2 KB SRAM, 5 V | Huge community, simple, forgiving, 5 V logic | Little memory; no FPU; slow for heavy math |
| Arduino Nano / Mega, Teensy, Adafruit boards | AVR and ARM boards in many sizes | Easy libraries and shields; Teensy is very fast | Check voltage (5 V or 3.3 V) and pin counts |
| ESP32 family | 32-bit dual core, up to 240 MHz, Wi-Fi and Bluetooth, 3.3 V | Cheap wireless; lots of memory and peripherals; deep sleep | Higher power in use; 3.3 V only; Wi-Fi timing is not hard real-time |
| Raspberry Pi Pico (RP2040) and Pico 2 | Dual Cortex-M0+ (or M33), 133 MHz, 264 KB SRAM, 3.3 V | Low cost, good tools, programmable I/O (PIO) for custom protocols | Base board has no wireless; Pico W adds Wi-Fi |
| STM32 family | Cortex-M0 to M7, 8 MHz to 550 MHz, wide range | Professional, wide peripheral choice, low-power parts, strong tool support | Steeper learning curve; many variants |
| Nordic nRF52 / nRF53 | Cortex-M4 or M33 with Bluetooth Low Energy | Excellent BLE and very low power | Needs the vendor SDK (Zephyr or nRF Connect) |
| PIC, MSP430, other 8/16-bit MCUs | Small, cheap, low power | Simple, very low cost, long supply life | Less modern tooling |
| Single-board computer (Raspberry Pi) | Linux, multi-core, gigabytes of RAM | Network, cameras, databases, full OS | Not real-time; boots slowly; SD cards wear out |
Cores matter more than names. 8-bit parts (AVR, PIC) handle simple control and are limited in speed and memory; 32-bit ARM Cortex-M parts (M0+, M3, M4, M7, M33) dominate modern designs, with optional hardware floating-point (the “F” in M4F) and DSP instructions; RISC-V cores (newer ESP32 variants) are increasingly common. A 32-bit core does in one instruction what an 8-bit core needs several for: a 32 × 32 integer multiply takes about 1 cycle on a Cortex-M4 and many dozens of cycles on an AVR.
Choosing a chip for the job
Start from what the job needs, then check the tools and the supply. The key numbers are I/O count and types, speed, memory, power budget, wireless, cost and volume.
Worked example: sizing a data-logging sensor node
- The job. Four analog sensors read every second, averaged over 100 samples; a temperature and humidity chip on I²C; an SD card or radio for storage; a battery with six months of life.
- RAM. A buffer of 4 channels × 100 samples × 2 bytes = 800 B; plus about 256 B of stack and 400 B for libraries and variables: 1 456 B. An ATmega328P (2 048 B) would fit with little margin, so a part with 8 KB or more is safer.
- Flash. Application 18 KB plus libraries (radio, SD, file system) 24 KB with a 30 % margin for growth: about 55 KB — over the 32 KB of an Uno, so choose a part with at least 128 KB.
- Peripherals. Four ADC channels, one I²C, one SPI (SD card), one UART (debug), two timers. Almost any 32-bit part has these; check that the pins you need are not shared (see the MCU Pinout and Peripheral Multiplexer).
- Power. Six months on a battery needs a sleep current of a few microamps and a wake-up schedule (see Power and Low-Power Firmware): STM32L, nRF52, ESP32 (deep sleep) or RP2040 with care. A part with a good sleep mode beats a faster one.
- Decision: a low-power Cortex-M with 64 KB RAM and 256 KB flash and a good radio is easily enough; the choice then comes down to the toolchain and supply you like.
- Use a microcontroller for hard real-time work (motor control, fast sampling, timing-critical outputs). Use a single-board computer for networking, vision and storage, often with an MCU alongside it for the timing-critical I/O.
- Plan the voltage. 5 V and 3.3 V parts are both common: check every device on the board and level-shift where they meet (see Microcontroller Electronics).
- Think about the clock. A crystal of ±20 ppm gains or loses 1.7 s per day, about 10.5 minutes a year: fine for timing events, not for a clock that must stay right without a time source.
- Check the supply chain. Pick parts that are available in volume and likely to stay in production; a chip that is “not recommended for new designs” is a risk.
Speed is not the only measure
| Chip | Clock | Core | Floating point | What it means |
|---|---|---|---|---|
| ATmega328P | 16 MHz | 8-bit | none (software) | Roughly 1 simple instruction per clock, but 8-bit data: a 32-bit add takes 4 or more instructions |
| RP2040 | 133 MHz | 32-bit Cortex-M0+ | none (software) | Single-cycle 32-bit integer math; floats are done in a fast ROM library |
| STM32F4 | 168 MHz | 32-bit Cortex-M4F | single-precision hardware FPU | Single-cycle multiply and DSP instructions; float math is fast |
| ESP32 | 240 MHz | 32-bit Xtensa LX6 (dual core) | single-precision hardware FPU | Fast, but shares the core with the Wi-Fi stack |
A higher clock does not always mean a faster program: word width, floating-point hardware, memory speed and the interrupt design matter as much. A real application is limited by memory, peripherals and the interrupt design as much as by clock speed.
The toolchain: from source to chip
| Step | What happens | Tool |
|---|---|---|
| 1. Edit | Write C, C++ (or MicroPython, Rust) source files | Any editor or an IDE |
| 2. Compile | Each source file becomes an object file for the chip’s core | A cross-compiler: avr-gcc, arm-none-eabi-gcc, xtensa-esp32-elf-gcc, riscv-gcc, clang |
| 3. Link | Object files and libraries are joined using a linker script that tells where flash and RAM are | The linker (ld) with the chip’s linker script |
| 4. Convert | The result (ELF) is turned into a binary or hex file | objcopy |
| 5. Flash | The file is written to the chip’s flash memory | USB bootloader, UF2 drag-and-drop, serial bootloader, or a debug probe (SWD / JTAG) with OpenOCD or vendor tools |
| 6. Run and debug | Reset the chip; watch the serial output; use a debugger to set breakpoints | Serial monitor, logic analyzer, GDB with a probe (see Debugging) |
- Cross-compiling: the compiler runs on your PC but produces code for another processor. The “target triple” (for example
arm-none-eabi) names the architecture and that there is no operating system (“bare metal”). - Optimization level:
-Os(small),-O2(fast) and-Og(debuggable). Many bugs appear or vanish when the optimization changes, which usually signals a missingvolatileor undefined behavior. - Map file: the linker can write a map of where every function and variable went and how much flash and RAM each uses. Read it when a build runs out of space.
- Bootloaders and probes: a bootloader (Arduino, UF2, ESP ROM) lets you flash over USB without extra hardware; a debug probe (ST-Link, J-Link, CMSIS-DAP, Picoprobe) also gives breakpoints and register views.
Development environments and frameworks
| Environment | Good for | Notes |
|---|---|---|
| Arduino IDE / Arduino CLI | First projects, huge library collection | Simple; supports many boards through board packages |
| PlatformIO (in VS Code) | Serious projects across many boards | Project files, library management and tests; uses the Arduino framework or the vendor SDKs |
| Vendor IDEs and SDKs | STM32CubeIDE, MPLAB X, ESP-IDF, Pico SDK, nRF Connect SDK | Full access to the chip’s peripherals, drivers and examples |
| Zephyr and FreeRTOS | Larger projects with an RTOS and many boards | Consistent device drivers, networking, power management |
| MicroPython / CircuitPython | Quick prototypes, education, scripting | Slower and uses more memory; superb for experimenting at a prompt |
| Rust (embassy, embedded-hal) | Memory-safe firmware | Growing support; steeper learning curve |
Libraries or registers?
A pin can be set three ways: with a friendly library call (digitalWrite(13, HIGH)), with a vendor hardware-abstraction layer (HAL), or by writing the chip’s registers directly. The library is easiest and portable; the register method is the fastest and the smallest, and it teaches how the chip works. The Bitwise and Register Field Calculator helps with the masks.
// Three ways to turn on the LED on Arduino pin 13 (PB5 on an ATmega328P)
pinMode(13, OUTPUT); // Arduino library: about 100 cycles per call because it looks up the pin
digitalWrite(13, HIGH);
DDRB |= (1 << DDB5); // registers: set the data-direction bit (output)
PORTB |= (1 << PORTB5); // registers: set the output bit; compiles to one instruction, 2 cycles
PORTB &= ~(1 << PORTB5); // and clear it
The |=, &= ~ and ^= idioms set, clear and toggle single bits without disturbing the others. At 16 MHz one cycle is 62.5 ns, so the register version toggles a pin at up to about 4 MHz, while digitalWrite manages well under 150 kHz.
A first project, step by step
- 1. Check the board’s voltage and pin map (3.3 V or 5 V logic; which pins are safe, which are used for boot or USB).
- 2. Install the toolchain and the board package, and connect the board by USB; select the board and port.
- 3. Blink an LED and print a message to the serial port: proves the compiler, the upload and the monitor.
- 4. Add one peripheral at a time (a button, then an analog sensor, then an I²C device), testing each before the next.
- 5. Write down the wiring and the pin names in the code as constants, not as magic numbers.
- 6. Use version control (git), commit working states, and keep the tool versions in the project so that it still builds next year.
const uint8_t LED_PIN = 13; // named constants, not magic numbers
const uint8_t BUTTON_PIN = 2;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // internal pull-up: pressed = LOW
Serial.begin(115200);
Serial.println("ready");
}
void loop() {
bool pressed = (digitalRead(BUTTON_PIN) == LOW);
digitalWrite(LED_PIN, pressed);
}
Organizing a firmware project
- Separate the hardware from the logic. Put pin and register access in a small “driver” layer, and the decisions (state machines, control laws) in code that does not touch pins directly. The logic can then be tested on your PC (see Debugging, Testing and Troubleshooting).
- One file per module with a header that lists its public functions:
sensor.c / sensor.h,motor.c / motor.h. - Configuration in one place: pin assignments, timing constants and thresholds in a single header.
- Fixed-size types: use
uint8_t,int32_tand so on (see Memory, Number Formats and Reliability). - Fail visibly: check return values, handle bad input, and report errors through an LED, a log line or a status register.
- Plan for updating in the field before shipping (see Wireless, Networking and Updates).
Related
- Embedded Systems: the overview of this guide.
- GPIO, Interrupts and Debouncing: the first peripheral to master.
- Memory, Number Formats and Reliability: flash, RAM and number formats.
- MCU Pinout and Peripheral Multiplexer and Bitwise and Register Field Calculator.
- Programming Languages: C types, bit operations and numerical methods.
FAQ
Which board should a beginner buy?
An Arduino Uno or Nano is the easiest start: huge community, forgiving, plenty of tutorials. An ESP32 board or a Raspberry Pi Pico is a better value once you want more speed or wireless, at the cost of working in 3.3 V logic.
Do I need to learn C?
C (and a little C++) is the common language of firmware and the best way to understand the hardware. MicroPython is a quick way to start, but you will meet C in every datasheet example and library.
What is the difference between a bootloader and a debugger?
A bootloader is a small program in flash that receives new firmware over USB or serial. A debug probe talks to the chip’s debug port (SWD or JTAG) to program it, stop it, step through code and inspect registers.
Can I use a Raspberry Pi instead of a microcontroller?
For networking, vision and user interfaces yes, but not for hard real-time timing, microsecond-level control or microamp sleep currents. Many designs use both: a Pi for the interface and an MCU for the real-time I/O.
References
- Microcontroller datasheets and reference manuals (Microchip ATmega328P, Raspberry Pi RP2040, Espressif ESP32, STMicroelectronics STM32).
- Arm, Cortex-M Technical Reference Manuals (core instruction timing).
- White, E., Making Embedded Systems, O’Reilly (design process and toolchains).
Last reviewed: October 2026. Chip specifications are typical: always check the current datasheet.
