A ₹1,390 pan-tilt mount you steer from a web page on your phone. Two SG90-class servos in a pan-tilt bracket are driven by an ESP32 that serves a small web page with two sliders. Move a slider and the servo follows. The page includes the wiring, a separate servo supply, a complete sketch with the page built in, and a test plan for the brown-outs and Wi-Fi drops that make hobby servo projects flaky.
Scope: intermediate. This is a design worksheet with illustrative costs. It has not been bench-tested by this site. The sketch uses the ESP32 Arduino core’s built-in WebServer and Wi-Fi, plus the ESP32Servo library installed from the Library Manager; check both against their current examples, because names and defaults change between versions. The mount can later carry a camera, a sensor or a laser pointer; this build is only the mount and its controls.
How it works
The ESP32 joins your home Wi-Fi and starts a web server. Opening its address in a phone browser loads a page with two range sliders. Each slider sends a small request such as /set?pan=90 to the board, and the sketch moves the matching servo to that angle. There is no cloud service and no account: the phone and the board only need to share a network. The page is stored in the sketch as a text string, so the project is a single file.
Bill of materials
| Part | Qty | Unit | Total |
|---|---|---|---|
| ESP32 dev board | 1 | ₹450 | ₹450 |
| SG90-class servo | 2 | ₹130 | ₹260 |
| Pan-tilt bracket | 1 | ₹120 | ₹120 |
| 5 V 2 A buck converter | 1 | ₹220 | ₹220 |
| 2×18650 holder with cells | 1 | ₹200 | ₹200 |
| Switch | 1 | ₹30 | ₹30 |
| Jumper wire set | 1 | ₹50 | ₹50 |
| Small breadboard | 1 | ₹60 | ₹60 |
| Parts total | ₹1,390 | ||
Scroll the table sideways to see every column.
Prices checked Nov 2026. Shipping and tools are not included.
Prices are illustrative figures for Indian sellers in November 2026, before shipping, and have not been checked against current listings. The buck converter gives the servos their own 5 V rail; do not run both from the ESP32 board.
Power and wiring
| Part | ESP32 dev board | Note |
|---|---|---|
| Pan servo signal | GPIO 18 | A normal output pin; check your board’s pinout |
| Tilt servo signal | GPIO 19 | A normal output pin; check your board’s pinout |
| Servo VCC | Buck converter 5 V output | Not the board’s 3V3 or 5V pin |
| Servo GND | Common GND | Join the buck, ESP32 and servo grounds |
| Board power | USB, or the buck 5 V into the 5V/VIN pin | Use one source at a time |
Two servos moving at once can pull more current than a USB port or the board’s regulator should provide. A weak supply shows up as the board rebooting when the servos move; see ESP32 brownout detector was triggered. A separate servo rail and a common ground is the fix.
The sketch
// ESP32 Wi-Fi pan-tilt mount: ESP32 + 2 servos, controlled from a web page
// Libraries: ESP32Servo (Library Manager). WiFi and WebServer come with the ESP32 core.
// Design worksheet: check names against the current library examples.
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
const char* WIFI_SSID = "your-network-name";
const char* WIFI_PASSWORD = "your-network-password";
const int PAN_PIN = 18;
const int TILT_PIN = 19;
const int PAN_MIN = 20; // keep inside the range the bracket allows
const int PAN_MAX = 160;
const int TILT_MIN = 40;
const int TILT_MAX = 140;
Servo panServo;
Servo tiltServo;
WebServer server(80);
const char PAGE[] PROGMEM = R"HTML(
<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Pan-tilt</title></head><body style="font-family:sans-serif;padding:16px">
<h2>Pan-tilt mount</h2>
<p>Pan <input id="pan" type="range" min="20" max="160" value="90" style="width:100%"></p>
<p>Tilt <input id="tilt" type="range" min="40" max="140" value="90" style="width:100%"></p>
<script>
function send(){
fetch('/set?pan='+document.getElementById('pan').value+'&tilt='+document.getElementById('tilt').value);
}
document.getElementById('pan').oninput=send;
document.getElementById('tilt').oninput=send;
</script></body></html>
)HTML";
void handleRoot() {
server.send(200, "text/html", PAGE);
}
void handleSet() {
if (server.hasArg("pan")) {
int pan = constrain(server.arg("pan").toInt(), PAN_MIN, PAN_MAX);
panServo.write(pan);
}
if (server.hasArg("tilt")) {
int tilt = constrain(server.arg("tilt").toInt(), TILT_MIN, TILT_MAX);
tiltServo.write(tilt);
}
server.send(200, "text/plain", "ok");
}
void setup() {
Serial.begin(115200);
panServo.setPeriodHertz(50);
tiltServo.setPeriodHertz(50);
panServo.attach(PAN_PIN);
tiltServo.attach(TILT_PIN);
panServo.write(90);
tiltServo.write(90);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Open http://");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/set", handleSet);
server.begin();
}
void loop() {
server.handleClient();
}The sketch clamps every request to the angle limits, so a bad request cannot drive a servo into its stop. Put your own network name and password into the two placeholders, and do not publish a sketch that contains them. The page has no password of its own, so anyone on the same network can move the mount; keep it on a home network you trust and do not expose the board to the internet.
Test plan
- Connection: confirm the Serial Monitor prints an address and that the page loads on a phone on the same network. If the board never connects, see ESP32 Wi-Fi won’t connect; the ESP32 uses a 2.4 GHz network.
- Servo centre: before fitting the horns, power the servos at 90 degrees and fit the mount level.
- Limits: slide to each end and listen for humming. Narrow the limits until it stops.
- Supply: move both sliders quickly together. A board reboot means the servo rail is too weak or the grounds are not joined.
- Latency: drag a slider fast and judge the lag. If requests pile up, send them less often from the page.
- Drop-out: move the phone away until Wi-Fi weakens, and confirm the mount stays where it was and the board recovers.
- Load: add the camera or sensor you plan to carry and check that the servos hold it without buzzing.
What usually goes wrong
| Symptom | Likely cause | Fix |
|---|---|---|
| Board reboots when servos move | Servos drawing from a weak supply | Separate 5 V rail and a common ground |
| Never connects to Wi-Fi | Wrong password or a 5 GHz-only network | Check the credentials and use a 2.4 GHz network |
| Page loads, servos ignore it | Wrong GPIO or the library not attached | Check the pin numbers and the attach calls |
| Servos twitch | Noisy supply or long signal wires | Shorten the wires, add a capacitor across the servo rail |
| Mount hums at the ends | Angle limits too wide | Narrow the limits |
| Address changes each day | The router hands out a new address | Reserve an address in the router settings |
Upgrades
- Camera: mount an ESP32-CAM and stream video, as in the ESP32-CAM robot car.
- Smooth motion: move toward the target in small steps instead of jumping.
- Presets: add buttons for home, left and right positions.
- Access control: add a simple passcode check before any move is accepted.
Cost and parts notes
The ₹1,390 total is an illustration for the parts above and does not include shipping. Pan-tilt brackets vary in fit for SG90-class servos, so check the servo size before you buy. Use the cost calculator to compare sellers.
Common questions
Can I use an Arduino Uno instead?
Not for this version, because the Uno has no Wi-Fi. You could add a Wi-Fi module, but the ESP32 is simpler and cheaper for the job.
Is it safe to leave on my network?
The page has no password, so anyone on the same network can move the mount. Keep it on a trusted home network and do not forward a port to it.
Why is the servo supply separate?
Servos draw bursts of current that a USB port or the board regulator can not always supply. A separate rail avoids resets.
Can I drive more than two servos?
Yes, the library supports several, but watch the total current and keep the supply sized for the worst case.
Last reviewed: November 2026. This is a design worksheet with illustrative costs. It has not been bench-tested by this site; library names, pins and limits must be checked against the current ESP32 core and your board, and measurements from your build are welcome.

