ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project
Picking the wrong microcontroller at the start of a project is one of the most expensive mistakes in embedded development. You end up either redesigning hardware mid-project or shipping a product that can't hit its power or performance targets. After years of shipping production firmware for clients across multiple industries, we have developed a clear framework for making this decision.
This guide is a deep technical comparison — not a marketing comparison. We will look at real numbers, real trade-offs, and give you the decision matrix we use internally at CodeCaracal.
Processing Power and Architecture
The ESP32 is a dual-core Xtensa LX6 processor running at up to 240 MHz with 520 KB of SRAM. The newer ESP32-S3 adds vector extensions useful for ML inference at the edge. For most IoT workloads — JSON serialization, protocol handling, sensor fusion — this is more than sufficient.
STM32 is a family, not a single chip. The F0/G0 series runs Cortex-M0+ at 64 MHz. The F4/F7 series runs Cortex-M4/M7 at up to 216 MHz with hardware FPU, DSP instructions, and ART Accelerator (zero-wait-state flash execution). The H7 series hits 480 MHz with 1 MB of tightly coupled RAM and a full DSP/FPU pipeline.
For raw determinism and interrupt latency, Cortex-M wins. The ARM architecture has a well-characterized interrupt entry and exit cycle count. Xtensa interrupts have more variable behavior in the face of cache misses, which matters for hard real-time control loops.
// STM32 hard real-time motor control ISR — sub-microsecond jitter guaranteed
void TIM1_UP_IRQHandler(void) {
// ARM Cortex-M automatically pushes registers — latency is deterministic
if (TIM1->SR & TIM_SR_UIF) {
TIM1->SR &= ~TIM_SR_UIF; // Clear flag first (LL pattern) // Read ADC result (injected, pre-triggered by timer)
int32_t current_raw = ADC1->JDR1;
int32_t current_mA = (current_raw * 3300) / 4096; // 12-bit, 3.3V ref
// PI controller — 400 ns total ISR execution on 168 MHz F4
int32_t error = target_current_mA - current_mA;
integral += error;
int32_t output = (KP * error + KI * integral) >> 10;
TIM1->CCR1 = (uint32_t)CLAMP(output, 0, TIMER_PERIOD);
}
}
WiFi and Bluetooth: Built-in vs External
This is the single biggest practical differentiator. The ESP32 has WiFi 802.11 b/g/n and Bluetooth 4.2/BLE built directly into the chip. You get a complete wireless stack for $3–5 in quantity. The SDK (ESP-IDF) provides a production-grade TCP/IP stack (lwIP), TLS (mbedTLS), and MQTT clients out of the box.
STM32 has no built-in radio. Adding WiFi means adding a module like the ESP8266, ESP-AT firmware companion chip, or a dedicated WiFi module such as the ATWINC1500. This adds BOM cost ($3–8), PCB area, an additional SPI/UART interface, and firmware complexity managing an AT command layer.
For any project where wireless is a core feature, the ESP32 wins on system cost and simplicity. The only reason to pair an STM32 with an external radio module is when you need the STM32's real-time capabilities and wireless is secondary.
Power Consumption — Where the Numbers Actually Matter
This is the most nuanced section, because datasheets lie by omission.
ESP32 active current: 160–260 mA during WiFi Tx. Deep sleep: 10 µA (typical), 150 µA with ULP coprocessor active.
STM32 active current: Varies enormously by series. STM32L4 in run mode at 80 MHz draws ~8 mA. In stop mode with RTC: 900 nA. The L series is purpose-built for ultra-low power with multiple sub-microamp stop modes and a low-power timer that keeps running through stop modes.
// STM32L4 entering Stop 2 mode — sub-microamp standby
void enter_ultra_low_power_stop(uint32_t wakeup_seconds) {
// Configure LPTIM1 on LSE (32.768 kHz) — stays active in Stop 2
LPTIM1->ARR = (32768 * wakeup_seconds) - 1;
LPTIM1->CR |= LPTIM_CR_CNTSTRT; // Disable all unnecessary peripherals
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
// Enter Stop 2 — core off, SRAM retained, RTC/LPTIM1 alive
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
// Wakeup here after LPTIM match — re-enable clocks, resume
SystemClock_Config();
}
The STM32L series achieves 10× lower sleep current than ESP32 deep sleep. For a coin-cell device sampling once per hour, this translates to 5-year vs 6-month battery life.
Development Ecosystem and IDE Support
ESP32 development happens in ESP-IDF (CMake-based, C/C++) or Arduino framework. VS Code with the ESP-IDF extension is the dominant IDE. The toolchain is mature, documentation is excellent, and the community is enormous. ESP-IDF's component manager has hundreds of production-ready drivers.
STM32 development uses STM32CubeIDE (Eclipse-based), STM32CubeMX for peripheral initialization code generation, and optionally VS Code with Cortex-Debug. The HAL/LL library ecosystem is comprehensive. ST provides reference designs for virtually every peripheral combination.
Both ecosystems support FreeRTOS natively. Both have good JTAG/SWD debugging support — though ST's on-board ST-Link debugger is marginally more reliable than ESP32's JTAG over USB, which requires OpenOCD.
Cost Analysis at Scale
| | ESP32-WROOM-32E | STM32F411CE | STM32L476RG | |---|---|---|---| | Unit cost (1k qty) | ~$2.80 | ~$3.20 | ~$4.50 | | External WiFi needed | No | Yes (+$3.00) | Yes (+$3.00) | | Effective wireless BOM | $2.80 | $6.20 | $7.50 | | Ultra-low power | Moderate | No | Yes |
For wireless applications, the ESP32 is significantly cheaper at scale. For ultra-low-power wired applications (industrial sensors, medical devices), the STM32L series justifies its cost premium.
The Decision Matrix
Choose ESP32 when:
Choose STM32 when:
Use both together when:
There is no universal winner. The right answer depends on your power budget, connectivity requirements, real-time constraints, and production timeline.
[Contact Code Caracal](/contact) — we build production firmware for clients across 15+ countries.