Pendahuluan — Dari On/Off ke Sudut Presisi

Di Seri 1, aktuator paling sering dipakai adalah modul relay — lampu hanya ON atau OFF lewat MQTT (artikel #8). Itu cukup untuk smart switch, tapi tidak cukup untuk gerakan bertahap: buka pintu setengah, atur sudut kamera, atau posisi flap greenhouse.

Artikel Tier 2 ini mengajarkan servo SG90 dan dasar PWM di ESP32: putar lengan servo ke sudut 0–180° dari perintah MQTT — pola yang melengkapi relay di proyek gabungan (#9) dan capstone #10.

Prasyarat: Paham GPIO dasar (#3), MQTT subscribe (#7), dan sudah pernah wiring relay #8. Broker sendiri Mosquitto (#16) dipakai di sketch ini.

Relay vs Servo vs PWM

AktuatorKontrolMQTT payloadCocok untuk
Relay (#8)Digital on/offON / OFFLampu, pompa, kipas on/off
Servo SG90Sudut 0–180°0180Flap, lengan robot, throttle
PWM / LEDCDuty cycle 0–100%Angka atau persenDimmer, kecepatan DC motor kecil

Servo SG90 secara internal sudah memakai PWM 50 Hz — library ESP32Servo menyembunyikan detail LEDC. Kamu cukup panggil servo.write(sudut).

Hardware — SG90 & ESP32

  • Micro servo SG90 (9g, 4,8–6 V)
  • ESP32 DevKit
  • Kabel jumper · breadboard opsional
  • Power supply 5 V eksternal disarankan jika servo + WiFi bersamaan (servo bisa tarik hingga ~650 mA saat stall)

Wiring SG90 ke ESP32

Pin SG90Warna umumKe ESP32 / power
GNDCoklatGND ESP32
VCCMerah5 V (USB/VIN — atau supply 5 V terpisah)
Signal (PWM)OranyeGPIO 27

Kita pakai GPIO 27 agar tidak bentrok dengan GPIO 26 relay (#8) dan GPIO 4 DHT22 (#5). Hindari GPIO 6–11 (flash internal).

Pro tip: Jika servo bergetar di sudut tertentu, sambungkan kapasitor 100 µF antara VCC dan GND dekat servo — mengurangi noise saat WiFi transmit.

Apa Itu PWM di ESP32?

PWM (Pulse Width Modulation) mengubah lebar pulsa dalam satu periode tetap. Servo standar mengharapkan pulsa 50 Hz (periode 20 ms):

  • ~1 ms pulse → sudut ~0°
  • ~1,5 ms pulse → sudut ~90°
  • ~2 ms pulse → sudut ~180°

ESP32 punya periferal LEDC (LED PWM controller) untuk generate sinyal ini. Library ESP32Servo mengalokasikan timer LEDC secara otomatis.

Library & Dependensi

Di Arduino IDE, install dari Library Manager:

  • ESP32Servo oleh Kevin Harrington
  • PubSubClient + WiFi (sudah familiar dari #7/#8)

Di PlatformIO (#29):

lib_deps =
  madhephaestus/ESP32Servo
  knolleary/PubSubClient

Broker & Topic MQTT

Mengikuti konvensi Seri 2 — broker pribadi 192.168.1.50, user kindo_esp32:

  • Topic sudut servo: kodingindonesia/esp32/servo/sudut
  • Payload: angka bulat 0180 (derajat)
  • Topic relay (referensi #8): kodingindonesia/esp32/lampu/kontrolON/OFF

Sketch Dasar — Sweep Lokal (Tanpa MQTT)

Uji hardware dulu sebelum WiFi:

#include <ESP32Servo.h>

#define SERVO_PIN 27

Servo servo;

void setup() {
  Serial.begin(115200);
  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);
  servo.setPeriodHertz(50);
  servo.attach(SERVO_PIN, 500, 2400);
  servo.write(90);
  Serial.println("Servo siap — sweep 0-180");
}

void loop() {
  for (int a = 0; a <= 180; a += 10) {
    servo.write(a);
    Serial.printf("Sudut: %d\n", a);
    delay(300);
  }
  delay(1000);
}

Sketch Lengkap — MQTT Subscribe Sudut

#include <WiFi.h>
#include <PubSubClient.h>
#include <ESP32Servo.h>

#define SERVO_PIN 27

const char* ssid = "GANTI_NAMA_WIFI";
const char* pass = "GANTI_PASSWORD_WIFI";
const char* mqtt_host = "192.168.1.50";
const char* mqtt_user = "kindo_esp32";
const char* mqtt_pass = "GANTI_PASSWORD_MQTT";
const char* topic_servo = "kodingindonesia/esp32/servo/sudut";

Servo servo;
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
int currentAngle = 90;

void onMessage(char* topic, byte* payload, unsigned int len) {
  char msg[8];
  size_t n = len < sizeof(msg) - 1 ? len : sizeof(msg) - 1;
  memcpy(msg, payload, n);
  msg[n] = '\0';

  int angle = atoi(msg);
  angle = constrain(angle, 0, 180);
  currentAngle = angle;
  servo.write(angle);
  Serial.printf("MQTT → sudut %d\n", angle);
}

void reconnectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect("kindo-esp32-servo", mqtt_user, mqtt_pass)) {
      mqtt.subscribe(topic_servo);
      Serial.println("MQTT connected + subscribe servo/sudut");
    } else {
      delay(2000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);
  servo.setPeriodHertz(50);
  servo.attach(SERVO_PIN, 500, 2400);
  servo.write(90);

  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) { delay(300); }

  mqtt.setServer(mqtt_host, 1883);
  mqtt.setCallback(onMessage);
  reconnectMqtt();
}

void loop() {
  if (!mqtt.connected()) reconnectMqtt();
  mqtt.loop();
}

Payload MQTT mengikuti pola JSON sensor di topic kodingindonesia/esp32/dht22/data — di sini sengaja angka mentah agar mudah dikirim dari mosquitto_pub atau Node-RED (#23). Untuk timestamp di log, lihat NTP (#34) — contoh unix 1782977400 = 2026-07-02T14:30:00 UTC.

Mapping Sudut ke Skenario Nyata

Beberapa contoh mapping praktis untuk proyek IoT:

Sudut MQTTContoh mekanikProyek terkait
0Flap tertutup penuhGreenhouse capstone (#39)
90Posisi netral / setengah bukaKamera tilt
180Flap terbuka penuhVentilasi maksimum

Simpan mapping di dokumentasi tim — MQTT hanya mengirim angka; arti mekanik ditentukan oleh instalasi fisik.

Integrasi Home Assistant (Opsional)

Di Home Assistant (#21), buat MQTT number atau slider:

  • Topic state: kodingindonesia/esp32/servo/sudut
  • Min: 0 · Max: 180 · Step: 1
  • Mode: slider di dashboard — gerakkan servo dari UI HA

Berbeda dengan switch relay lampu yang hanya dua state — slider HA langsung memetakan ke servo.write() di firmware.

Uji dengan mosquitto_pub

Dari laptop yang bisa reach broker 192.168.1.50:

mosquitto_pub -h 192.168.1.50 -u kindo_esp32 -P GANTI_PASSWORD_MQTT \
  -t kodingindonesia/esp32/servo/sudut -m 0

mosquitto_pub -h 192.168.1.50 -u kindo_esp32 -P GANTI_PASSWORD_MQTT \
  -t kodingindonesia/esp32/servo/sudut -m 90

mosquitto_pub -h 192.168.1.50 -u kindo_esp32 -P GANTI_PASSWORD_MQTT \
  -t kodingindonesia/esp32/servo/sudut -m 180

Servo harus bergerak ke posisi yang dikirim. Bandingkan dengan relay: mosquitto_pub … -t kodingindonesia/esp32/lampu/kontrol -m ON hanya dua state.

Kombinasi Servo + Relay di Satu Proyek

Sketch gabungan bisa subscribe dua topic:

  • …/lampu/kontrol → relay GPIO 26 (#8)
  • …/servo/sudut → servo GPIO 27 (artikel ini)

Di callback MQTT, bandingkan strcmp(topic, …) untuk route perintah. Saat proyek membesar, pertimbangkan FreeRTOS (#31) — task servo terpisah dari task sensor DHT22.

LEDC Manual (Opsional)

Jika butuh PWM murni tanpa servo (mis. dimmer LED), pakai ledcSetup + ledcWrite langsung — tanpa library servo. Servo SG90 lebih praktis lewat ESP32Servo karena kalibrasi pulse width sudah di-handle.

Gerakan Bertahap (Anti-Jerk)

Memanggil servo.write(180) langsung dari 0 bisa membuat lengan servo “loncat” — mekanik flap atau kamera tilt terasa kasar. Untuk demo yang lebih halus, ramp sudut per langkah kecil di firmware:

void moveServoSmooth(int target) {
  target = constrain(target, 0, 180);
  int step = (target > currentAngle) ? 2 : -2;
  while (currentAngle != target) {
    currentAngle += step;
    if ((step > 0 && currentAngle > target) ||
        (step < 0 && currentAngle < target)) {
      currentAngle = target;
    }
    servo.write(currentAngle);
    delay(15);  // ~75–150 ms total untuk lonjakan 90°
  }
}

Panggil moveServoSmooth(angle) dari callback MQTT menggantikan servo.write(angle) langsung. Di produksi, pertimbangkan millis() non-blocking agar mqtt.loop() tetap responsif — pola yang sama dengan task terpisah di FreeRTOS (#31).

Untuk flap greenhouse (#39), ramp 2° per 20 ms sering cukup: flap tidak “membanting” engsel plastik, tapi tetap merespons perintah MQTT dalam waktu kurang dari dua detik untuk perjalanan penuh 0→180°.

Kalibrasi Pulse Width SG90

Parameter servo.attach(SERVO_PIN, 500, 2400) memetakan sudut ke lebar pulsa dalam mikrodetik. SG90 murah kadang tidak mencapai 0° atau 180° nominal — jika lengan mentok sebelum angka MQTT, sesuaikan:

  • 0° terlalu dalam: naikkan nilai minimum (mis. 600 µs bukan 500)
  • 180° tidak penuh: naikkan maksimum (mis. 2500 µs)
  • Uji dengan mosquitto_pub -m 0 dan -m 180 sambil amati mekanik — jangan paksa gear saat mentok

Catat nilai kalibrasi di README proyek tim; MQTT tetap mengirim 0–180, firmware yang menerjemahkan ke pulse width fisik.

Daya, Stall Current & Mekanik

  • SG90 stall current ~650 mA — USB ESP32 saja bisa drop voltage saat servo + WiFi peak
  • Gunakan supply 5 V 1 A terpisah untuk servo; GND servo dan GND ESP32 harus common
  • Jangan memaksa lengan servo saat motor aktif — bisa rusak gear plastik
  • Servo continuous rotation (360°) berbeda — butuh library/kalibrasi lain

Keamanan & Produksi

  • Ganti GANTI_NAMA_WIFI, GANTI_PASSWORD_WIFI, GANTI_PASSWORD_MQTT — jangan commit ke GitHub
  • Validasi sudut di firmware: constrain(angle, 0, 180) — jangan percaya payload MQTT mentah
  • Pakai MQTT TLS (#17) jika perintah servo mengontrol perangkat fisik berisiko
  • Topic kontrol servo sebaiknya tidak retained dengan sudut acak — hindari gerakan tak terduga saat boot

Estimasi Biaya

KomponenHarga perkiraan (IDR)
ESP32 DevKit35.000 – 55.000
SG90 servo18.000 – 30.000
Breadboard + jumper10.000 – 20.000
Total~63.000 – 105.000

Checklist Sebelum Demo

  • ☐ Sweep lokal 0→180→90 berjalan halus
  • ☐ WiFi connect + MQTT subscribe servo/sudut
  • mosquitto_pub -m 0, 90, 180 menggerakkan servo
  • ☐ GPIO 27 signal · GND common · tidak bentrok GPIO relay/DHT
  • ☐ Serial mencetak sudut yang diterima

Node-RED & Dashboard Visual

Di Node-RED (#23), tambahkan node ui_slider yang publish ke kodingindonesia/esp32/servo/sudut. Slider 0–180 di browser langsung menggerakkan SG90 — pola mirip tombol ON/OFF relay di dashboard yang sama, tapi dengan kontrol analog.

Kombinasikan dengan chart suhu dari topic kodingindonesia/esp32/dht22/data — misalnya buka flap (sudut 120) otomatis saat suhu > 30°C lewat function node. Itu pratinjau logika capstone greenhouse (#39).

Publish Balik Sudut (Opsional)

Untuk monitoring di Grafana (#19), ESP32 bisa publish sudut terakhir ke topic kodingindonesia/esp32/servo/status setelah setiap perubahan — payload sama angka 0–180. Subscriber Python (#18) bisa menyimpan histori posisi aktuator, bukan hanya sensor.

FAQ Singkat

Servo tidak bergerak sama sekali?
Cek VCC 5 V, GND common, dan pin signal GPIO 27. Uji sketch sweep lokal dulu.
Servo bergetar di satu sudut?
Normal di beban berat — kurangi beban mekanik atau naikkan supply current.
Bisa kontrol dari Home Assistant?
Ya — buat MQTT number/slider ke topic servo/sudut seperti switch relay di #21.
MG996R vs SG90?
MG996R lebih kuat tapi butuh arus lebih besar — wiring MQTT sama, perhatikan power supply.

Tips & Troubleshooting

  • ESP32 reset saat servo gerak: Power supply lemah — pisahkan 5 V servo atau tambah kapasitor
  • MQTT tidak mengubah sudut: Cek mqtt.setCallback sebelum connect; pastikan mqtt.loop() di loop()
  • Sudut tidak akurat: Kalibrasi attach(pin, minUs, maxUs) — SG90 umum 500–2400 µs
  • Topic salah: Harus persis kodingindonesia/esp32/servo/sudut — case sensitive
  • Payload bukan angka: atoi mengembalikan 0 — kirim 45 bukan "sudut":45 di artikel ini
  • Konflik timer PWM: Pastikan allocateTimer(0..3) dipanggil sekali di setup()

Langkah Selanjutnya — Tier 2 Seri 2

Servo melengkapi relay on/off — sekarang aktuator punya gerakan presisi. Lanjut ke pelengkap Tier 2:

  • ADC Soil Moisture & LDR (#35): sensor analog — melengkapi servo dengan input tanah & cahaya
  • NTP (#34) — timestamp di log pergerakan servo
  • BLE (#32) — kontrol servo dari app HP (lanjutan)
  • Grafana (#19) — grafik histori sudut jika publish balik ke MQTT
  • Capstone greenhouse (#39) — servo flap + pompa relay + sensor

PWM membuka gerakan halus di proyek ESP32 — lanjutkan di halaman artikel Koding Indonesia.