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.
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.
| Function | GPIO Pin | Hardware Notes |
|---|---|---|
| I2C SDA (Qwiic) | GPIO 8 | Pulled up to 3.3V via 4.7kΩ on the mainboard |
| I2C SCL (Qwiic) | GPIO 9 | Pulled up to 3.3V via 4.7kΩ on the mainboard |
| Battery Voltage (ADC) | GPIO 14 | Internal 1/3 voltage divider; requires multiplier in code |
| Native USB D- | GPIO 19 | Used for USB CDC serial and JTAG |
| Native USB D+ | GPIO 20 | Used for USB CDC serial and JTAG |
| Boot Button | GPIO 0 | Active LOW; required for manual bootloader entry |
Wiring Steps
- Connect the 1000mAh LiPo battery to the 2-pin JST connector on the SparkFun board. Ensure the red wire aligns with the '+' silkscreen.
- Plug one end of the 100mm Qwiic cable into the BME280 breakout and the other into the S3 Thing Plus.
- 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:
- 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.
- 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.
- 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.
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. UseHTTPClientto POST the JSON payload to an MQTT broker or REST API. UseWiFi.disconnect(true)andWiFi.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.






