XIAO ESP32S3 via arduino-cli (Windows)
Everything below was verified on real hardware (esp32 core 3.3.10, 2026-07).
Follow it literally and you will not need to troubleshoot.
Paths below are relative to this skill's folder. <PORT> is the board's COM
port and <SKETCH_DIR> is a sketch folder in the user's workspace.
One-time setup (skip any step that is already true)
arduino-cli version # confirm arduino-cli exists
arduino-cli core list # is esp32:esp32 3.x installed?
# if not (this downloads ~1 GB and can take several minutes — use a long timeout):
arduino-cli core update-index
arduino-cli core install esp32:esp32
Finding the board
arduino-cli board list
The XIAO usually shows as an unidentified "Serial Port (USB)" — that row's
port (e.g. COM4) is <PORT>. If NO port appears: the board may be in deep
sleep (port only exists while awake — see pitfall 2), or needs bootloader
mode (hold the BOOT button while plugging in USB; port number may change).
Standard workflow
# sketch layout rule: folder name and .ino name must match: my_sketch\my_sketch.ino
# compile (first compile of a session takes 1-3 min; cached after)
arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
# upload
arduino-cli upload -p <PORT> --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
# read serial for 10 s (NEVER `arduino-cli monitor` — it blocks forever)
powershell -File scripts\read_serial.ps1 -Port <PORT> -Seconds 10
# optional: send a line to the board and read the response
powershell -File scripts\read_serial.ps1 -Port <PORT> -Seconds 10 -Send "hello"
PowerShell 5.1 has no &&. Chain with ; or if ($?) { ... }.
Verified example sketches
assets/sketches/ contains 11 sketches that all compiled, uploaded, and ran
on real hardware. Copy the closest one into the user's workspace as a
starting point instead of writing from scratch. See references/examples.md
for what each proves and its expected output.
Sketch rules that prevent bugs
Serial.begin(115200); then delay(3000); before the first print — USB CDC
needs time to enumerate, otherwise early output is silently lost.
- The user LED is
LED_BUILTIN (GPIO21) and is inverted: LOW = ON,
HIGH = OFF. analogWrite(LED_BUILTIN, 0) = full brightness.
- ADC: call
analogReadResolution(12); read raw with analogRead(A0) and
millivolts with analogReadMilliVolts(A0). A floating pin reads noisy
60–160 raw — that is normal, not a bug.
- Touch:
touchRead(T2) (T2 = GPIO2 = pin D1). Baseline ≈ 17,500; the value
RISES well above 40,000 when touched (opposite of original ESP32).
WiFi.macAddress() returns 00:00:00:00:00:00 until WiFi is started with
WiFi.mode(WIFI_STA) — not an error.
- Networking in core 3.x: use
NetworkClient / NetworkServer
(old WiFiClient / WiFiServer names may not exist).
Pin map (XIAO ESP32S3)
| Label |
GPIO |
Notes |
| D0 |
1 |
A0, touch T1 |
| D1 |
2 |
A1, touch T2 |
| D2 |
3 |
A2, touch T3 |
| D3 |
4 |
A3, touch T4 |
| D4 |
5 |
SDA, touch T5 |
| D5 |
6 |
SCL, touch T6 |
| D6 |
43 |
UART TX (Serial1) |
| D7 |
44 |
UART RX (Serial1) |
| D8 |
7 |
SPI SCK |
| D9 |
8 |
SPI MISO |
| D10 |
9 |
SPI MOSI |
| user LED |
21 |
inverted (LOW = on) |
Known pitfalls — read before "debugging"
- Opening the COM port resets the board (DTR toggle →
rst:0x15 USB_UART_CHIP_RESET). Every serial read starts from a fresh boot. This
also wipes RTC_DATA_ATTR variables, so you cannot observe deep-sleep
boot counts through a serial monitor. It is not a crash.
- Deep sleep makes the COM port disappear. USB CDC powers down during
sleep, so the port vanishes and Windows plays disconnect/connect sounds
every cycle. This is normal and is the correct evidence that sleep works.
Verify the cycle with
scripts\watch_port.ps1 -Port <PORT> (logs
appear/disappear timestamps) instead of opening the port. To reflash a
sleeping board, catch the short awake window, or hold the BOOT button
while plugging USB (bootloader mode; the port number may change).
arduino-cli monitor never exits. In an automated session it hangs
your shell. Always use scripts\read_serial.ps1, which has a deadline.
- PSRAM reads 0 bytes by default. The default build has PSRAM disabled.
If a sketch needs PSRAM (camera, big buffers), add
--board-options PSRAM=opi to BOTH compile and upload commands.
netsh wlan show networks is cached and may not show a SoftAP the
board just created. Trust the board's serial output ("AP IP address:
192.168.4.1"); test the AP from a phone instead.
- Upload fails / port busy: make sure no serial reader is still running
(only one process can hold the port). If the board looks bricked, use
bootloader mode (pitfall 2).
- First compile is slow (cold cache, big toolchain). Do not kill it
before ~3 minutes; use a generous timeout.
- Board can get stuck in reset after a serial session (only the ROM
banner
ESP-ROM:esp32s3-... prints, app never boots, SoftAP/servers die).
Cause: DTR/RTS line state left by the PC when closing the port. Recover
with arduino-cli upload (esptool's reset sequence fixes it). If a
headless demo (web server etc.) must keep running, avoid opening the
serial port at all — use mDNS or the router's DHCP table to find the
board instead of reading its IP over serial.
- Same-name Edge Impulse library swap needs
--clean. Replacing the
<project>_inferencing library with a rebuilt copy (same version) leaves
stale objects in arduino-cli's cache — symptoms: objs.a ... is not an object link errors or edits that appear to have no effect. Always pass
--clean on the first compile after swapping the library.
Verifying without human eyes
- LED/PWM: also print state to serial each cycle so the log proves behavior.
- WiFi scan / BLE scan: success = a numbered list of networks/devices.
- SoftAP: success = serial prints the AP IP; full HTTP test needs a phone.
- Deep sleep: success =
watch_port.ps1 shows a stable disappear/appear
cycle matching the programmed sleep time (+ ~3 s awake).
1---2name: xiao-esp32s33description: Operate a Seeed XIAO ESP32S3 board with arduino-cli on Windows: install the core, compile, upload, read serial output, and run verified examples (blink, serial, touch, ADC, PWM, WiFi scan/AP, BLE, deep sleep). Use this skill WHENEVER the task involves an ESP32S3 / XIAO board, arduino-cli, flashing a sketch, reading the serial monitor, COM ports, or any embedded/Arduino work — even for a "simple" LED change. It contains verified commands and known pitfalls (DTR reset, deep-sleep USB drop, PSRAM flag) that prevent wasted debugging time.4license: MIT5---67# XIAO ESP32S3 via arduino-cli (Windows)89Everything below was verified on real hardware (esp32 core 3.3.10, 2026-07).10Follow it literally and you will not need to troubleshoot.1112Paths below are relative to this skill's folder. `<PORT>` is the board's COM13port and `<SKETCH_DIR>` is a sketch folder in the user's workspace.1415## One-time setup (skip any step that is already true)1617```powershell18arduino-cli version # confirm arduino-cli exists19arduino-cli core list # is esp32:esp32 3.x installed?20# if not (this downloads ~1 GB and can take several minutes — use a long timeout):21arduino-cli core update-index22arduino-cli core install esp32:esp3223```2425## Finding the board2627```powershell28arduino-cli board list29```3031The XIAO usually shows as an unidentified "Serial Port (USB)" — that row's32port (e.g. `COM4`) is `<PORT>`. If NO port appears: the board may be in deep33sleep (port only exists while awake — see pitfall 2), or needs bootloader34mode (hold the BOOT button while plugging in USB; port number may change).3536## Standard workflow3738```powershell39# sketch layout rule: folder name and .ino name must match: my_sketch\my_sketch.ino4041# compile (first compile of a session takes 1-3 min; cached after)42arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>4344# upload45arduino-cli upload -p <PORT> --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>4647# read serial for 10 s (NEVER `arduino-cli monitor` — it blocks forever)48powershell -File scripts\read_serial.ps1 -Port <PORT> -Seconds 104950# optional: send a line to the board and read the response51powershell -File scripts\read_serial.ps1 -Port <PORT> -Seconds 10 -Send "hello"52```5354PowerShell 5.1 has no `&&`. Chain with `;` or `if ($?) { ... }`.5556## Verified example sketches5758`assets/sketches/` contains 11 sketches that all compiled, uploaded, and ran59on real hardware. Copy the closest one into the user's workspace as a60starting point instead of writing from scratch. See `references/examples.md`61for what each proves and its expected output.6263## Sketch rules that prevent bugs6465- `Serial.begin(115200);` then `delay(3000);` before the first print — USB CDC66 needs time to enumerate, otherwise early output is silently lost.67- The user LED is `LED_BUILTIN` (GPIO21) and is **inverted**: `LOW` = ON,68 `HIGH` = OFF. `analogWrite(LED_BUILTIN, 0)` = full brightness.69- ADC: call `analogReadResolution(12)`; read raw with `analogRead(A0)` and70 millivolts with `analogReadMilliVolts(A0)`. A floating pin reads noisy71 60–160 raw — that is normal, not a bug.72- Touch: `touchRead(T2)` (T2 = GPIO2 = pin D1). Baseline ≈ 17,500; the value73 RISES well above 40,000 when touched (opposite of original ESP32).74- `WiFi.macAddress()` returns `00:00:00:00:00:00` until WiFi is started with75 `WiFi.mode(WIFI_STA)` — not an error.76- Networking in core 3.x: use `NetworkClient` / `NetworkServer`77 (old `WiFiClient` / `WiFiServer` names may not exist).7879## Pin map (XIAO ESP32S3)8081| Label | GPIO | Notes |82|-------|------|-------|83| D0 | 1 | A0, touch T1 |84| D1 | 2 | A1, touch T2 |85| D2 | 3 | A2, touch T3 |86| D3 | 4 | A3, touch T4 |87| D4 | 5 | SDA, touch T5 |88| D5 | 6 | SCL, touch T6 |89| D6 | 43 | UART TX (Serial1) |90| D7 | 44 | UART RX (Serial1) |91| D8 | 7 | SPI SCK |92| D9 | 8 | SPI MISO |93| D10 | 9 | SPI MOSI |94| user LED | 21 | inverted (LOW = on) |9596## Known pitfalls — read before "debugging"97981. **Opening the COM port resets the board** (DTR toggle → `rst:0x1599 USB_UART_CHIP_RESET`). Every serial read starts from a fresh boot. This100 also wipes `RTC_DATA_ATTR` variables, so you cannot observe deep-sleep101 boot counts through a serial monitor. It is not a crash.1022. **Deep sleep makes the COM port disappear.** USB CDC powers down during103 sleep, so the port vanishes and Windows plays disconnect/connect sounds104 every cycle. This is normal and is the correct evidence that sleep works.105 Verify the cycle with `scripts\watch_port.ps1 -Port <PORT>` (logs106 appear/disappear timestamps) instead of opening the port. To reflash a107 sleeping board, catch the short awake window, or hold the BOOT button108 while plugging USB (bootloader mode; the port number may change).1093. **`arduino-cli monitor` never exits.** In an automated session it hangs110 your shell. Always use `scripts\read_serial.ps1`, which has a deadline.1114. **PSRAM reads 0 bytes by default.** The default build has PSRAM disabled.112 If a sketch needs PSRAM (camera, big buffers), add113 `--board-options PSRAM=opi` to BOTH compile and upload commands.1145. **`netsh wlan show networks` is cached** and may not show a SoftAP the115 board just created. Trust the board's serial output ("AP IP address:116 192.168.4.1"); test the AP from a phone instead.1176. **Upload fails / port busy**: make sure no serial reader is still running118 (only one process can hold the port). If the board looks bricked, use119 bootloader mode (pitfall 2).1207. **First compile is slow** (cold cache, big toolchain). Do not kill it121 before ~3 minutes; use a generous timeout.1228. **Board can get stuck in reset after a serial session** (only the ROM123 banner `ESP-ROM:esp32s3-...` prints, app never boots, SoftAP/servers die).124 Cause: DTR/RTS line state left by the PC when closing the port. Recover125 with `arduino-cli upload` (esptool's reset sequence fixes it). If a126 headless demo (web server etc.) must keep running, avoid opening the127 serial port at all — use mDNS or the router's DHCP table to find the128 board instead of reading its IP over serial.1299. **Same-name Edge Impulse library swap needs `--clean`.** Replacing the130 `<project>_inferencing` library with a rebuilt copy (same version) leaves131 stale objects in arduino-cli's cache — symptoms: `objs.a ... is not an132 object` link errors or edits that appear to have no effect. Always pass133 `--clean` on the first compile after swapping the library.134135## Verifying without human eyes136137- LED/PWM: also print state to serial each cycle so the log proves behavior.138- WiFi scan / BLE scan: success = a numbered list of networks/devices.139- SoftAP: success = serial prints the AP IP; full HTTP test needs a phone.140- Deep sleep: success = `watch_port.ps1` shows a stable disappear/appear141 cycle matching the programmed sleep time (+ ~3 s awake).