Project Overview: SparkFun ESP32-S3 Thing Plus Qwiic Sensor Node

The SparkFun ESP32-S3 Thing Plus (SKU: DEV-20168) pairs the dual-core Xtensa LX7 processor with native USB and a Qwiic I2C connector, making it an ideal hub for low-power environmental telemetry. This guide walks through building a battery-powered, deep-sleep sensor node that reads temperature, humidity, and pressure, then logs the data before returning to a microamp sleep state.

Board Variant Target: This firmware and pin mapping specifically target the SparkFun ESP32-S3 Thing Plus (8MB Flash, 2MB PSRAM) with the integrated Qwiic connector and LiPo charging circuit. If you are using the older original ESP32 Thing Plus (WRL-15663), the SDA/SCL pins and USB boot sequences will differ.

Bill of Materials

  • Microcontroller: SparkFun ESP32-S3 Thing Plus (DEV-20168)
  • Sensor: SparkFun Qwiic BME280 Breakout (SEN-15440)
  • Interconnect: Qwiic Cable - 100mm (PRT-17260)
  • Power: Lithium Ion Battery - 1000mAh (PRT-13851)
  • Tools: USB-C data cable, solderless breadboard (optional for probing)

Pin Mapping and Hardware Wiring

Because we are using the Qwiic ecosystem, the physical wiring is foolproof. However, understanding the underlying GPIO routing is critical for debugging and writing robust firmware. The S3 Thing Plus routes the primary I2C bus directly to the Qwiic connector and includes an internal voltage divider for battery monitoring.

FunctionGPIO PinHardware Notes
I2C SDA (Qwiic)GPIO 8Pulled up to 3.3V via 4.7kΩ on the mainboard
I2C SCL (Qwiic)GPIO 9Pulled up to 3.3V via 4.7kΩ on the mainboard
Battery Voltage (ADC)GPIO 14Internal 1/3 voltage divider; requires multiplier in code
Native USB D-GPIO 19Used for USB CDC serial and JTAG
Native USB D+GPIO 20Used for USB CDC serial and JTAG
Boot ButtonGPIO 0Active LOW; required for manual bootloader entry

Wiring Steps

  1. Connect the 1000mAh LiPo battery to the 2-pin JST connector on the SparkFun board. Ensure the red wire aligns with the '+' silkscreen.
  2. Plug one end of the 100mm Qwiic cable into the BME280 breakout and the other into the S3 Thing Plus.
  3. Connect the USB-C cable to your PC. Verify it is a data-capable cable, not a charge-only cable.

Complete Firmware: Deep Sleep I2C Telemetry

The following C++ code is written for the Arduino IDE using the ESP32 core (v3.x). It initializes the I2C bus on the correct S3 pins, reads the BME280, calculates the LiPo voltage using the internal divider, prints the telemetry, and triggers deep sleep.

Prerequisites: Install the Adafruit BME280 Library and Adafruit Unified Sensor via the Arduino Library Manager. Select SparkFun ESP32-S3 Thing Plus in the Boards Manager.

#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <esp_sleep.h>

// --- Pin Definitions for SparkFun ESP32-S3 Thing Plus ---
#define I2C_SDA_PIN 8
#define I2C_SCL_PIN 9
#define BATTERY_ADC_PIN 14
#define BME_I2C_ADDR 0x77 // SparkFun BME280 default address

// --- Configuration ---
#define SLEEP_DURATION_US 600000000ULL // 10 minutes in microseconds
#define VBAT_DIVIDER_RATIO 3.0 // Internal divider ratio on Thing Plus

Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow USB CDC time to enumerate on S3 native USB

  // Initialize I2C on specific S3 Qwiic pins
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);

  // Error Handling: Sensor Initialization
  if (!bme.begin(BME_I2C_ADDR, &Wire)) {
    Serial.println("[ERROR] Could not find BME280. Check Qwiic connection.");
    // Go to sleep and try again later rather than hanging
    esp_deep_sleep_start(); 
  }

  Serial.println("--- SparkFun ESP32-S3 Telemetry Wake ---");

  // Read Environmental Data
  float tempC = bme.readTemperature();
  float humidity = bme.readHumidity();
  float pressurePa = bme.readPressure();

  // Read Battery Voltage
  int rawAdc = analogReadMilliVolts(BATTERY_ADC_PIN);
  float batteryVoltage = (rawAdc / 1000.0) * VBAT_DIVIDER_RATIO;

  // Print Telemetry
  Serial.printf("Temp: %.2f C | Hum: %.1f %% | Press: %.0f Pa | VBat: %.2f V\n", 
                tempC, humidity, pressurePa, batteryVoltage);

  Serial.println("Going to deep sleep for 10 minutes...");
  Serial.flush(); // Ensure all serial data is transmitted before sleep

  // Configure and enter Deep Sleep
  esp_sleep_enable_timer_wakeup(SLEEP_DURATION_US);
  esp_deep_sleep_start();
}

void loop() {
  // Loop is never reached in deep sleep implementations
}

Debugging: "Failed to connect to ESP32-S3"

The ESP32-S3 utilizes native USB rather than a dedicated UART-to-USB bridge chip. While this saves board space and enables USB HID/OTG, it introduces a specific failure mode when the board is in deep sleep or crashes before the USB CDC stack initializes.

If you attempt to upload new code while the board is asleep, the Arduino IDE will throw this exact error:

A fatal error occurred: Failed to connect to ESP32-S3: No serial data received.

The First Three Things to Check

When this error halts your upload, execute these three checks in order:

  1. Force Manual Bootloader Mode (The Boot Dance): Because the S3 is asleep, it cannot receive the auto-reset signal over USB. Press and hold the BOOT button (GPIO 0), then press and release the RESET button, and finally release the BOOT button. This forces the ROM bootloader to start and listen for serial data. Click 'Upload' in the IDE immediately after.
  2. Verify USB CDC On Boot: In the Arduino IDE, go to Tools > USB CDC On Boot and ensure it is set to Enabled. If this is disabled, the board will not expose a serial port upon waking, making subsequent uploads impossible without the physical button dance.
  3. Inspect the USB-C Cable: The S3 requires D+ and D- data lines to negotiate the bootloader. Swap your cable for a verified data-sync cable. If the PC only registers a power draw and no COM port, the cable is charge-only.
Pro-Tip for Deep Sleep Development: While actively writing and testing deep sleep code, temporarily reduce your sleep duration to 5 seconds and add a 3-second delay(3000) at the very top of setup(). This gives you a predictable window to hit 'Upload' before the board goes back to sleep, saving you from constantly reaching for the physical BOOT button.

Extending and Simplifying the Build

Depending on your project phase, you may need to alter the power profile of this node.

How to Simplify (Bench Testing Mode)

If you are prototyping the sensor logic and don't want to deal with serial port disconnects, strip out the deep sleep functions. Remove #include <esp_sleep.h>, delete the esp_sleep_enable_timer_wakeup() and esp_deep_sleep_start() calls, and move your sensor reading logic into the loop() function with a standard delay(5000). This keeps the USB CDC connection permanently alive for Serial Plotter debugging.

How to Extend (Production IoT Mode)

To turn this local logger into a remote IoT node:

  • Add WiFi Telemetry: Initialize WiFi.begin(ssid, password) in setup. Use HTTPClient to POST the JSON payload to an MQTT broker or REST API. Use WiFi.disconnect(true) and WiFi.mode(WIFI_OFF) immediately after transmission to prevent the WiFi modem from drawing 120mA during sleep.
  • Add External Wake Triggers: Use esp_sleep_enable_ext0_wakeup(GPIO_NUM_X, 0) to wake the S3 from a physical reed switch or PIR motion sensor, allowing event-driven telemetry rather than strictly time-based polling.

SparkFun ESP32 FAQ

How do I put the SparkFun ESP32 into download mode manually?

To manually enter download (bootloader) mode, you must pull GPIO 0 LOW during a reset cycle. Physically, this means pressing and holding the '0' (BOOT) button on the SparkFun board, tapping the 'RST' button, and then releasing the BOOT button. The Espressif ROM bootloader will then wait for a serial handshake on the native USB pins (GPIO 19/20) according to the official ESP32-S3 hardware reference.

Why is my SparkFun ESP32-S3 not showing up in the Arduino IDE port menu?

The most common cause is that the firmware currently flashed to the board does not initialize the USB CDC stack. If your code crashes before Serial.begin() or if "USB CDC On Boot" was disabled when the code was compiled, the PC will not recognize the device. Perform the manual bootloader button sequence (Hold BOOT -> Press RESET -> Release BOOT) to force the hardware ROM bootloader to expose the port, then re-flash with CDC enabled.

Can I power the SparkFun ESP32 Thing Plus directly from a 5V LiPo pack?

No. The SparkFun ESP32-S3 Thing Plus features an integrated MCP73831 charge controller and an AP2112 3.3V voltage regulator. The LiPo JST connector expects a nominal 3.7V to 4.2V single-cell lithium polymer battery. Applying 5V directly to the battery pins will bypass the charge controller's safety limits, potentially damaging the battery protection circuit or causing a thermal event. If you must use a 5V source, feed it through the USB-C connector, which handles 5V regulation natively. For more details on the power tree, consult the SparkFun S3 Thing Plus Hookup Guide.