LOG ENTRY
"ESP32-S3-N16R8 First Bring-Up: GPIO38 RGB LED, Arduino IDE, and Upload Debugging"
ESP32-S3-N16R8 First Bring-Up: GPIO38 RGB LED, Arduino IDE, and Upload Debugging

Context
This was my first proper bring-up session with two ESP32-S3 boards. The board/module in use is an ESP32-S3-N16R8 type setup:
N16= 16 MB flashR8= 8 MB PSRAM- Arduino IDE used for programming
- Onboard RGB LED labelled as
RGB@IO38 - Main board target: ESP32-S3 DevKitC-style board
The immediate goal was simple:
Upload code, control the onboard RGB LED, understand what the Arduino C++ syntax means, and recover the board when upload/debugging breaks.
This ended up covering more than just LED blinking. It also clarified GPIO naming, constexpr, #define, pinMode(), baud rate, Serial Monitor, Arduino upload mode, debugger mode, and ESP32-S3 boot recovery.
Hardware observed
The board pinout image showed:
RGB@IO38
GPIO38 ... RGB_LED
So for this board, the onboard RGB LED signal pin is treated as:
constexpr int RGB_LED = 38;
There was also confusion around whether some ESP32-S3 boards use GPIO48. That can happen on some board revisions, but for this board image and label, GPIO38 is the correct working assumption.
Arduino IDE board setup used
The practical Arduino IDE setup for an ESP32-S3-N16R8 board is:
Board: ESP32S3 Dev Module
Flash Size: 16MB
PSRAM: OPI PSRAM
USB CDC On Boot: Enabled
USB Mode: Hardware CDC and JTAG
Upload Speed: 115200 or 460800 while debugging upload issues
Notes:
921600can work, but it is less forgiving.- If upload is unstable, reduce upload speed first.
- If Serial Monitor is empty, check
USB CDC On Boot. - If using USB-Serial/JTAG, debug mode can interfere with normal beginner workflow.
Concept: what 3V3 means
3V3 means the 3.3 V power rail.
On ESP32 boards:
3V3 = 3.3 volt power
GND = ground / 0 V
GPIO = signal pin
Typical wiring idea:
ESP32 3V3 -> sensor VCC
ESP32 GND -> sensor GND
ESP32 GPIO -> sensor signal
Important:
Do not connect 3V3 directly to GND.
ESP32 GPIO is 3.3 V logic.
Do not feed 5 V directly into ESP32 GPIO.
Concept: constexpr int RGB_LED = 38;
This line:
constexpr int RGB_LED = 38;
means:
Create a fixed integer named RGB_LED.
Its value is 38.
It cannot change while the program runs.
It does not control the LED by itself.
It only gives GPIO 38 a readable name.
So this:
neopixelWrite(RGB_LED, 255, 0, 0);
means the same as:
neopixelWrite(38, 255, 0, 0);
But the named version is better because it explains the purpose of the pin.
Bad style:
neopixelWrite(38, 255, 0, 0);
Better style:
neopixelWrite(RGB_LED, 255, 0, 0);
If the pin changes later, only this line needs changing:
constexpr int RGB_LED = 48;
Concept: #define RGB_LED 38
This:
#define RGB_LED 38
is a preprocessor replacement rule.
It means every RGB_LED is replaced with 38 before compilation.
It is not a normal C++ variable.
For Arduino C++, prefer:
constexpr int RGB_LED = 38;
Use this rule:
constexpr int NAME = VALUE;
Examples:
constexpr int RGB_LED = 38;
constexpr int DELAY_MS = 1000;
constexpr int BAUD_RATE = 115200;
Use normal int only when the value changes:
int count = 0;
Concept: count must not be constexpr
This is wrong for a counter:
constexpr int count = 0;
because constexpr means fixed.
This will fail:
count++;
Correct:
int count = 0;
because a counter changes during runtime.
Concept: boolean readability
This is valid:
if (stopped == true) {
return;
}
This is also valid and more common C++ style:
if (stopped) {
return;
}
For learning, the explicit version is fine:
if (stopped == true) {
return;
}
It reads directly as:
If stopped is true, exit this loop call.
Important Arduino loop behaviour
This does not stop the Arduino forever:
return;
Inside loop(), return; only exits the current call to loop().
Arduino then calls loop() again.
So to stop repeated behaviour, use a state flag:
bool stopped = false;
Then:
if (stopped == true) {
return;
}
And later:
stopped = true;
First intended blink logic
The intended logic was:
Start
If stopped, do nothing
Turn RGB LED red
Wait 1 second
Turn RGB LED off
Wait 1 second
Increment count
If count reaches 10:
turn LED off
print "stopped"
set stopped = true
The code:
constexpr int RGB_LED = 38;
constexpr int DELAY_MS = 1000;
int count = 0;
bool stopped = false;
void setup() {
Serial.begin(115200);
}
void loop() {
if (stopped == true) {
return;
}
neopixelWrite(RGB_LED, 255, 0, 0);
delay(DELAY_MS);
neopixelWrite(RGB_LED, 0, 0, 0);
delay(DELAY_MS);
count++;
if (count >= 10) {
neopixelWrite(RGB_LED, 0, 0, 0);
Serial.println("stopped");
stopped = true;
}
}
This logic is correct.
The issue later was not the counter logic. It was upload/debugger/LED-driver behaviour.
Serial baud rate
This line:
Serial.begin(115200);
sets the Serial communication speed.
It means:
115200 bits per second
The Serial Monitor must match the same rate.
Example:
Serial.begin(921600);
requires Serial Monitor to be set to:
921600 baud
For beginner debugging, 115200 is simpler and more standard.
Baud rate is not polling rate
Baud rate is the data transfer speed.
Polling rate is how often something is checked.
Example:
Serial.begin(115200);
means Serial sends data at around 115200 bits per second.
This:
int buttonState = digitalRead(4);
delay(10);
polls roughly every 10 ms, or about 100 times per second.
Simple mental model:
baud rate = how fast data moves
polling rate = how often you check something
Debug mode problem
At one point, Arduino IDE was in debug mode.
The output showed:
Thread 2 "loopTask" hit Temporary breakpoint 1, setup()
Serial.begin(115200);
That meant the ESP32 was paused by the debugger at the start of setup().
So the sketch had not reached loop() yet.
The logs also showed:
OpenOCD
GDB
Target halted
Detected FreeRTOS
That confirmed it was debugging, not normal upload/run.
For beginner LED testing, normal upload is better:
Sketch -> Upload
or:
Cmd + U
Avoid pressing Debug unless actually debugging with breakpoints.
Normal upload evidence
A normal Arduino upload showed:
esptool v5.2.0
Writing at 0x...
Hash of data verified.
Hard resetting via RTS pin...
That means the sketch uploaded successfully.
A debug session shows things like:
openocd
gdb-server
Target halted
That is not the same thing.
Upload failure encountered
One failure was:
A fatal error occurred: Failed to connect to ESP32-S3: No serial data received.
Another was:
A fatal error occurred: The chip stopped responding.
These are upload/connection/bootloader problems, not sketch logic problems.
The recovery that worked:
Hold BOOT
Press RESET/EN
Release RESET/EN
Then release BOOT
Upload again
After doing this, the board reset itself properly and upload worked again.
Working LED-off test
This code successfully turned the onboard RGB light off:
constexpr int RGB_LED = 38;
void setup() {
Serial.begin(921600);
pinMode(RGB_LED, OUTPUT);
}
void loop() {
digitalWrite(RGB_LED, LOW);
}
This proved that GPIO38 could affect the onboard LED line.
Important distinction:
digitalWrite(GPIO38, LOW) can force the signal line low and keep the LED off.
It is not proper RGB colour control.
For proper RGB colour control, the likely route is still:
neopixelWrite(RGB_LED, red, green, blue);
or a NeoPixel-compatible library.
But that will be investigated later.
Current confirmed state
Confirmed:
- The ESP32-S3 board can be uploaded to using Arduino IDE.
- BOOT + RESET/EN recovery works when upload fails.
- Debug mode was causing confusion because the chip paused at
setup(). - Normal upload works through
esptool. - GPIO38 matches the board’s
RGB@IO38marking. digitalWrite(RGB_LED, LOW)successfully turned the onboard RGB light off.constexpr int RGB_LED = 38;is the preferred way to name the LED pin.int count = 0;is correct for a changing counter.bool stopped = false;is correct for stopping the loop behaviour.- Serial baud must match the Serial Monitor baud.
Not fully solved yet:
- Proper colour blinking using
neopixelWrite()or a NeoPixel driver. - Whether Arduino board settings or RGB protocol handling affected the colour test.
Final notes for next session
Next debugging step:
- Keep board target as
ESP32S3 Dev Module. - Keep
RGB_LED = 38. - Use normal Upload, not Debug.
- Confirm Serial Monitor output first.
- Test proper RGB control with either:
neopixelWrite(RGB_LED, r, g, b), orAdafruit_NeoPixel
- Compare behaviour between:
digitalWrite(RGB_LED, LOW)neopixelWrite(RGB_LED, 0, 0, 0)neopixelWrite(RGB_LED, 255, 0, 0)
The important conclusion is that the board is not dead. The upload path works, GPIO38 can affect the LED, and the remaining problem is just correct RGB control.