ESP32
Arduino project layout
- The directory must contain a main
.inofile with the same name as the directory.blink/blink.inobuilds;blink/main.inodoes not. - Every other
.inoin the same directory is concatenated onto the main sketch in alphabetical order, after it, and compiled as a single translation unit. That is why a function can be called before it is defined in a sketch but not in a plain.cppfile. - Put
.cppand.hfiles in the same directory to keep them as separate translation units — the usual approach once a sketch grows. c_cpp_properties.jsondrives IntelliSense only. Add library paths toincludePaththere; it has no effect on what actually gets compiled.
VS Code
.vscode/arduino.json:
{
"sketch": "test.ino",
"configuration": "FlashFreq=80,UploadSpeed=921600",
"board": "esp32:esp32:nodemcu-32s",
"port": "COM7",
"output": "../build"
}
Setting output outside the sketch directory keeps build artefacts out of
version control and lets the compiler cache survive between builds — without it,
Arduino rebuilds the whole core every time.
If uploads fail at 921600 baud, drop UploadSpeed to 460800 or 115200. Long or
unshielded USB cables rarely sustain the higher rate.
Boards
ESP32-WROOM-32D (NodeMCU-32S / ESP32-E DevKit)
Board manager URL (stable): https://espressif.github.io/arduino-esp32/package_esp32_index.json
Board manager URL (development): https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
Board package: esp32 by Espressif Systems
Board: NodeMCU-32S (ESP32-E DevKit)
USB bridge: Silicon Labs CP210x USB to UART Bridge VCP driver
The USB bridge on this board is UART-only. The chip itself has a JTAG interface, but reaching it needs an external probe (ESP-Prog, FT2232H) wired to the JTAG pins — OpenOCD cannot be used over the on-board CP210x connector.
Blink example:
// Runs once at reset or power-on
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
// Runs repeatedly after setup() returns
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // LED on
delay(1000);
digitalWrite(LED_BUILTIN, LOW); // LED off
delay(1000);
}
LED_BUILTIN is only defined for board variants that declare it. On boards
without an on-board LED the sketch fails to compile — define the pin yourself,
typically GPIO2 on DevKit-style boards.
ESP32-C3-AWS-ExpressLink-DevKit
Serial: 115200 baud, 8 data bits, no parity, 1 stop bit
Library: EspSoftwareSerial 8.1.0
The ESP32-C3 is a RISC-V core, not Xtensa. Libraries with hand-written Xtensa
assembly or prebuilt .a files will not link.
ESP32-PICO-KIT-1
Not pin-compatible with the common 30-pin DevKit footprint — check the pinout in the board datasheet before reusing an existing carrier board or breadboard layout.
Official AT firmware
- Published by Espressif, open source.
- UART1 (GPIO16 RX, GPIO17 TX) carries AT commands.
- UART0 (GPIO3 RX, GPIO1 TX) is used for firmware download and log output.
Pin assignments differ per chip and per AT release. Confirm them in the ESP-AT documentation for the exact firmware build you flash.
Flashing
Write factory_WROOM-32.bin at offset 0x0.
SPI speed: 40MHz SPI mode: DIO DoNotChgBin: checked Baud: 115200
The factory image is a complete flash image — bootloader, partition table,
application and NVS in one file — which is why it starts at 0x0 rather than at
the usual 0x10000 application offset.
Partition layout for a 4 MB (0x400000) flash:
+SYSFLASH:"mfg_nvs",1,2,start=0x21000,size=0x1C000 # 0x21000-0x3CFFF
+SYSFLASH:"fatfs",1,129,0x70000,0x90000
First commands
Set the terminal to transmit CR+LF on newline (Tera Term: Setup ▸ Terminal ▸
New-line ▸ Transmit). The AT parser ignores commands that arrive with only
CR or only LF.
AT # expect OK; confirms the link before anything else
AT+GMR # firmware version, useful when behaviour differs from the docs
AT+CWMODE=1 # station mode
AT+CWJAP="SSID","PASSWORD"
AT+CIPSTA? # check the assigned IP address
AT+HTTPGETSIZE="https://example.com/logo.svg"
AT+HTTPCLIENT=2,0,"https://example.com/logo.svg",,,2
For AT+HTTPCLIENT the parameters are, in order: method (2 = GET),
content-type (0 = application/x-www-form-urlencoded), URL, host, path, and
transport type (1 = TCP, 2 = TLS). Host and path may be left empty when the
full URL is given.
HTTPS requests need the server's CA certificate present in the firmware's certificate partition; without it the request fails even though the same URL works over plain HTTP.
Secure OTA with code signing (SignServer)
Signing keeps the private key on the server, so it never reaches a build machine.
- Configure the signer token (PKCS#12) and import it into SignServer.
- Build the plain, unsigned ESP32 application binary.
- Send the binary to SignServer, which hashes it and returns the signature.
- Append the signature to the application image.
- Flash the image to an ESP32 whose bootloader holds the matching public key.
The bootloader rejects the image if the public key does not match, so flash the signed bootloader before the first signed application — otherwise the device refuses to boot and needs recovery over UART.
The client-side script and the SignServer setup are on the Security page.
Environment
MCU ESP32-WROOM-32D
Additional boards manager URLs: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
BOARDS: Nodemcu-32S (ESP32-E DevKit)
Board Manager: esp32 by Espressif Systems
COM port Driver:
CP210x USB to UART Bridge VCP Drivers / Silicon Labs CP210x USB to UART Bridge
Example: Blink
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
ESP32-C3-AWS-EXPRESSLINK-DEVKIT
Baud Rate: 115200, 8, None, 1
EspSoftwareSerial 8.1.0
AT Firmware
factory_WROOM-32.bin @ 0x0
# Flash size 4mb=0x40 0000
+SYSFLASH:"mfg_nvs",1,2,start=0x2 1000,size=0x1 c000, 0x2 1000~0x3 CFFF
+SYSFLASH:"fatfs",1,129,0x7 0000,0x9 0000
SPI: 40MHz, SPI MODE: DIO, DoNotChgBin, BAUD: 115200, Start
Firmware use GPIO16/17 UART1(or 2) RX/TX for AT command.
GPIO1/3 UART0 for download firmware and log monitor.
Tera Term New-Line Transmit: CR+LF
# Https access
AT+CWMODE=1
AT+CWJAP="NAME","PA55W0RD"
# Check IP
AT+CIPSTA?
AT+HTTPGETSIZE="https://bitdove.net/logo/logo_full.svg"
AT+HTTPCLIENT=2,0,"https://bitdove.net/logo/logo_full.svg",,,2
Project
There must be a main .ino code file with the same name as the directory name.
Automatically compile files in the same directory in name order and finally combine them into one file.
c_cpp_properties.json use includePath to include library.
VSCode
{
"sketch": "test.ino",
"configuration": "FlashFreq=80,UploadSpeed=921600",
"board": "esp32:esp32:nodemcu-32s",
"port":"COM7",
"output": "../build"
}