Kicking off the electronics build-logs — flashing the ESP32-S3, blinking an
LED, and, at the end, where this section actually went afterwards.
This build-log started on dankdev.com. That address,
dankdev.com/electronics/hello-esp32,
now carries a short notice that sends you straight back to this page — the full write-up
is the one you are reading.
This is the start of the Electronics section — build-logs from my journey
into ESP32 and electrical engineering. Two more pages have gone up here since; they are
linked, with what each one gives you, under What came next.
Why this section exists
I build web things for a living, but lately I've been pulled toward hardware —
microcontrollers, breadboards, and the satisfying moment when code makes something in the
physical world move. These posts are my notebook: what I built, the bill of materials, the
wiring, the code, and the gotchas.
Bill of materials: the parts this build uses
The third column carries nothing new. Every line in it is a sentence from further down this
page, quoted word for word and linked back to the section that argues it. It is there because
“ESP32-S3 DevKitC” and “1” do not tell you which board the two-port
behaviour below was read off, or which cable can stop an upload dead — and this page says
both, a long way below the point where you would be ordering parts. Nothing in the column is a
price, a seller or a part number this page had not already printed; where the log recorded
nothing further about a part, the cell is empty, and the reason is under the table.
Why one cell in that column is empty. The breadboard-and-jumper-wires row has
nothing in it, and that is the entry rather than a gap someone forgot to fill. This log recorded a
breadboard and jumper wires and never recorded which ones: no size, no tie-point count, no wire
length, no brand, no seller. What the page will say about a breadboard is how it catches you out
— the centre channel splits every row
— and a note about how a part fails you is not a note about which part to put in a basket.
Filling the cell would mean inventing a purchase. It is the same refusal as
the bench parts page, which ships no
bench-tray.stl because any size it published would be a guess about somebody
else’s bench.
The classic “hello world”: blink
Every embedded journey starts with blinking an LED. Here it is on an ESP32-S3 using the
Arduino core:
Wiring the LED: GPIO2 through a 220 Ω resistor to ground
Wire the LED to GPIO2 through a 220 Ω resistor to ground, flash,
and you've got first light. The rest of this section is what “flash” actually
involves, and what to do when there is no first light.
A drawing of that sentence, not a
photograph — nothing here is traced from a board, and the site's other figures are
drawings too. It carries only what this page already states: wire the LED to
GPIO2 through a 220 Ω resistor to ground, with the long leg (anode)
toward GPIO2 and the short leg (cathode) toward ground. The resistor is drawn on the GPIO2
side of the LED for one reason — it has to be drawn somewhere. It may sit on either
side of the LED; it is a series circuit and it limits the same current in either position.
The two ends are placed in schematic order, not in their positions on the board's headers:
there is deliberately no header pin, no silkscreen, no wire colour and no resistor band
pattern in this drawing, because this log does not record any of them.
Flashing it, and what goes wrong the first time
Between “here is the code” and “the LED is blinking” sits a short
chain of decisions, and most of them have one obvious wrong answer. What follows is those
decisions, stated as decisions — not a transcript of a session. Everything here is either a
consequence of the circuit and the sketch printed above, or is read off Espressif's published
documentation for this board and chip, and is labelled as such where it is.
Every first-run failure this section describes, as symptom → cause → what to
do. It is an index, not a replacement: each case is stated in full in the sub-sections
below, and nothing appears here that is not argued there.
What you see
Why it happens
What to do
The upload never handshakes and eventually times out, with a wiring diagram that was
correct the whole time
The cable is in one connector and the port selected in the IDE belongs to the other.
Either connector will power the board, so “it has power” tells you nothing
about which port your computer is talking to.
If exactly one serial device appears when the board is plugged in and vanishes when you
unplug it, that is the one to select; if two appear, you want the bridge.
The serial port disappears and re-enumerates on every reset
That is the USB connector, produced by the chip's own
USB-Serial-JTAG peripheral, so it only exists while the chip is up and presenting it.
Use the UART connector: the bridge is a separate device from the
ESP32-S3, so it enumerates whenever the board has power, which is what makes it the port
that still works when the firmware is wedged.
GPIO19 and GPIO20 are not behaving as free pins
Espressif's ESP32-S3 datasheet assigns that peripheral GPIO19 and GPIO20.
Those two pins are not yours while you are using the USB connector.
A successful-looking upload and a dead board
A plain ESP32 board entry was chosen. The original ESP32 and the ESP32-S3 are different
chips with different architectures, and a binary built for the wrong target will not
run.
Install arduino-esp32 through the board manager and choose an ESP32-S3
board entry. Pick the target for the chip, then pick the port.
Serial output goes missing on this board
The per-board USB CDC On Boot option decides whether the core's
Serial object is routed to the chip's native USB port or out through the
UART bridge.
Worth knowing about, but it cannot affect this sketch: the code above never calls
Serial at all.
The port is there and the upload still fails
The chip is not sitting in its serial bootloader — a cable without data lines, a
bridge the host has not enumerated, or firmware holding the CPU busy.
Hold BOOT, tap RESET, release BOOT, and start the
upload then. If the manual entry works and the automatic one never does, the problem is
the cable or the reset circuit, not the sketch.
Nothing blinks at all
Upload failures are loud and they are the most likely cause.
Read the last few lines of the uploader output before you touch a single jumper
wire.
The LED just sits there dark, and the uploader output was clean
LED polarity. Backwards, an LED does not blow, it simply does not conduct, which makes
this the quietest failure on the list.
The code drives GPIO2 high, so current has to run GPIO2 → LED →
ground: the long leg (anode) toward GPIO2, the short leg (cathode) toward ground.
It blinks, but out of phase with the code
Wrong rail: the LED runs from the 3V3 rail back into GPIO2 instead of from GPIO2 out to
ground, so it is lit when the code says LOW.
Electrically fine, logically backwards. Run it from GPIO2 out to ground instead.
A dark LED behind a perfectly good sketch
Wrong hole. The sketch hard-codes pin 2, and a jumper one row over on the breadboard,
or one header pin over on the board, misses it.
Count the row again: the jumper has to land on GPIO2 itself.
The board's own user LED does nothing
This sketch does not drive it. Nothing here writes to any pin except GPIO2.
Watch the LED you wired, not the on-board one — the on-board LED tells you
nothing about whether the program is running.
An LED and a resistor that look like they are in series, and nothing lights
Breadboard continuity. The centre channel splits every row, and the power rails on many
boards are split in the middle too.
They can be sitting in two unconnected halves of the same row; re-seat them into one
connected half.
A dark LED, and no way to tell whether the program is running
This sketch gives you no way to tell “the program is not running” apart from
“the program is running and the LED is wrong.” Both look identical.
Add Serial.begin(115200) to
setup() and a Serial.println() inside the loop, flash again, and
open a serial monitor. If the lines arrive, the fault is in the six wires; if nothing
arrives, stop looking at the breadboard.
The two USB ports, and which one carries what
This is the failure that eats the most first evenings with this board family, and it has
nothing to do with your code. Espressif's ESP32-S3-DevKitC-1 user guide describes two
USB-C connectors on the board, silkscreened UART and USB, and
they are not interchangeable:
UART runs through the on-board USB-to-UART bridge chip. The bridge is a
separate device from the ESP32-S3, so this port enumerates on your computer as a serial
port whenever the board has power, whether or not the ESP32-S3 is running anything at all.
That is what makes it the port that still works when the firmware is wedged.
USB is wired to the ESP32-S3's own USB peripheral — the guide calls it
USB-Serial-JTAG. The port is produced by the chip itself, so it only exists while the chip
is up and presenting it, and it disappears and re-enumerates on every reset, which includes
every reset the uploader performs. Espressif's ESP32-S3 datasheet assigns that peripheral
GPIO19 and GPIO20, so those two pins are not yours while you are using this connector.
Either connector will power the board, so “it has power” tells you nothing about
which port your computer is talking to. The classic version of this failure is a cable in one
connector and a port selected in the IDE that belongs to the other: the upload never handshakes
and eventually times out, with a wiring diagram that was correct the whole time. If exactly one
serial device appears when the board is plugged in and vanishes when you unplug it, that is the
one to select; if two appear, you are looking at both connectors and you want the bridge.
Board target and core
The sketch above is written against the Arduino core, which in practice means installing
Espressif's arduino-esp32 package through the Arduino IDE's board manager and then
choosing an ESP32-S3 board entry. Choosing a plain ESP32 entry is a real and
easy mistake: the original ESP32 and the ESP32-S3 are different chips with different
architectures, and a binary built for the wrong target will not run — you get a successful-looking
upload and a dead board. Pick the target for the chip, then pick the port.
The arduino-esp32 core also exposes a per-board USB CDC On Boot option,
which decides whether the core's Serial object is routed to the chip's native USB
port or out through the UART bridge. It is worth knowing about because it explains why serial
output can go missing on this board, but it cannot affect this sketch: the code above never
calls Serial at all.
If the port is there and the upload still fails
Uploading requires the chip to be sitting in its serial bootloader. The toolchain normally
puts it there for you by toggling the bridge's control lines as the upload starts. When that
does not happen — a cable without data lines, a bridge the host has not enumerated, firmware
holding the CPU busy — the board carries the manual equivalent: the user guide documents
BOOT and RESET buttons. Hold BOOT, tap RESET, release BOOT,
and the chip comes up in download mode; start the upload then. If a manual download-mode entry
works and the automatic one never does, the problem is the cable or the reset circuit, not the
sketch.
What it looks like when it works
The upload finishes, the chip resets, and the sketch starts. The only thing this program
produces is the LED: delay(500) on each side of the loop, so an even on/off blink
at roughly one cycle per second. There is no serial banner and no boot message from the sketch,
because it never opens a serial port — anything you do see in a serial monitor came from the
bootloader or the core, not from these nine lines. Nothing else on the board is touched: the
sketch writes to GPIO2 and to no other pin.
One thing that makes GPIO2 a safe choice here rather than a lucky one:
Espressif's ESP32-S3 datasheet lists the chip's strapping pins as GPIO0, GPIO3, GPIO45 and
GPIO46, and GPIO2 is not among them, so an LED and a 220 Ω resistor hanging on it
cannot change how the board boots. That was not true of the original ESP32, which is part of
why blink examples for the two chips disagree about which pin to use — copying a pin number
from an ESP32 tutorial into an ESP32-S3 sketch is its own small trap.
When nothing blinks, in this order
Cheapest checks first. Each of these is a statement about the circuit and the code on this
page, not a report about any particular board:
Did the upload actually finish? Upload failures are loud and they are the
most likely cause. Read the last few lines of the uploader output before you touch a single
jumper wire.
LED polarity. The code drives GPIO2high to light
the LED, so current has to run GPIO2 → LED → ground: the long leg (anode) toward GPIO2, the
short leg (cathode) toward ground. Backwards, an LED does not blow, it simply does not
conduct — it just sits there dark, which makes this the quietest failure on the list and the
first wiring thing to check. The 220 Ω resistor may sit on either side of the LED;
it is a series circuit and it limits the same current in either position.
Wrong rail. If the LED runs from the 3V3 rail back into GPIO2
instead of from GPIO2 out to ground, it will still blink — inverted, lit when the code says
LOW. Electrically fine, logically backwards. If the blink works but feels out of
phase with the code, this is why.
Wrong hole. The sketch hard-codes pin 2. A jumper one row over on the
breadboard, or one header pin over on the board, is a dark LED behind a perfectly good
sketch.
Wrong LED. If your board carries its own user LED, this sketch does not
drive it. Nothing here writes to any pin except GPIO2, so watching the on-board
LED tells you nothing about whether the program is running.
Breadboard continuity. The centre channel splits every row, and the power
rails on many boards are split in the middle too. An LED and a resistor that look like they
are in series can be sitting in two unconnected halves of the same row.
There is a limit to how far that list can take you, and it is worth naming: this sketch gives
you no way to tell “the program is not running” apart from “the program is
running and the LED is wrong.” Both look identical — a dark LED. The cheapest way out is to
add Serial.begin(115200) to setup() and a Serial.println()
inside the loop, flash again, and open a serial monitor. If the lines arrive, the board is fine
and the fault is in the six wires; if nothing arrives, stop looking at the breadboard. A blink
sketch is the standard first program because it needs no serial port — which is exactly the
reason it is a poor diagnostic when it fails.
What came next
Two pages have gone up on this site since this one, and neither of them is more firmware.
Both are about the same awkward step: you have the board in front of you, you want to print
something to hold it, and the dimension you need is not written down anywhere you can cite.
Both also say up front that nothing on them has been printed — they are geometry checked by
code, not parts anyone has held.
In Measuring a dev board for
3D printing when the size is not published you get the method: how to work out a dimension
the vendor never wrote down, why that page argues for a stepped gauge over a confident guess,
how leaving the uncertain axis open-ended makes being wrong cheap, and the four layers of code
that check the solid before any filament is spent.
On Four printable ESP32-S3 bench parts you get
the files that method produced: three of the four ship as STL downloads, and the fourth is
generated in the browser only. The downloads are a bench plate, a fit gauge, and a jumper-wire
comb whose teeth stand on the 2.54 mm breadboard pitch; the fourth, a parts tray, has no STL
at all, because there is no tray size that page could defend as a default. What all four do
have is an in-page generator — four of them, one per part — rebuilding the solid around
dimensions you type in yourself, running the same generator code in your own browser with
nothing uploaded.
Follow new build-logs by RSS: paste dankbuild.com/feed.xml
into any feed reader. It is a plain XML file — no signup, no email address, no account. One feed
covers the whole site, so the next build-log arrives without you having to check back.
What this log does not record
The notebook is thinner than the sentence at the top of it. Here is exactly what it holds and
what it does not, so nobody rebuilds from it expecting a completeness that is not there.
The board variant. The bill of materials says “ESP32-S3 DevKitC”
and that is the whole of it: no revision, no flash or PSRAM size, no seller. The two-port
behaviour described above is read off Espressif's published user guide for boards sold under
that name — it is documentation, not an observation of a specific board.
The LED. “5 mm LED” is recorded. Colour, forward voltage
and rated current are not.
The resistor rating. 220 Ω is recorded as a value only — no
tolerance and no wattage. Because the LED's forward voltage was never written down, the
current actually flowing through it is not recoverable from this page, and this page does not
pretend to compute it.
The serial output. There is none, and that is a fact about the sketch
rather than a gap in the notes: the code never calls Serial, so it printed
nothing. No boot log, chip ID or flash-size readout was captured.
Which port this build used. The section above says which connector carries
what according to the vendor's documentation. It does not say which one was plugged in here,
or whether the first upload succeeded.
Timing. 500 ms is what the sketch asks for. Nothing recorded what the
LED actually did, so the blink rate on this page is the code's intent, not a reading.
Whether it was re-run. This was built once, on 17 June 2026. There is no
record of it being rebuilt, re-flashed onto a second board, or left running.
Photographs. There are none of this build. Nothing on this page is
illustrated.
And one thing this page holds that no other page on this site does: a circuit that physically
existed. Measuring a dev board for
3D printing is a method note — an argument about how to proceed when a dimension is missing —
and the bench parts page is a catalogue of geometry
checked by code, which states on its own face that nothing on it has ever been printed or held.
Both are longer and more thoroughly worked than this page. Neither describes hardware anyone
assembled. This short entry is the only build-log here where a part was put in a breadboard and
something lit up, and that is worth knowing if you landed on this page first and were about to
judge the site by its length.