# Arduino and ESP32 projects to 3D print, with parts lists, wiring and code

> Sixteen builds with a printed enclosure: ten click-together ESP32 gadgets you can flash from the browser, and six wired projects with a full parts list, a wiring diagram, working code and print settings.

Page: https://printpal.io/guides/arduino-esp32-projects-to-3d-print
Updated: 2026-11-12
Vendor facts checked: November 2026
Reading time: 14 min

## Key takeaways

- Start with a **click-together build**: an ESP32 board with a built-in screen, a printed case and firmware you flash from Chrome. No soldering, about 45 minutes, around $20.
- For your first wired project, an **Arduino Uno R4** is the forgiving choice: 5 V logic matches most hobby sensors. Move to an **ESP32** for Wi-Fi and an **ESP32-S3** for native USB or a screen.
- **ESP32 pins are 3.3 V.** They tolerate 3.6 V at most, ADC2 pins stop working when Wi-Fi is on, and GPIO6 to 11 belong to the flash chip.
- Budget **power** first: 16 NeoPixels at full white draw 960 mA, almost twice what a USB 2.0 port supplies. Cap the current in code and add a capacitor.
- Match the **filament to the place**: PLA indoors, PETG near water, ASA in the sun. Measure every module with calipers before you design its case.

## The short answer

> **Short answer:** The best first Arduino or ESP32 project with a 3D-printed case is one that needs **no wiring**: a board with a built-in screen dropped into a printed stand, flashed from the browser. Then build a **weather station** (two modules, four wires) and a **NeoPixel lamp** (your first power budget). Every project below has a parts list, wiring, code and print settings.

**The projects in this guide**

| Project | Board | Level | Soldering | Wi-Fi | Parts |
|---|---|---|---|---|---|
| [10 click-together builds](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#no-solder) | Waveshare ESP32-C6 or S3 screen boards | Beginner | No | Some | $18–32 |
| [Weather station](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#weather-station) | ESP32 | Beginner | No | No | $20–35 |
| [NeoPixel lamp](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#led-lamp) | ESP32 | Beginner | Yes | No | $20–30 |
| [Parking sensor](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#parking-sensor) | Arduino Uno R4 Minima | Beginner | No | No | $30–35 |
| [Plant soil monitor](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#soil-monitor) | ESP32 | Intermediate | No | Yes | $20–25 |
| [Useless box](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#useless-box) | ESP32 | Intermediate | Optional | No | $20–25 |
| [USB macro pad](https://printpal.io/guides/arduino-esp32-projects-to-3d-print#macro-pad) | ESP32-S3 | Intermediate | Or hot-swap | No | $25–35 |

*Parts costs are our estimates from board list prices plus typical module prices in November 2026. Build-guide costs are from each PrintPal guide.*

## Start here: ESP32 builds that click together

These PrintPal build guides use a single board with the screen already on it, so there is nothing to wire. Print the case, press the board in, and flash the program from Chrome or Edge.

Two boards cover all ten: the [Waveshare ESP32-C6-LCD-1.47](https://www.waveshare.com/wiki/ESP32-C6-LCD-1.47) (a 172 × 320 rectangular screen, Wi-Fi 6) and the [Waveshare ESP32-S3-Touch-LCD-1.28](https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-1.28) (a round 240 × 240 touch screen with a motion sensor). Each guide has the printable files, a parts list, step-by-step photos, the source code and a **Flash to board** button.

- [Slab eyes](https://printpal.io/projects/slab-eyes) (Beginner, 45 min, $18, ESP32-C6 slab): Cartoon eyes in a tiny printed case that look around, blink, wink and doze off on their own.
- [Lucky slab slot machine](https://printpal.io/projects/lucky-slab) (Beginner, 45 min, $18, ESP32-C6 slab): A hand-sized slot machine with a real printed lever and three neon reels.
- [Split-flap departure slab](https://printpal.io/projects/split-flap-board) (Beginner, 45 min, $20, ESP32-C6 slab): A desk-sized airport departures board where flights click in letter by letter.
- [DVD screensaver slab](https://printpal.io/projects/gif-player) (Beginner, 45 min, $20, ESP32-C6 slab): The bouncing DVD logo plus a running dino and a space invader, in a printed stand.
- [Music Play Pal](https://printpal.io/projects/music-play-pal) (Beginner, 30 min, $20, ESP32-C6 slab): Shows the song playing on your PC with the album cover spinning like a record. One USB-C cable, no Wi-Fi.
- [Desk command slab](https://printpal.io/projects/desk-command-slab) (Intermediate, 1h 10m, $20, ESP32-C6 slab): Three pages on a tiny desk brick: a clock, today's weather, and a message you type on your phone.
- [Desk Pal buddy](https://printpal.io/projects/desk-pal-buddy) (Beginner, 45 min, $32, ESP32-S3 round touch): A desk pet with big eyes and real moods that reacts to taps, pets and shakes, and reminds you to drink water.
- [Desk Pal fortune 8-ball](https://printpal.io/projects/fortune-8ball) (Beginner, 45 min, $30, ESP32-S3 round touch): A printed magic 8-ball: ask a question, shake or tap, and an answer swirls up.
- [Desk Pal touch doodle](https://printpal.io/projects/touch-doodle) (Beginner, 40 min, $30, ESP32-S3 round touch): A round finger-painting pad: draw with a fingertip, hold still to change ink, shake to erase.
- [Desk Pal plane radar](https://printpal.io/projects/plane-radar) (Intermediate, 1h 30m, $32, ESP32-S3 round touch): A radar scope that shows real planes flying near your home, with their callsigns.

> **Flashing from the browser** The [ESP32 web flasher](https://printpal.io/tools/esp32-web-flasher) writes firmware from Chrome or Edge over USB, with nothing to install. Use a USB-C **data** cable: charge-only cables are the most common reason no port shows up.

## Choosing a board

> **Rule of thumb:** Pick an **Arduino Uno R4** for 5 V sensors and first projects, a **classic ESP32 dev board** when you need Wi-Fi on a budget, and an **ESP32-S3** for native USB (keyboards, macro pads) or a screen. Every ESP32 runs its pins at 3.3 V.

**Boards for 3D-printed electronics projects (prices checked November 12, 2026)**

| Board | Chip | Logic | Wireless | USB | Price |
|---|---|---|---|---|---|
| Arduino Uno R4 Minima | Renesas RA4M1, 48 MHz | 5 V | None | USB-C | $20 list |
| Arduino Uno R4 WiFi | RA4M1 plus ESP32-S3 | 5 V | Wi-Fi, Bluetooth | USB-C | $27.50 list |
| Arduino Nano ESP32 | ESP32-S3 (u-blox NORA-W106) | 3.3 V | Wi-Fi, Bluetooth | USB-C | $18.30 list |
| ESP32-DevKitC-32E | ESP32 | 3.3 V | Wi-Fi, Bluetooth 4.2 | Micro-USB | $15 (Adafruit) |
| ESP32-S3-DevKitC-1 | ESP32-S3 | 3.3 V | Wi-Fi, BLE | Native USB plus UART port | $19.95 (Adafruit, N8R8) |
| ESP32-C6-DevKitC-1 | ESP32-C6 | 3.3 V | Wi-Fi 6, BLE 5, Zigbee, Thread | Two USB-C | $9.95 (Adafruit) |
| Seeed XIAO ESP32-S3 | ESP32-S3, 8 MB PSRAM | 3.3 V | Wi-Fi, BLE 5 | USB-C | $7.49 |
| Seeed XIAO ESP32-C6 | ESP32-C6 | 3.3 V | Wi-Fi 6, Zigbee, Thread | USB-C | $5.20 |
| Raspberry Pi Pico 2 W | RP2350, 150 MHz | 3.3 V | Wi-Fi, Bluetooth 5.2 | Micro-USB | $7 at launch |
| Waveshare ESP32-S3-Touch-LCD-1.28 | ESP32-S3, round touch screen, IMU | 3.3 V | Wi-Fi, BLE 5 | USB-C | $29.99 (Amazon, sold by Waveshare) |
| Waveshare ESP32-C6-LCD-1.47 | ESP32-C6, 172 × 320 screen | 3.3 V | Wi-Fi 6, Bluetooth 5 | USB-C | $19.20 (Amazon, third-party listing) |

*Arduino's US store showed sale prices on November 12, 2026; we list the regular prices. Espressif boards are often out of stock at Adafruit, and other distributors carry them.*

## Six wired projects, with code

Each project lists every part, shows the wiring as a diagram and a table, and gives a complete Arduino sketch with the libraries and board settings it needs. Use the chips to filter.

### ESP32 weather station with a BME280 and an OLED

Temperature, humidity and air pressure on a 0.96-inch screen, updated every two seconds. Both modules share the same two I2C wires.

- **Level:** Beginner
- **Time:** 1 hour + printing
- **Parts cost:** $20–35
- **Board:** ESP32-DevKitC-32E or any ESP32 dev board

What you learn:

- How I2C lets two modules share two wires, each with its own address
- Reading a sensor library and printing formatted numbers
- Why a sensor sitting next to the ESP32 reads warm

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | ESP32 dev board | The brain; 3.3 V logic and Wi-Fi if you log data later |
| 1 | BME280 breakout, I2C | Temperature ±1 °C, humidity ±3%, pressure ±1 hPa. Not a BMP280, which has no humidity |
| 1 | SSD1306 OLED, 0.96", 128 × 64, I2C | About 20 mA; address 0x3C (or 0x3D by jumper) |
| 1 | Breadboard and 8 jumper wires | No soldering if the modules come with headers fitted |
| 1 | USB data cable | Power and upload |
| 30 g | PETG or ASA filament | For an outdoor case; PLA softens in the sun |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| ESP32 DevKitC 3V3 | BME280 VIN | Power | 3.3 V, not 5 V |
| ESP32 DevKitC GND | BME280 GND | Ground |  |
| ESP32 DevKitC GPIO22 | BME280 SCL | Signal | I2C clock |
| ESP32 DevKitC GPIO21 | BME280 SDA | Signal | I2C data |
| ESP32 DevKitC 3V3 | OLED VCC | Power |  |
| ESP32 DevKitC GND | OLED GND | Ground |  |
| ESP32 DevKitC GPIO22 | OLED SCL | Signal | Same clock line |
| ESP32 DevKitC GPIO21 | OLED SDA | Signal | Same data line |

- Both modules hang off the same SDA and SCL lines; their addresses keep them apart.
- Breakout boards usually include the pull-up resistors I2C needs, so you add none.

#### Code

Libraries: **Adafruit BME280**, **Adafruit SSD1306** and **Adafruit GFX** from the Library Manager. Board: **ESP32 Dev Module**.

```cpp
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BME280.h>

Adafruit_SSD1306 oled(128, 64, &Wire, -1);   // 128 x 64 pixels, no reset pin
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);                          // SDA, SCL: the ESP32 defaults
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED not found at 0x3C");
    while (true) delay(1000);
  }
  if (!bme.begin(0x76) && !bme.begin(0x77)) {  // breakouts use one or the other
    Serial.println("BME280 not found at 0x76 or 0x77");
    while (true) delay(1000);
  }
  oled.setTextColor(SSD1306_WHITE);
}

void loop() {
  float t = bme.readTemperature();             // degrees C
  float h = bme.readHumidity();                // % relative humidity
  float p = bme.readPressure() / 100.0F;       // Pa to hPa

  oled.clearDisplay();
  oled.setTextSize(2);
  oled.setCursor(0, 0);
  oled.printf("%.1f C\n", t);
  oled.setTextSize(1);
  oled.setCursor(0, 28);
  oled.printf("Humidity %.0f %%\n", h);
  oled.printf("Pressure %.0f hPa\n", p);
  oled.display();

  Serial.printf("%.2f C  %.1f %%  %.1f hPa\n", t, h, p);
  delay(2000);
}
```

#### Build steps

1. **Install the board package.** In Arduino IDE, open Boards Manager, install **esp32** by Espressif Systems and choose **ESP32 Dev Module**.
2. **Wire it on a breadboard.** Follow the table. Both modules take power from 3V3.
3. **Upload and open the Serial Monitor.** At 115200 baud. "Not found" usually means SDA and SCL are swapped.
4. **Check it reacts.** Breathe on the sensor: humidity should jump within seconds.
5. **Move the sensor away from the board.** Mount the BME280 at the end of a short cable or in its own vented chamber; the ESP32 warms the air around it by a few degrees.

**Print settings:** PETG or ASA, 0.2 mm layers, 3 walls, 20% infill. Print louvred rings flat, with no supports.

#### The printed enclosure

A Stevenson-screen style case: stacked louvred rings that let air through but keep sun and rain off the sensor, with the ESP32 and OLED in a separate box below. OLED module sizes vary between makers, so measure yours with calipers before designing the window. The [CAD agent](https://printpal.io/cad-agent) can draft both parts from a description with your measurements.

- If `bme.begin()` fails at both addresses you may have a BMP280: it reports chip ID 0x58 where a BME280 reports 0x60.

### NeoPixel desk lamp with a vase-mode shade

A ring of WS2812B LEDs under a printed shade, with warm white, rainbow and candle modes on one button. The code caps the current so a USB charger can run it safely.

- **Level:** Beginner
- **Time:** 1 hour + printing
- **Parts cost:** $20–30
- **Board:** ESP32-DevKitC-32E

What you learn:

- Why addressable LEDs need a power budget
- Protecting LEDs with a resistor and a capacitor
- Modes and a debounced button

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | ESP32 dev board | Drives the LEDs with FastLED; its BOOT button switches modes |
| 1 | WS2812B ring, 12 or 16 LEDs | Up to 60 mA per LED at full white: 960 mA for 16 |
| 1 | 330 Ω resistor | In the data line, close to the ring, to protect the first LED |
| 1 | 1000 µF capacitor, 6.3 V or higher | Across 5V and GND at the ring, to soak up the switch-on surge |
| 1 | 74AHCT125 level shifter (optional) | Lifts the 3.3 V data to 5 V if the first LED glitches |
| 1 | USB charger, 5 V | The code limits the LEDs to 450 mA |
| 40 g | White or natural PLA | For the shade; thin translucent walls diffuse best |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| ESP32 DevKitC GPIO16 | Resistor IN | Signal | Data from the ESP32 |
| Resistor OUT | LED ring DIN | Signal | Into the first LED |
| ESP32 DevKitC 5V | LED ring 5V | Power | From USB, through the board |
| ESP32 DevKitC GND | LED ring GND | Ground | A shared ground is required |
| Capacitor + | LED ring 5V | Power | Long leg to 5V |
| Capacitor - | LED ring GND | Ground | Striped side to GND |

- Power the ring from 5V, never from 3V3, and always join the grounds.
- The ESP32 sends 3.3 V data and WS2812B LEDs expect close to 5 V. It often works anyway, but Adafruit recommends a 74AHCT125 for reliable data.

#### Code

Library: **FastLED**. `setMaxPowerInVoltsAndMilliamps` dims everything just enough to stay under 450 mA.

```cpp
#include <FastLED.h>

#define DATA_PIN   16
#define NUM_LEDS   16
#define BUTTON_PIN 0        // the BOOT button on most ESP32 dev boards

CRGB leds[NUM_LEDS];
uint8_t mode = 0;           // 0 warm white, 1 rainbow, 2 candle
uint8_t hue = 0;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setMaxPowerInVoltsAndMilliamps(5, 450);   // stay inside what USB can give
  FastLED.setBrightness(160);
}

void loop() {
  static bool wasDown = false;
  bool down = digitalRead(BUTTON_PIN) == LOW;
  if (down && !wasDown) {
    mode = (mode + 1) % 3;
    delay(30);                                      // crude debounce
  }
  wasDown = down;

  if (mode == 0) {
    fill_solid(leds, NUM_LEDS, CRGB(255, 147, 41)); // warm white, like a candle bulb
  } else if (mode == 1) {
    fill_rainbow(leds, NUM_LEDS, hue++, 255 / NUM_LEDS);
  } else {
    for (int i = 0; i < NUM_LEDS; i++) {
      leds[i] = CRGB(255, 120, 20);
      leds[i].nscale8(random8(120, 255));           // each LED flickers on its own
    }
  }
  FastLED.show();
  delay(mode == 2 ? 60 : 20);
}
```

#### Build steps

1. **Solder three wires to the ring.** To 5V, GND and DIN. Put the resistor in the data wire, at the ring end.
2. **Add the capacitor.** Across 5V and GND at the ring. The striped side is negative.
3. **Upload.** Install FastLED, choose ESP32 Dev Module and upload. The ring glows warm white.
4. **Press BOOT to change mode.** Warm white, rolling rainbow, candle flicker.
5. **Print the shade.** Make one with the vase generator and print it in spiral vase mode.

**Print settings:** Shade: spiral vase mode with a 0.6 to 0.8 mm line width. Base: PLA, 0.2 mm, 3 walls, 15% infill.

#### The printed enclosure

Two parts: a round base that holds the ring face up with the board underneath, and a translucent shade printed in vase mode. Design the shade with the [vase generator](https://printpal.io/tools/vase-generator). Ring sizes differ between makers, so measure yours with calipers. Want no wiring at all? The [LED lamp maker](https://printpal.io/tools/led-lamp-maker) designs shades for Bambu Lab's LED Lamp Kit.

**Current for a 16-LED WS2812B ring**

| Item | Value |
|---|---|
| 16 LEDs at full white | 960 mA |
| One USB 2.0 port | 500 mA |
| This sketch's limit | 450 mA |

*Adafruit gives up to 60 mA per LED at full-brightness white. FastLED scales brightness down to stay under the limit you set.*

### Garage parking sensor with an HC-SR04

An ultrasonic sensor on the garage wall beeps faster as the car gets closer, then holds a solid tone and a red light when it is time to stop.

- **Level:** Beginner
- **Time:** 1 hour + printing
- **Parts cost:** $30–35
- **Board:** Arduino Uno R4 Minima

What you learn:

- Timing an echo to measure distance
- Mapping one range of numbers onto another
- Why pin current limits matter

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | Arduino Uno R4 Minima | 5 V logic, so the HC-SR04 connects directly; $20 list |
| 1 | HC-SR04 ultrasonic sensor | 5 V, measures 2 cm to 4 m; most reliable from 10 to 250 cm |
| 1 | Passive piezo buzzer | `tone()` sets the pitch; an active buzzer beeps at one pitch only |
| 1 | Red LED | The stop light |
| 1 | 1 kΩ resistor | About 3 mA for the LED, well under the R4's 8 mA per pin |
| 1 | Breadboard and jumper wires |  |
| 1 | USB-C cable and 5 V charger | Power in the garage |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| Uno R4 Minima 5V | HC-SR04 VCC | Power |  |
| Uno R4 Minima GND | HC-SR04 GND | Ground |  |
| Uno R4 Minima D9 | HC-SR04 TRIG | Signal | A 10 µs pulse starts a reading |
| Uno R4 Minima D10 | HC-SR04 ECHO | Signal | A 5 V pulse: fine on an Uno |
| Uno R4 Minima D6 | Resistor A | Signal |  |
| Resistor B | LED + | Signal | Long leg |
| Uno R4 Minima GND | LED - | Ground | Short leg |
| Uno R4 Minima D5 | Buzzer + | PWM / signal | tone() output |
| Uno R4 Minima GND | Buzzer - | Ground |  |

- Using an ESP32 instead? Its pins take 3.6 V at most, so divide the ECHO signal: Adafruit uses two 10 kΩ resistors to bring 5 V down to 2.5 V.
- A magnetic buzzer can draw more than a pin should supply. Use a piezo, or drive it through a transistor.

#### Code

No libraries needed. Board: **Arduino UNO R4 Minima** (install **Arduino UNO R4 Boards** in Boards Manager).

```cpp
const int TRIG = 9;
const int ECHO = 10;
const int LED = 6;
const int BUZZER = 5;       // a passive piezo buzzer: tone() sets the pitch

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

long readCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);                             // a 10 us pulse starts a reading
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, 30000UL);   // give up after 30 ms (about 5 m)
  if (us == 0) return -1;                            // nothing in range
  return us / 58;                                    // out and back: about 58 us per cm
}

void loop() {
  long cm = readCm();
  Serial.println(cm);

  if (cm < 0 || cm > 150) {                          // far away: stay quiet
    noTone(BUZZER);
    digitalWrite(LED, LOW);
    delay(100);
    return;
  }
  if (cm < 20) {                                     // stop here
    tone(BUZZER, 2000);
    digitalWrite(LED, HIGH);
    delay(100);
    return;
  }
  int gap = map(cm, 20, 150, 60, 600);               // closer means faster beeps
  tone(BUZZER, 2000, 50);
  digitalWrite(LED, HIGH);
  delay(50);
  digitalWrite(LED, LOW);
  delay(gap);
}
```

#### Build steps

1. **Wire it on a breadboard.** Follow the table. Mind the LED's long leg (+).
2. **Upload and test on the bench.** Move your hand toward the sensor: beeps speed up below 150 cm and turn solid under 20 cm.
3. **Set your stop distance.** Park where you want to stop, read the distance in the Serial Monitor, and replace both 20s in the code with it.
4. **Mount it.** At bumper height on the wall, pointing straight at the car.

**Print settings:** PLA or PETG, 0.2 mm, 3 walls, 15% infill. Print the front plate face down.

#### The printed enclosure

A front plate with two round holes for the transducers and a box behind it for the Uno. Measure the transducer diameter and spacing of your module with calipers before you design; clones vary. Keep the transducers flush with the front so the edges of the holes do not reflect the pulse.

### Plant soil monitor with its own web page

A capacitive sensor in the pot and an ESP32 that serves a page to any phone on your Wi-Fi: moisture as a percentage, and a "water me" warning.

- **Level:** Intermediate
- **Time:** 1 hour + printing
- **Parts cost:** $20–25
- **Board:** ESP32-DevKitC-32E

What you learn:

- Analog readings and calibration
- Why ADC2 and Wi-Fi do not mix
- A tiny web server on a microcontroller

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | ESP32 dev board | Wi-Fi, plus ADC1 pins that keep working while Wi-Fi is on |
| 1 | Capacitive soil moisture sensor (SEN0193 design) | 3.3 to 5.5 V in, 0 to 3.0 V out, no exposed metal to corrode |
| 3 | Female-to-female jumper wires |  |
| 1 | USB charger and cable |  |
| 25 g | PETG filament | Copes with damp better than PLA |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| ESP32 DevKitC 3V3 | Soil sensor VCC | Power |  |
| ESP32 DevKitC GND | Soil sensor GND | Ground |  |
| ESP32 DevKitC GPIO34 | Soil sensor AOUT | Analog | Input-only pin on ADC1 |

- Use an ADC1 pin (GPIO32 to 39). ADC2 pins cannot be read while Wi-Fi is on.
- The reading goes **down** as the soil gets wetter.

#### Code

Built-in libraries only. Replace `DRY` and `WET` with the readings the Serial Monitor shows for your sensor.

```cpp
#include <WiFi.h>
#include <WebServer.h>

const char* WIFI_NAME = "your-network";       // 2.4 GHz only
const char* WIFI_PASS = "your-password";

const int SENSOR = 34;      // ADC1, so it keeps working while Wi-Fi is on
const int DRY = 3000;       // your reading in dry air
const int WET = 1300;       // your reading in a glass of water

WebServer server(80);

int moisture() {
  long sum = 0;
  for (int i = 0; i < 16; i++) { sum += analogRead(SENSOR); delay(5); }
  int raw = sum / 16;                                 // average out the noise
  return constrain(map(raw, DRY, WET, 0, 100), 0, 100);
}

void handleRoot() {
  int m = moisture();
  String html = "<!doctype html><meta name=viewport content='width=device-width'>"
                "<meta http-equiv=refresh content=10><title>Plant</title>"
                "<body style='font:24px system-ui;text-align:center;padding:40px'>"
                "<h1>" + String(m) + "%</h1><p>" +
                (m < 30 ? "Water me!" : "I'm fine, thanks.") + "</p>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);
  analogSetAttenuation(ADC_11db);                    // read the full 0 to ~3.1 V range
  WiFi.begin(WIFI_NAME, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print('.'); }
  Serial.printf("\nOpen http://%s\n", WiFi.localIP().toString().c_str());
  server.on("/", handleRoot);
  server.begin();
}

void loop() {
  server.handleClient();
  static unsigned long last = 0;
  if (millis() - last > 2000) {                      // raw readings, for calibrating
    last = millis();
    Serial.println(analogRead(SENSOR));
  }
}
```

#### Build steps

1. **Calibrate dry and wet.** Upload, open the Serial Monitor and note the reading with the sensor in air (DRY) and in a glass of water up to its line (WET). Put both in the code.
2. **Add your Wi-Fi.** Fill in WIFI_NAME and WIFI_PASS. The ESP32 joins 2.4 GHz networks only.
3. **Open the page.** The Serial Monitor prints an address such as http://192.168.1.42. Open it on a phone on the same network.
4. **Seal the top edge.** The electronics at the top of the sensor are not waterproof. Coat them with nail varnish or heat-shrink, and push the sensor in only up to its line.
5. **Fit the printed cap.** It covers the electronics and the cable so watering cannot reach them.

**Print settings:** PETG, 0.2 mm, 3 walls, 20% infill.

#### The printed enclosure

A stake-shaped cap that slides over the top of the sensor and covers the electronics, with the cable leaving downward so water runs off. Keep the ESP32 in a small box out of the pot.

### Useless box: flip the switch, a finger flips it back

The classic desk toy. Flip the toggle and a servo arm pops out of the lid to switch itself off, in one of three moods so it never gets old.

- **Level:** Intermediate
- **Time:** 2 hours + printing
- **Parts cost:** $20–25
- **Board:** ESP32-DevKitC-32E

What you learn:

- Driving a servo with PWM
- Reading a switch with a pull-up resistor
- Random behavior that feels alive

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | ESP32 dev board | Any ESP32 with a 5V pin |
| 1 | SG90 micro servo | 4.8 V, 1.8 kg·cm stall torque: plenty to flip a toggle |
| 1 | SPST toggle switch | Wired between GPIO14 and GND |
| 1 | 470 µF capacitor (optional) | Across the servo supply if the board resets when the servo moves |
| 1 | USB charger, 1 A or more | Laptop ports can sag when the servo starts |
| 80 g | PLA filament | Box, lid, flap and arm |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| ESP32 DevKitC 5V | SG90 VCC | Power | Red wire |
| ESP32 DevKitC GND | SG90 GND | Ground | Brown wire |
| ESP32 DevKitC GPIO13 | SG90 SIG | PWM / signal | Orange wire, PWM |
| ESP32 DevKitC GPIO14 | Switch A | Signal | INPUT_PULLUP |
| ESP32 DevKitC GND | Switch B | Ground |  |

- Power the servo from 5V, not 3V3. The 3.3 V signal from the ESP32 drives an SG90 fine.
- TowerPro rates the SG90 at 4.8 V and says 4.8 to 6 V is fine.

#### Code

Library: **ESP32Servo** from the Library Manager. Raise `PUSH` until the arm just flips the switch without straining.

```cpp
#include <ESP32Servo.h>
#include <esp_random.h>

const int SERVO_PIN = 13;
const int SWITCH_PIN = 14;  // switch to GND: LOW means "on"
const int REST = 10;        // arm tucked inside the box, in degrees
const int PUSH = 150;       // arm far enough to flip the switch: tune this

Servo arm;

void setup() {
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  randomSeed(esp_random());                     // a different mood order every power-up
  arm.setPeriodHertz(50);
  arm.attach(SERVO_PIN, 500, 2400);             // SG90 pulse range in microseconds
  arm.write(REST);
}

void sweep(int from, int to, int stepDelay) {
  int dir = from < to ? 1 : -1;
  for (int a = from; a != to; a += dir) { arm.write(a); delay(stepDelay); }
  arm.write(to);
}

void loop() {
  if (digitalRead(SWITCH_PIN) == HIGH) return;  // switch is off: nothing to do

  switch (random(3)) {
    case 0:                                     // polite
      sweep(REST, PUSH, 4);
      break;
    case 1:                                     // sneaky: peek, wait, strike
      sweep(REST, 90, 12);
      delay(random(600, 1800));
      sweep(90, PUSH, 2);
      break;
    default:                                    // annoyed: twice, fast
      arm.write(PUSH); delay(250);
      arm.write(110);  delay(150);
      arm.write(PUSH); delay(250);
  }
  delay(200);
  sweep(PUSH, REST, 3);
}
```

#### Build steps

1. **Print the box first.** You need it to find the servo angles.
2. **Mount the servo and switch.** Servo under the flap, horn pointing at the switch; switch through the lid.
3. **Find REST and PUSH.** Start with PUSH at 120 and raise it until the arm flips the switch cleanly.
4. **Upload and flip.** Each flip picks one of three moods at random.
5. **Close it up.** The arm lifts the hinged flap on its way out.

**Print settings:** PLA, 0.2 mm, 3 walls, 15% infill. Print the arm flat so the layers run along it.

#### The printed enclosure

A box with a hinged flap in the lid, a servo mount under the flap and a hole for the toggle. A print-in-place hinge saves hardware. SG90 clones vary slightly, so measure yours before designing the mount.

### Three-key USB macro pad with an ESP32-S3

Three mechanical keys that type shortcuts on any computer with no driver: the ESP32-S3 shows up as a USB keyboard.

- **Level:** Intermediate
- **Time:** 1.5 hours + printing
- **Parts cost:** $25–35
- **Board:** ESP32-S3-DevKitC-1

What you learn:

- USB HID: how a board pretends to be a keyboard
- Edge detection for buttons
- Standard keyboard dimensions for printed plates

#### Parts list

| Qty | Part | Why |
|---|---|---|
| 1 | ESP32-S3 dev board with native USB | The S3 can act as a USB keyboard; the classic ESP32 cannot |
| 3 | MX-compatible mechanical switches | They clip into a 14 mm square plate cutout |
| 3 | MX-stem keycaps |  |
| 1 | Hookup wire, or hot-swap sockets | One wire per key plus a shared ground |
| 40 g | PLA or PETG | Plate and case |

#### Wiring

| From | To | Wire | Note |
|---|---|---|---|
| ESP32-S3 DevKitC-1 GPIO4 | Key 1 A | Signal |  |
| ESP32-S3 DevKitC-1 GND | Key 1 B | Ground |  |
| ESP32-S3 DevKitC-1 GPIO5 | Key 2 A | Signal |  |
| ESP32-S3 DevKitC-1 GND | Key 2 B | Ground |  |
| ESP32-S3 DevKitC-1 GPIO6 | Key 3 A | Signal |  |
| ESP32-S3 DevKitC-1 GND | Key 3 B | Ground |  |

- Each switch connects its pin to ground; INPUT_PULLUP holds the pin HIGH until you press.
- GPIO4, 5 and 6 are not strapping pins on the S3, so they are safe for keys.

#### Code

Board: **ESP32S3 Dev Module**, **USB Mode: USB-OTG (TinyUSB)**. On a Mac use `KEY_LEFT_GUI` (Command) instead of `KEY_LEFT_CTRL`.

```cpp
#include "USB.h"
#include "USBHIDKeyboard.h"

USBHIDKeyboard Keyboard;

const int KEYS[3] = {4, 5, 6};     // each switch connects its pin to GND
bool wasDown[3] = {false, false, false};

void setup() {
  for (int i = 0; i < 3; i++) pinMode(KEYS[i], INPUT_PULLUP);
  Keyboard.begin();
  USB.begin();
}

void press(int i) {
  switch (i) {
    case 0:                                       // copy
      Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('c');
      break;
    case 1:                                       // paste
      Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('v');
      break;
    case 2:                                       // type a snippet
      Keyboard.print("Printed on my own macro pad!");
      return;
  }
  delay(20);
  Keyboard.releaseAll();
}

void loop() {
  for (int i = 0; i < 3; i++) {
    bool down = digitalRead(KEYS[i]) == LOW;
    if (down && !wasDown[i]) press(i);
    wasDown[i] = down;
  }
  delay(5);                                       // simple debounce
}
```

#### Build steps

1. **Print the plate and case.** The 1.5 mm plate is what the switch clips grip.
2. **Snap in the switches.** Push from the top until they click.
3. **Wire the keys.** One leg of each switch to its GPIO, the other legs together to GND.
4. **Set USB mode and upload.** USB Mode: USB-OTG (TinyUSB). Upload through the UART port, then plug the port labelled USB into the computer.
5. **Try it.** Open a text editor and press key 3.

**Print settings:** PLA or PETG, 0.2 mm, 4 walls; print the plate flat so the cutouts stay square.

#### The printed enclosure

A 1.5 mm plate with three 14 × 14 mm square cutouts spaced 19.05 mm apart, the standard key pitch, on a case deep enough for the board and wires. Leave an opening for the USB port and the BOOT button.

## ESP32 rules that save hours of debugging

**Pin rules for the classic ESP32 (and one for the Uno R4)**

| Rule | Why | Do this |
|---|---|---|
| Pins are 3.3 V, not 5 V tolerant | Espressif rates GPIO at 3.6 V maximum | Divide or level-shift 5 V signals, such as an HC-SR04 echo |
| ADC2 stops when Wi-Fi is on | Wi-Fi uses the ADC2 hardware | Read analog sensors on ADC1: GPIO32 to 39 |
| GPIO34 to 39 are input only | No output driver, no internal pull-ups | Use them for sensors; add external pull-ups |
| Strapping pins: GPIO0, 2, 5, 12, 15 | Their level at power-up sets the boot mode | Avoid parts that pull them high or low at boot |
| GPIO6 to 11 belong to the flash | They connect to the SPI flash chip on most modules | Never use them |
| Default I2C is SDA 21, SCL 22 | Set in the Arduino ESP32 core | Use them unless you need others |
| Uno R4 pins supply 8 mA | Arduino's specification for the R4 Minima | Use 1 kΩ for LEDs; a transistor for more |

> **Power and batteries** Never power servos or LED strips from the 3V3 pin. Lithium cells need protection: charge to no more than about 4.2 V, do not discharge below about 3.0 V, charge only between about 0 and 50 °C, and use cells with a protection circuit. A TP4056 charger set with a 1.2 kΩ resistor charges at 1 A, too much for small cells.

## Designing the printed enclosure

- **Measure, do not trust drawings.** Module sizes vary between makers. Measure every board with calipers, then add clearance: 0.2 to 0.4 mm per side is a good start on FDM printers.
- **Make the USB opening generous.** Plugs have overmoulds that are bigger than the port.
- **Plan for heat.** Give regulators and the ESP32 some air, and keep temperature sensors away from both.
- **Print faces down.** Front plates and windows come out cleanest printed face-first on the bed.
- **Use inserts or bosses for screws.** Heat-set inserts survive repeated opening; self-tapping screws into bare plastic wear out.
- **Pick the material for the place.** See the table below.

**Filament for electronics enclosures**

| Material | Use it for | Avoid |
|---|---|---|
| PLA | Indoor gadgets, desk toys, prototypes | Cars, windowsills, outdoors: it softens in heat |
| PETG | Damp places, planters, garages | Very fine detail (it strings more) |
| ASA | Outdoor cases in sunlight | Printing without an enclosure and ventilation |
| TPU | Feet, bumpers, gaskets, cable strain relief | Rigid parts |

Do not want to model it yourself? Describe the box with your measurements to the [CAD agent](https://printpal.io/cad-agent), or start from a PrintPal maker for the round ESP32-S3 screen.

- [AI CAD agent](https://printpal.io/cad-agent) (Exact sizes): Describe a part with real dimensions and get parametric OpenSCAD you can edit, with a live preview and STL export.
- [Desk Pal maker](https://printpal.io/tools/desk-pal-maker) (ESP32-S3): Design a printed head for the round ESP32-S3 screen.
- [LED lamp maker](https://printpal.io/tools/led-lamp-maker) (Lamp kit): Design a shade for the Bambu Lab LED Lamp Kit 001.
- [ESP32 web flasher](https://printpal.io/tools/esp32-web-flasher) (Web Serial): Flash firmware to an ESP32 from Chrome or Edge, nothing to install.
- [Filament finder](https://printpal.io/tools/filament-finder) (Free): Pick a material by strength, heat, flex and ease of printing.
- [Bin generator](https://printpal.io/tools/bin-generator) (Free): Storage bins and drawer organizers sized to the millimetre.

## Getting code onto the board

1. **Install Arduino IDE.** Version 2.3.11 is current (November 12, 2026). Arduino Cloud's free plan also has an online editor.
2. **Add the board package.** **esp32** by Espressif Systems for ESP32 boards, **Arduino UNO R4 Boards** for the Uno R4.
3. **Use a data cable.** If no port appears, swap the cable before anything else.
4. **Enter download mode if needed.** Hold BOOT, tap RESET, release BOOT, then upload again.
5. **Or skip the IDE.** PrintPal build guides include precompiled firmware for the [ESP32 web flasher](https://printpal.io/tools/esp32-web-flasher): plug in, click, done.
6. **Simulate first.** [Wokwi](https://wokwi.com) simulates ESP32 and Arduino projects in the browser, free with unlimited public projects, so you can test code before the parts arrive.

**Pick a build and print it tonight** Ten ESP32 build guides with printable cases, parts lists and one-click flashing. [Browse build guides](https://printpal.io/projects)

## FAQ

### What is a good first ESP32 project?

One with no wiring: an ESP32 board with a built-in screen in a printed case, flashed from the browser, such as PrintPal's Slab eyes or Desk Pal buddy builds. After that, an ESP32 weather station with a BME280 and an OLED teaches I2C with just four wires.

### Should I start with Arduino or ESP32?

Start with an Arduino Uno R4 if your sensors are 5 V parts, because its pins run at 5 V. Choose an ESP32 for Wi-Fi or Bluetooth, and an ESP32-S3 for native USB or a screen. Both use the Arduino IDE.

### Can I connect a 5 V sensor to an ESP32?

Power it from 5 V if it needs that, but never feed a 5 V signal into an ESP32 pin: they are rated for 3.6 V at most. Use a voltage divider (Adafruit uses two 10 kΩ resistors on the HC-SR04 echo pin) or a level shifter.

### Why does my ESP32 reset when the servo moves?

The servo pulls a burst of current that makes the supply voltage dip. Power the servo from 5V rather than 3V3, use a stronger USB charger, and add a large capacitor (around 470 µF) across the servo supply.

### What filament is best for electronics enclosures?

PLA for indoor projects, PETG for damp places such as planters and garages, and ASA for outdoor cases in sunlight. PLA softens in heat, so keep it out of cars and windowsills.

### Do I need to solder for these projects?

Not for the ten click-together builds, the weather station, the parking sensor or the soil monitor if your modules come with headers fitted. The NeoPixel lamp needs three wires soldered to the ring, and the macro pad needs soldering or hot-swap sockets.

### How do I flash an ESP32 from the browser?

Open a web flasher in Chrome or Edge, connect the board with a USB data cable, pick its port and click Install. PrintPal's ESP32 web flasher and every PrintPal build guide work this way. If no port appears, hold BOOT, tap RESET and try again.

## Sources and method

Board specifications and prices come from the makers' and distributors' own pages (Arduino, Espressif, Adafruit, Seeed, Raspberry Pi, Waveshare) on November 12, 2026. Electrical limits come from Espressif's documentation and the part makers' datasheets listed below. The code was written for this guide against the current Arduino cores and the libraries named in each project. Parts costs are estimates. The ten click-together builds are PrintPal's own build guides.

- [Arduino Uno R4 Minima](https://store-usa.arduino.cc/products/uno-r4-minima): 5 V logic, 8 mA per pin
- [Arduino Uno R4 WiFi](https://store-usa.arduino.cc/products/uno-r4-wifi)
- [Arduino Nano ESP32](https://store-usa.arduino.cc/products/nano-esp32)
- [ESP32-DevKitC user guide](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-devkitc/user_guide.html)
- [ESP32-S3-DevKitC-1 user guide](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/user_guide_v1.1.html)
- [ESP32-C6-DevKitC-1 user guide](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c6/esp32-c6-devkitc-1/user_guide.html)
- [Adafruit ESP32-DevKitC-32E](https://www.adafruit.com/product/3269)
- [Seeed XIAO ESP32-S3](https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html)
- [Seeed XIAO ESP32-C6](https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html)
- [Raspberry Pi Pico 2 W launch](https://www.raspberrypi.com/news/raspberry-pi-pico-2-w-on-sale-now/)
- [Waveshare ESP32-S3-Touch-LCD-1.28 wiki](https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-1.28)
- [Waveshare ESP32-C6-LCD-1.47 wiki](https://www.waveshare.com/wiki/ESP32-C6-LCD-1.47)
- [ESP32-S3-Touch-LCD-1.28 on Amazon (B0CM68M8LR)](https://www.amazon.com/dp/B0CM68M8LR)
- [Espressif ESP-FAQ: hardware design](https://docs.espressif.com/projects/esp-faq/en/latest/hardware-related/hardware-design.html): 3.6 V GPIO limit
- [ESP-IDF GPIO reference](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html): ADC2, input-only and strapping pins
- [Arduino ESP32 core pin map](https://raw.githubusercontent.com/espressif/arduino-esp32/master/variants/esp32/pins_arduino.h)
- [Adafruit: powering NeoPixels](https://learn.adafruit.com/adafruit-neopixel-uberguide/powering-neopixels)
- [Adafruit: NeoPixel best practices](https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices)
- [SparkFun HC-SR04](https://www.sparkfun.com/ultrasonic-distance-sensor-hc-sr04.html)
- [Adafruit HC-SR04](https://www.adafruit.com/product/3942): echo divider
- [TowerPro SG90](https://towerpro.com.tw/product/sg90-7/)
- [Adafruit BME280](https://www.adafruit.com/product/2652)
- [Adafruit SSD1306 OLED](https://www.adafruit.com/product/326)
- [DFRobot SEN0193 datasheet](https://media.digikey.com/pdf/Data%20Sheets/DFRobot%20PDFs/SEN0193_Web.pdf)
- [TP4056 datasheet](https://www.toppwr.com/uploadfile/file/20230304/640301eae1260.pdf)
- [Adafruit: Li-ion and LiPoly protection](https://learn.adafruit.com/li-ion-and-lipoly-batteries/protection-circuitry)
- [Arduino IDE releases](https://github.com/arduino/arduino-ide/releases/latest)
- [Wokwi pricing](https://wokwi.com/pricing)

## More guides

- [Easy electronics projects for kids and teens, with 3D-printed cases](https://printpal.io/guides/electronics-projects-for-kids): A step-by-step path from age 8 to 14+: no-wiring builds that work in an afternoon, a first breadboard circuit with code, free simulators, kid-friendly design tools, and the safety rules that matter.
- [3D printing tools for beginners: the software and hand tools that save the most prints](https://printpal.io/guides/3d-printing-tools-for-beginners): The slicer to start with, the nine hand tools worth $86.83, the four calibrations that fix most bad prints, the free design software, and how to keep the air in your room clean.
- [The best free STL sites in 2026, and how to pick a model that prints](https://printpal.io/guides/best-free-stl-sites): Eleven places to download free 3D printing files, ranked for beginners, with what changed in 2026, what each license lets you do, and the six things on a model page that tell you it will print.

- All guides: https://printpal.io/guides
- PrintPal for LLMs: https://printpal.io/llms.txt
