Community project
SKEMATIK ROV
The SKEMATIK ROV is a remotely operated underwater vehicle powered by an ESP32 microcontroller and six brushless motors controlled via 30 A PWM ESCs. The system uses a 15-meter RS-485 tether to communicate between the surface controller and the submerged ROV, with dual logic level shifters managing the 3.3 V to 5 V signal conversion for reliable motor control in a waterproof enclosure.
This guide provides a complete wiring diagram, full parts list, and ready-to-flash firmware for both the ROV and surface controller. Assembly steps cover power distribution from a 24 V 30 A PSU, motor connections, tether preparation, and safe testing procedures to get your ROV operational.
Wiring diagram

Gather all the parts
Assemble it in 7 steps
1. Matikan catu daya dan siapkan rumah kedap air
Jangan sambungkan PSU dahulu. Pasang ESP32 ROV, dua modul buck 12 V, buck 5 V, enam ESC, dan modul MAX3485 ROV di dalam wadah yang tetap kering. Motor A2212 dan ESC drone biasa tidak tahan air; gunakan tabung, seal, dan jalur kabel yang benar-benar kedap sebelum ROV masuk air.
- Lepaskan seluruh baling-baling selama pengujian di meja.
- Beri label ESC1 sampai ESC6 pada kabel agar tidak tertukar.
- Air yang masuk ke ESC, motor, atau ESP32 dapat menyebabkan korsleting dan merusak perangkat.
2. Buat jalur daya 24 V dan 12 V
Hubungkan V+ PSU 24 V ke VIN+ buck motor A, VIN+ buck motor B, dan VIN+ buck 5 V. Hubungkan V− PSU ke ketiga VIN−. Atur kedua buck motor ke tepat 12 V memakai multimeter sebelum ESC dipasang. Hubungkan keluaran buck A ke kabel daya ESC1, ESC2, ESC3; hubungkan keluaran buck B ke ESC4, ESC5, ESC6.
- Buck A → ESC1–ESC3 (daya); buck B → ESC4–ESC6 (daya).
- Gunakan kabel daya yang cukup besar dan sekring pada tiap keluaran buck bila tersedia.
- Jangan pernah menyatukan keluaran 12 V dari buck A dan buck B; dua catu yang disatukan dapat saling merusak.
- Jangan sambungkan 24 V langsung ke ESC atau ESP32 karena dapat merusaknya.
3. Berikan daya 5 V pada ESP32 ROV
Atur buck logic ke tepat 5 V terlebih dahulu. Hubungkan VOUT+ buck logic ke pin VIN/5V ESP32 ROV (daya), lalu VOUT− ke GND ESP32 ROV (ground). Sambungkan GND ini juga ke ground sinyal ESC dan kedua pengubah level agar semua sinyal mempunyai patokan yang sama.
- Ukur keluaran buck 5 V sebelum memasang ESP32.
- Jaga kabel sinyal jauh dari kabel motor tiga fasa untuk mengurangi gangguan.
- Jangan hubungkan keluaran buck 5 V ke pin 3V3 ESP32; tegangan itu dapat merusak papan.
4. Sambungkan enam sinyal ESC melalui pengubah level
Pada pengubah level pertama: LV → 3V3 (daya), HV → 5V (daya), GND → GND (ground); LV1 → GPIO25 (sinyal) dan HV1 → PWM ESC1 (sinyal), LV2 → GPIO26 (sinyal) dan HV2 → PWM ESC2 (sinyal), LV3 → GPIO27 (sinyal) dan HV3 → PWM ESC3 (sinyal). Pada pengubah kedua lakukan sama: LV1 → GPIO32 lalu HV1 → PWM ESC4, LV2 → GPIO33 lalu HV2 → PWM ESC5, LV3 → GPIO4 lalu HV3 → PWM ESC6.
- Kabel sinyal ESC biasanya putih/kuning; kabel hitam/cokelatnya harus ke GND bersama.
- Pengubah level kedua mempunyai satu kanal cadangan yang tidak dipakai.
- Pastikan VCC dan GND pengubah level tidak tertukar — sambungan terbalik dapat merusak modul dan ESP32.
5. Hubungkan motor ke masing-masing ESC
Sambungkan tiga kabel motor_1 ke tiga kabel keluaran esc_1, lalu ulangi untuk pasangan motor_2/esc_2 sampai motor_6/esc_6. Bila arah putar motor salah saat nanti diuji, matikan daya lalu tukar sembarang dua dari tiga kabel fasa motor itu.
- Gunakan label motor dan ESC yang sama agar arah serta posisi thruster mudah dilacak.
- Jangan menukar kabel motor ketika sistem menyala; percikan dapat merusak ESC.
6. Buat kabel tether RS-485 sepanjang 15 meter
Pada ESP32 ROV hubungkan MAX3485: VCC → 3V3 (daya), GND → GND (ground), RO → GPIO16 (data masuk), DI → GPIO17 (data keluar), DE dan RE yang disatukan → GPIO21 (arah komunikasi). Hubungkan A MAX3485 ROV ke A MAX3485 permukaan (data), dan B ke B (data) memakai satu pasang kabel terpilin pada tether. Pada sisi permukaan: VCC MAX3485 → 3V3 ESP32 permukaan (daya), GND → GND (ground), DI → GPIO17 (data), RO → GPIO16 (data), dan DE/RE → GPIO21 (arah).
- Pakai satu twisted pair khusus untuk A dan B; beri label yang sama pada kedua ujung tether.
- Jika komunikasi tidak berjalan, matikan daya lalu tukar A dan B di salah satu ujung saja.
- Jangan menyambungkan kabel A/B RS-485 ke 24 V atau 12 V; modul komunikasi akan rusak.
7. Siapkan pengendali permukaan dan uji aman
Nyalakan ESP32 pengendali permukaan dengan USB 5 V, pasangkan stik PS3 melalui Bluetooth, lalu nyalakan sistem ROV. Saat ESP32 ROV menyala, ia mengirim pulsa berhenti selama empat detik untuk mempersiapkan ESC. Uji setiap motor tanpa baling-baling dan naikkan perintah secara perlahan. Bila tether lepas atau perintah berhenti selama setengah detik, firmware ROV menghentikan seluruh motor.
- Mulai dari throttle nol dan pastikan semua ESC berbunyi siap sebelum perintah motor diberikan.
- Untuk tahap awal, batas keluaran firmware adalah sekitar 30% agar sesuai dengan kemampuan PSU dan buck yang ada.
- Jauhkan tangan, kabel longgar, dan benda kecil dari motor saat pengujian; motor dapat berputar mendadak.
Review all connections
1. Connections between "psu_24v" and "ESP32"
2. Connections between "buck_motor_a" and "ESP32"
3. Connections between "buck_motor_b" and "ESP32"
4. Connections between "buck_logic_5v" and "ESP32"
5. Connections between "esc_1" and "ESP32"
6. Connections between "esc_2" and "ESP32"
7. Connections between "esc_3" and "ESP32"
8. Connections between "esc_4" and "ESP32"
9. Connections between "esc_5" and "ESP32"
10. Connections between "esc_6" and "ESP32"
11. Connections between "rs485_rov" and "ESP32"
12. Connections between "level_shifter_1" and "ESP32"
13. Connections between "level_shifter_2" and "ESP32"
14. Connections between "esp32_surface" and "ESP32"
15. Connections between "rs485_surface" and "ESP32"
Deploy the firmware
#include <Arduino.h>
#include <ESP32Servo.h>
// Firmware for the ESP32 inside the ROV.
// It receives: t1,t2,t3,t4,t5,t6\n over RS-485, where each value is 0..1000.
// Forward declarations
void stopAllMotors();
bool applyCommand(char *line);
void readRs485();
constexpr int ESC1_PIN = 25;
constexpr int ESC2_PIN = 26;
constexpr int ESC3_PIN = 27;
constexpr int ESC4_PIN = 32;
constexpr int ESC5_PIN = 33;
constexpr int ESC6_PIN = 4;
constexpr int RS485_RX_PIN = 16;
constexpr int RS485_TX_PIN = 17;
constexpr int RS485_DE_RE_PIN = 21;
constexpr int STOP_US = 1000;
constexpr int MAX_US = 1300; // Keeps the existing 24 V / 30 A supply within a cautious initial limit.
constexpr uint32_t ARM_TIME_MS = 4000;
constexpr uint32_t COMMAND_TIMEOUT_MS = 500;
Servo esc[6];
const int escPins[6] = {ESC1_PIN, ESC2_PIN, ESC3_PIN, ESC4_PIN, ESC5_PIN, ESC6_PIN};
char commandLine[64];
size_t commandLength = 0;
uint32_t lastCommandMs = 0;
void stopAllMotors() {
for (int i = 0; i < 6; ++i) {
esc[i].writeMicroseconds(STOP_US);
}
}
bool applyCommand(char *line) {
int values[6];
char *token = strtok(line, ",");
for (int i = 0; i < 6; ++i) {
if (token == nullptr) return false;
char *end = nullptr;
const long value = strtol(token, &end, 10);
if (*end != '\0' || value < 0 || value > 1000) return false;
values[i] = static_cast<int>(value);
token = strtok(nullptr, ",");
}
if (token != nullptr) return false;
for (int i = 0; i < 6; ++i) {
esc[i].writeMicroseconds(map(values[i], 0, 1000, STOP_US, MAX_US));
}
lastCommandMs = millis();
return true;
}
void readRs485() {
while (Serial2.available() > 0) {
const char c = static_cast<char>(Serial2.read());
if (c == '\r') continue;
if (c == '\n') {
commandLine[commandLength] = '\0';
if (commandLength > 0) applyCommand(commandLine);
commandLength = 0;
} else if (commandLength < sizeof(commandLine) - 1) {
commandLine[commandLength++] = c;
} else {
commandLength = 0;
}
}
}
void setup() {
pinMode(RS485_DE_RE_PIN, OUTPUT);
digitalWrite(RS485_DE_RE_PIN, LOW); // MAX3485 stays in receive mode.
Serial2.begin(115200, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
for (int i = 0; i < 6; ++i) {
esc[i].setPeriodHertz(50);
esc[i].attach(escPins[i], 1000, 2000);
}
stopAllMotors();
delay(ARM_TIME_MS);
lastCommandMs = millis();
}
void loop() {
readRs485();
if (millis() - lastCommandMs > COMMAND_TIMEOUT_MS) {
stopAllMotors();
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




