If you need WiFi or Bluetooth and have a budget over $5 per node, pick the ESP32-S3 DevKitC-1. If your design requires strict 5V logic, ultra-low microamp sleep currents without a radio, or native USB HID without external hubs, pick the Arduino Nano 33 IoT. For 90% of mains-powered or LiPo-backed environmental IoT builds in 2026, the ESP32-S3 is the undisputed default.
This guide cuts through the marketing fluff. We will compare the exact silicon capabilities of the ESP32-S3-WROOM-1 against the ATSAMD21G18A found in the Nano 33 IoT, map the I2C pins for a BME280 sensor, provide production-ready MQTT code, and debug the most common upload failures you will encounter when searching for the arduino esp core in the Boards Manager.
The Decision Path: Arduino vs ESP32 for IoT Nodes
Do not choose a board based on brand loyalty; choose it based on your power budget and logic level requirements. Use this decision tree to terminate your selection process.
| Project Constraint | If YES... | If NO... |
|---|---|---|
| Do you need native WiFi/BLE? | Pick ESP32-S3. The Nano 33 IoT requires the Nina W102 coprocessor, which adds cost and 120mA active current. | Proceed to next question. |
| Are you interfacing with legacy 5V sensors? | Pick Arduino Nano 33 IoT (SAMD21 is 5V tolerant on select pins, or use a level shifter). The ESP32-S3 is strictly 3.3V and will fry if fed 5V logic. | Proceed to next question. |
| Is the node powered by a CR2032 coin cell? | Pick Arduino Nano 33 IoT. The SAMD21 can hit ~15µA in standby. The ESP32-S3 baseline leakage and RTC domain draw make coin-cell operation impractical without massive duty cycling. | Pick ESP32-S3. |
| Do you need to run local machine learning (TinyML)? | Pick ESP32-S3. It features vector instructions for AI acceleration and dual 240MHz cores. | Either works, but ESP32-S3 is cheaper. |
Hardware Spec Sheet & Parts List
Before wiring, verify you have the exact board variants listed below. Clones with mismatched voltage regulators will cause brownouts during WiFi transmission.
Required Parts
- Microcontroller (Pick One): Espressif ESP32-S3-DevKitC-1 (N8R8 variant) OR Arduino Nano 33 IoT (ABX00027).
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652). Avoid the cheaper BMP280 if you need humidity.
- Power: 3.7V 2000mAh 18650 LiPo cell with a JST-PH 2.0 connector (for ESP32).
- Passives: 2x 4.7kΩ pull-up resistors (for I2C bus stabilization on long runs).
| Specification | ESP32-S3 DevKitC-1 | Arduino Nano 33 IoT |
|---|---|---|
| Core Processor | Xtensa LX7 Dual-Core @ 240MHz | ARM Cortex-M0+ (SAMD21) @ 48MHz |
| Logic Voltage | 3.3V (Strict) | 3.3V (5V tolerant on specific I/O) |
| Active WiFi Current | ~110mA (avg), peaks to 350mA TX | ~120mA (via Nina W102 coprocessor) |
| Deep Sleep Current | ~7µA (with ULP coprocessor active) | ~15µA (SAMD21 standby, radio off) |
| Native USB | Yes (USB-OTG + internal JTAG) | Yes (USB-OTG) |
Pin Mapping: Wiring the BME280 I2C Sensor
The BME280 uses I2C. While both boards support I2C, the default hardware pins differ. Furthermore, the ESP32-S3's internal pull-up resistors are often too weak (~45kΩ) to overcome the parasitic capacitance of breadboard tracks at 400kHz I2C speeds. Always use external 4.7kΩ pull-ups to 3.3V for reliable telemetry.
| BME280 Pin | ESP32-S3 DevKitC-1 Pin | Arduino Nano 33 IoT Pin |
|---|---|---|
| VIN / VCC | 3V3 | 3V3 |
| GND | GND | GND |
| SDI / SDA | GPIO 8 | A4 (SDA) |
| SCK / SCL | GPIO 9 | A5 (SCL) |
Complete MQTT Telemetry Code (Targets ESP32-S3)
The following code targets the ESP32-S3 DevKitC-1. It connects to WiFi, initializes the I2C bus on the custom GPIO 8/9 pins, reads the BME280, and publishes JSON-formatted telemetry to an MQTT broker. It includes robust error handling for sensor initialization and network reconnection.
Prerequisites: Install the ESP32 board package (v3.0.x or newer) via the Boards Manager, and install the PubSubClient and Adafruit BME280 libraries via the Library Manager.
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
// --- PIN DEFINITIONS (ESP32-S3 DevKitC-1) ---
#define I2C_SDA 8
#define I2C_SCL 9
#define STATUS_LED 48 // Built-in WS2812 or standard LED on most S3 devkits
// --- NETWORK & MQTT CONFIG ---
const char* ssid = "YourNetworkSSID";
const char* password = "YourNetworkPassword";
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;
const char* mqtt_topic = "sensor/bme280/node01";
WiFiClient espClient;
PubSubClient client(espClient);
Adafruit_BME280 bme;
unsigned long lastMsg = 0;
const long INTERVAL = 10000; // 10 seconds
void setup_wifi() {
delay(10);
Serial.println("\nConnecting to WiFi...");
// Pro-Tip: Reduce TX power to save battery and reduce heat on compact nodes
WiFi.setTxPower(WIFI_POWER_8_5dBm);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi connection failed. Rebooting...");
ESP.restart();
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32S3-Node01-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retry in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
while (!Serial) delay(10); // Wait for serial monitor
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, HIGH);
// Initialize I2C on custom pins for ESP32-S3
Wire.begin(I2C_SDA, I2C_SCL);
// Sensor Initialization with Error Handling
if (!bme.begin(0x77, &Wire)) { // Try 0x77, fallback to 0x76 if needed
if (!bme.begin(0x76, &Wire)) {
Serial.println("FATAL: Could not find a valid BME280 sensor, check wiring!");
while (1) {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
delay(100); // Fast blink indicates hardware fault
}
}
}
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > INTERVAL) {
lastMsg = now;
float temp = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
// Construct simple JSON payload
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temp\":%.2f,\"hum\":%.2f,\"pres\":%.2f}",
temp, humidity, pressure);
Serial.print("Publishing: ");
Serial.println(payload);
client.publish(mqtt_topic, payload);
// Blink LED to confirm TX
digitalWrite(STATUS_LED, LOW);
delay(50);
digitalWrite(STATUS_LED, HIGH);
}
}
Debugging the "Failed to Connect" Upload Error
The ESP32-S3 uses an internal USB-JTAG interface, which behaves differently than the old CP2102/CH340 serial chips on original ESP32 boards. If you hit an upload failure, do not blindly reinstall the IDE.
The Exact Error String:
A fatal error occurred: Failed to connect to ESP32-S3: No serial data received.
The First Three Things to Check
- Cable Integrity: Verify your USB-C cable supports data transfer. Over 60% of USB-C cables shipped with cheap electronics are charge-only (missing the D+ and D- lines). Test the cable by transferring a file from a smartphone to a PC.
- USB-JTAG Driver Binding (Windows Only): If Windows assigned the generic FTDI or CDC driver to the S3's internal JTAG, the Arduino IDE cannot handshake. Download Zadig, select the "USB JTAG/serial debug unit" interface, and replace the driver with WinUSB.
- Manual Boot Mode Entry: The auto-reset circuit on some S3 clone boards fails to pull GPIO0 low during boot. You must force it manually using the physical buttons on the DevKit.
How to Force Manual Boot Mode (The S3 Sequence)
If the auto-reset fails, follow this exact physical sequence on the ESP32-S3 DevKitC-1:
- Press and hold the
BOOTbutton (this pulls GPIO0 low). - While holding BOOT, press and release the
RST(Reset) button. - Release the
BOOTbutton. - Click "Upload" in the Arduino IDE immediately.
- Once the IDE says "Hard resetting via RTS pin...", press the
RSTbutton one more time to boot into the newly flashed application.
Extending and Simplifying the Build
Once your baseline telemetry is flowing, you will likely need to optimize for power or network topology. Here is how to adapt the architecture without rewriting the core logic.
How to Simplify: Drop MQTT for ESP-NOW
If your sensor node is in a Faraday cage (like a steel greenhouse or a concrete basement) and your WiFi router drops the connection constantly, abandon WiFi entirely. Use ESP-NOW. ESP-NOW is a connectionless, low-latency protocol native to the ESP32 silicon. It allows the S3 to beam sensor data directly to a central ESP32 "gateway" node in under 5 milliseconds, without needing a router, DHCP, or an MQTT broker. This slashes your active TX time from ~2 seconds (WiFi handshake + MQTT) to ~15 milliseconds, radically extending LiPo battery life.
How to Extend: Implement Deep Sleep
The provided code uses delay(), which keeps the CPU active and draws ~45mA continuously. To extend a 2000mAh 18650 cell from 2 days to 2 months, implement deep sleep.
- Replace the
delay(INTERVAL)logic with the ESP32 ULP (Ultra-Low Power) timer. - Add
#include <esp_sleep.h>at the top of your sketch. - At the end of your
loop()function, after the MQTT publish confirms, call:esp_sleep_enable_timer_wakeup(INTERVAL * 1000000ULL); Serial.flush(); esp_deep_sleep_start(); - Hardware Note: Ensure your BME280 breakout board does not have a permanent power LED soldered to it. A single 3mm LED will draw 5mA continuously, completely defeating the 7µA deep sleep current of the ESP32-S3. Desolder the LED or cut the trace if necessary.
For further reading on the silicon capabilities of these boards, consult the official Espressif ESP32-S3 Datasheet for exact current draw matrices, and the Arduino Nano 33 IoT documentation for SAMD21 pin multiplexing constraints. For MQTT payload sizing, review the PubSubClient API limits to ensure your JSON strings do not exceed the default 256-byte buffer.






