[ Getting Started with ESP32 Development ]

[ Overview ]

You have a board and it blinks — the hardware guide got you that far. This
is the other half: the software you write on, and the loop you'll live in every
time you change a line of code.

Nothing here is board-specific. It's the same for a bare WROOM-32 DevKit, an
ESP32-S3, or a Heltec LoRa board — pick a toolchain, install it once, and
the build → flash → monitor cycle is the same for all of them. The point of
this page is to get you past “which of these four things do I install” and into
actually shipping firmware.

[ Pick a toolchain ]

Four real options. They aren't rivals so much as different altitudes — from
“no code at all” up to “the raw SDK.” Pick by what you're building, not by what
sounds hardcore.

  Arduino core   The gentle start. The Arduino API (pinMode, digitalWrite,
                 WiFi.h) on top of Espressif's SDK, with a huge library
                 ecosystem. Every .ino sketch on this site is this. Start
                 here if the board is new to you.
  PlatformIO     The daily driver. A VS Code extension where each project
                 pins its board, framework and libraries in one
                 platformio.ini — reproducible, multi-board, with a
                 built-in monitor and debugger. Grow into this fast.
  ESP-IDF        Espressif's native C SDK (FreeRTOS, idf.py). Every
                 chip feature, full control, a steeper climb. Reach for it
                 when you're squeezing the silicon or shipping a product.
  ESPHome        No C at all — describe the device in YAML and it builds
                 the firmware for you. Perfect for Home Assistant sensors
                 (the BT proxy is this), useless for anything off that rail.

The honest default: start on the Arduino core to learn the chip, and move to
PlatformIO the moment a project has more than one source file or one library.
This guide walks both; ESP-IDF and ESPHome each have their own docs linked at
the end.

[ The Arduino path ]

Two ways in: the Arduino IDE 2.x (a GUI) or arduino-cli (terminal). Same
core underneath.

  1. Install the IDE, then add Espressif's board package. In
     Preferences → Additional boards manager URLs, paste:

       https://espressif.github.io/arduino-esp32/package_esp32_index.json

  2. Open Boards Manager, search esp32, and install “esp32 by Espressif
     Systems.” It's a big download — it pulls the whole toolchain and SDK.
  3. Select Tools → Board → ESP32 Dev Module, then pick the serial
     Port (see when flashing fails if none appears).
  4. Open the blink sketch, hit Upload, and watch the LED. That round
     trip — compile, flash, blink — proves the whole chain.
The Arduino IDE Boards Manager filtered to esp32, showing the "esp32 by Espressif Systems" package at version 3.3.11 marked INSTALLED, listing the ESP32, C3, C5, C6, H2, P4, S2 and S3 Dev Boards it provides.
Step 2, done: esp32 by Espressif Systems installed from Boards Manager. One package covers the whole family — the plain ESP32 Dev Board through the S3 and C-series — so “ESP32 Dev Module” in the board menu is now selectable.
Prefer the terminal? The same thing in arduino-cli:

  arduino-cli core install esp32:esp32
  arduino-cli compile -b esp32:esp32:esp32dev blink
  arduino-cli upload  -b esp32:esp32:esp32dev -p COM5 blink

The IDE is the friendlier place to start; the CLI is what you script later.

[ The PlatformIO path ]

PlatformIO installs as a VS Code extension (search “PlatformIO IDE” in the
extensions panel). The idea that makes it worth the switch: a project is a
folder, and one platformio.ini at its root declares everything — which
chip, which framework, which libraries, at which versions. Commit that file and
anyone rebuilds your firmware the same way.

A minimal platformio.ini for the DevKit:

  [env:esp32dev]
  platform = espressif32
  board = esp32dev
  framework = arduino
  monitor_speed = 115200
  lib_deps =
    bblanchon/ArduinoJson@^7.0.0

Your code lives in src/main.cpp. The loop is three commands, from the
terminal or the toolbar buttons:

  pio run                # compile
  pio run -t upload      # compile, then flash over USB
  pio device monitor     # open the serial console at 115200

framework = arduino gives you the exact same API as the Arduino IDE, so every
sketch and library carries over — you've just traded a GUI for a file you can
diff. Swap to framework = espidf the day you want the native SDK.
Don't want to build that scaffold by hand? Here's this exact setup as a
ready-to-open project — unzip it, open the folder in VS Code and PlatformIO
picks it up from the platformio.ini. It's the blink sketch already wired
into src/main.cpp, so pio run -t upload flashes it straight away.
The esp32-blink-platformio project open in VS Code: src/main.cpp with the blink sketch on the left, and platformio.ini on the right showing env esp32dev, platform espressif32, board esp32dev, framework arduino, monitor_speed 115200 and monitor_filters esp32_exception_decoder.
That download open in VS Code: src/main.cpp beside platformio.ini. PlatformIO reads the board, framework and monitor settings straight from that one file — pio run -t upload builds and flashes, no other setup.

[ Build, flash, monitor ]

Whatever you install, the shape of the work is identical, and you'll run this
loop hundreds of times: edit code, build it, flash it over USB, watch the serial
console, repeat. It's worth knowing what each step actually does.
The edit, build, flash, monitor loop Four stages left to right, each an arrow to the next: EDIT your sketch, BUILD to compile it, FLASH which hands the binary to esptool and writes it over USB, and MONITOR the serial console at 115200 baud. A feedback arrow runs from MONITOR back to EDIT: read the boot log, fix the code, repeat. EDIT your sketch BUILD compile FLASH esptool / USB MONITOR serial @115200 read the boot log — fix — repeat
The cycle you run all day. Build compiles, flash hands the binary to esptool to write over USB, and monitor shows what the board prints back at 115200. The boot log closes the loop: it tells you why the last flash crashed so you can fix it.
Build compiles your code against the framework into a single
    firmware binary (a .bin).
  — Flash hands that binary to esptool, which every toolchain drives
    underneath. It resets the chip into its ROM bootloader and writes the
    binary to flash over the USB-UART. When you see “Connecting...” or
    “Writing at 0x...”, that's esptool.
  — Monitor opens the serial port so you can read what the board
    prints. The ESP32 boots and logs at 115200 baud — set the monitor to
    match or you'll get gibberish. Your own Serial.println() output lands
    here too.

Two things save hours. First, the boot log: on every reset the ESP32 prints a
banner with the reset reason and, if it crashed, a Guru Meditation Error with
a backtrace. Second, the exception decoder turns that backtrace of raw
addresses into file-and-line numbers — a monitor filter in PlatformIO
(monitor_filters = esp32_exception_decoder) and a menu item in the Arduino
IDE. Turn it on before you need it.

[ Libraries, flash size and partitions ]

Libraries. In the Arduino IDE the Library Manager installs into one
shared folder used by every sketch — convenient until two projects want
different versions. PlatformIO's lib_deps installs per project at pinned
versions, so one project can't break another. Same libraries either way;
PlatformIO just keeps them from colliding.

Flash size. A WROOM-32 DevKit almost always has 4 MB of flash, and it's
not all yours — a partition table carves it into the bootloader, a small NVS
store for settings, one or two app slots for your firmware, and often a
filesystem (LittleFS) for files.

Partitions. You meet them the day you see “Sketch too big.” The default
scheme leaves room for OTA, so your app only gets ~1.2 MB. Two ways out:

  — No over-the-air updates? Pick a single-app scheme and reclaim the space.
    Arduino IDE: Tools → Partition Scheme → Huge APP (3MB No OTA).
    PlatformIO: board_build.partitions = huge_app.csv.
  — Need OTA and a big app? Drop the filesystem, or write a custom
    partition CSV that sizes the slots the way your build needs.

Most first projects never touch this. When one does, it's a one-line change, not
a rewrite.

[ OTA updates ]

Once a board is sealed in a case and stuck on a wall — a sensor node, a
mesh node, the BT proxy — you don't want to unplug it to change code.
OTA (over-the-air) flashing sends new firmware over Wi-Fi instead of USB.

The chip keeps two app slots: it runs one, writes the update into the other, and
swaps on reboot — a bad flash rolls back instead of bricking. That's why the
default partition scheme reserves OTA space. In practice:

  — Arduino: include ArduinoOTA, and the board shows up as a network
    port in the IDE.
  — PlatformIO: set upload_protocol = espota and upload_port to the
    board's IP.
  — ESPHome: OTA is built in and on by default.

One rule: put a password on it (ArduinoOTA.setPassword()). An open OTA
port is a remote-code path onto your LAN — convenient for you, and for anyone
else on the network.

[ When flashing fails ]

The upload step is where beginners lose the most time, and it's nearly always
one of these. Ranked by how often it bites.

  no serial port       The port never appears. Install the USB-UART
                       driver (CP2102 or CH340), and — check this first
                       — swap the USB cable. Charge-only cables have no data
                       lines and are the number-one culprit.
  failed to connect    “Connecting......” then a timeout. The board
                       didn't enter download mode. Hold BOOT, tap EN,
                       release BOOT — or hold BOOT through the
                       “Connecting” dots. Some clones need this every time.
  port is busy         Upload fails while the serial monitor is open.
                       The port is exclusive — close the monitor, flash,
                       reopen it.
  wrong baud           Serial monitor shows garbage. It's set to the
                       wrong speed — the ESP32 logs at 115200.
  brownout / resets    Flash stalls or the board reboots mid-write on a
                       weak USB port or thin cable. Use a rear-panel port or a
                       powered hub, and a cable you trust.
  boots to a loop      Flashes fine, then resets over and over. Usually a
                       code crash (read the backtrace), occasionally a wrong
                       flash-mode/size — leave those at the board defaults.

Work the list top-down and the first two fix it most of the time. The board is
hard to hurt from the USB side; it's the cable and the download-mode dance that
trip everyone.

[ See Also ]