Lewati ke isi

Input/Output Dasar dengan MicroPython di ESP32

Pada pertemuan kali ini akan membahas dasar-dasar input/output (I/O) menggunakan MicroPython pada ESP32. Mahasiswa akan mempelajari penggunaan GPIO, digital input (button), digital output (LED/buzzer), konsep pull-up/pull-down, PWM dasar, dan cara menggabungkan input dan output dalam satu aplikasi interaktif.

Alat dan Bahan

Hardware:

  • ESP32 Development Board yang sudah terinstal MicroPython
  • LED (3-5 buah, berbagai warna)
  • Resistor 220Ω atau 330Ω (untuk LED)
  • Push button atau tactile switch (2-3 buah)
  • Resistor 10KΩ (untuk pull-down jika diperlukan)
  • Buzzer aktif 3.3V atau 5V
  • Breadboard
  • Kabel jumper male-to-male
  • Kabel jumper male-to-female

Software:

  • MicroPython firmware sudah terinstal di ESP32
  • Visual Studio Code dengan extension Pymakr
  • Serial terminal (PuTTY/Screen) untuk REPL
  • Multimeter (opsional, untuk troubleshooting)

Bagian 1: Pengenalan GPIO ESP32

1.1 Konsep GPIO (General Purpose Input/Output)

GPIO adalah pin yang dapat dikonfigurasi sebagai input atau output untuk berinteraksi dengan komponen elektronik eksternal. ESP32 memiliki banyak pin GPIO yang dapat digunakan untuk berbagai keperluan.

Karakteristik GPIO ESP32:

  • Voltage Level: 3.3V (toleran hingga 3.6V)
  • Current per Pin: Maksimal 12mA per pin
  • Total Current: Maksimal 40mA untuk semua pin
  • Input Modes: Digital input, analog input (ADC)
  • Output Modes: Digital output, PWM, DAC
  • Internal Pull: Pull-up dan pull-down internal tersedia

1.2 Pin Layout dan Mapping ESP32

Setiap board ESP32 memiliki pin layout yang sedikit berbeda, namun GPIO numbering tetap sama. Pin GPIO ditandai dengan nomor seperti GPIO2, GPIO4, GPIO18, dan seterusnya.

Praktikum 1.1: Identifikasi Pin GPIO

Langkah 1: Periksa Pin Layout Board

Identifikasi pin-pin penting pada board ESP32 Anda: - Power pins: 3V3, GND, VIN (5V) - GPIO pins: GPIO2, GPIO4, GPIO5, GPIO18, GPIO19, GPIO21, GPIO22, GPIO23 - ADC pins: GPIO32, GPIO33, GPIO34, GPIO35, GPIO36, GPIO39 - Touch pins: GPIO4, GPIO0, GPIO2, GPIO15, GPIO13, GPIO12, GPIO14, GPIO27

Langkah 2: Pin yang Perlu Dihindari

Beberapa pin memiliki fungsi khusus dan sebaiknya dihindari: - GPIO0: Boot mode pin - GPIO2: Built-in LED (pada beberapa board) - GPIO6-11: Terhubung ke flash memory - GPIO34-39: Input only (tidak bisa output)

Langkah 3: Test GPIO dengan REPL

>>> import machine
>>> # Cek pin yang tersedia
>>> dir(machine)
# Akan muncul Pin di daftar
>>> pin2 = machine.Pin(2, machine.Pin.OUT)
>>> pin2.on()   # Nyalakan (jika ada LED built-in)
>>> pin2.off()  # Matikan

1.3 Konfigurasi GPIO Dasar

GPIO dapat dikonfigurasi dalam berbagai mode sesuai kebutuhan. Mode utama adalah input dan output dengan berbagai opsi tambahan.

Praktikum 1.2: Konfigurasi GPIO Dasar

Langkah 1: Import Module yang Diperlukan

>>> import machine
>>> import utime

Langkah 2: Konfigurasi Pin Output

>>> # Pin output untuk LED
>>> led_pin = machine.Pin(2, machine.Pin.OUT)
>>> 
>>> # Test output
>>> led_pin.on()    # Nyalakan
>>> utime.sleep(1)
>>> led_pin.off()   # Matikan

Langkah 3: Konfigurasi Pin Input

>>> # Pin input untuk button
>>> button_pin = machine.Pin(4, machine.Pin.IN)
>>> 
>>> # Baca status pin
>>> status = button_pin.value()
>>> print(f"Status pin: {status}")

Langkah 4: Konfigurasi dengan Pull Resistor

>>> # Input dengan pull-up internal
>>> button_pullup = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
>>> 
>>> # Input dengan pull-down internal
>>> button_pulldown = machine.Pin(5, machine.Pin.IN, machine.Pin.PULL_DOWN)

Bagian 2: Digital Output - LED Control

2.1 Dasar-dasar LED Control

LED (Light Emitting Diode) adalah komponen output digital yang paling sederhana. LED memerlukan arus yang dibatasi oleh resistor untuk mencegah kerusakan.

Teori LED:

  • Forward Voltage: ~1.8-3.3V (tergantung warna)
  • Forward Current: Umumnya 10-20mA
  • Resistor Calculation: R = (Vcc - Vf) / If
  • Untuk ESP32: R = (3.3V - 2.0V) / 0.015A = 87Ω (gunakan 220Ω atau 330Ω)

2.2 Rangkaian LED dengan ESP32

Ada dua cara memasang LED: current sourcing dan current sinking. Untuk ESP32, current sinking lebih direkomendasikan.

Praktikum 2.1: Rangkaian LED Dasar

Langkah 1: Siapkan Komponen

Komponen yang diperlukan: - LED (warna bebas) - Resistor 220Ω atau 330Ω - Kabel jumper - Breadboard

Langkah 2: Buat Rangkaian

Rangkaian Current Sinking (direkomendasikan):

3V3 → Resistor (220Ω) → LED (Anode) → LED (Cathode) → GPIO Pin → ESP32

Atau rangkaian Current Sourcing:

GPIO Pin → Resistor (220Ω) → LED (Anode) → LED (Cathode) → GND

Langkah 3: Test LED dengan REPL

>>> import machine
>>> import utime
>>> 
>>> # Inisialisasi pin untuk LED
>>> led = machine.Pin(18, machine.Pin.OUT)
>>> 
>>> # Test LED
>>> led.on()
>>> utime.sleep(1)
>>> led.off()

2.3 Program LED Sederhana

Setelah rangkaian bekerja, kita akan membuat berbagai program untuk mengontrol LED dengan pola yang berbeda.

Praktikum 2.2: Program LED Blinking

Langkah 1: Buat File led_basic.py

# led_basic.py - Program LED dasar
import machine
import utime

class LEDController:
    """Class untuk mengontrol LED"""

    def __init__(self, pin_number):
        """Inisialisasi LED controller"""
        self.led = machine.Pin(pin_number, machine.Pin.OUT)
        self.state = False
        print(f"LED controller initialized on GPIO{pin_number}")

    def on(self):
        """Nyalakan LED"""
        self.led.on()
        self.state = True
        print("LED ON")

    def off(self):
        """Matikan LED"""
        self.led.off()
        self.state = False
        print("LED OFF")

    def toggle(self):
        """Toggle LED state"""
        if self.state:
            self.off()
        else:
            self.on()

    def blink(self, times=5, delay=0.5):
        """Blink LED beberapa kali"""
        print(f"Blinking LED {times} times")
        for i in range(times):
            self.on()
            utime.sleep(delay)
            self.off()
            utime.sleep(delay)
        print("Blinking complete")

# Test LED controller
if __name__ == "__main__":
    # Inisialisasi LED pada GPIO18
    led_controller = LEDController(18)

    # Test basic operations
    print("=== LED Test Started ===")

    # Test on/off
    led_controller.on()
    utime.sleep(2)
    led_controller.off()
    utime.sleep(1)

    # Test toggle
    for i in range(4):
        led_controller.toggle()
        utime.sleep(0.5)

    # Test blink
    led_controller.blink(3, 0.3)

    print("=== LED Test Complete ===")

Langkah 2: Upload dan Test

  1. Upload file ke ESP32 menggunakan VSCode
  2. Jalankan dengan: exec(open('led_basic.py').read())

2.4 Multiple LED Control

Mengontrol beberapa LED sekaligus untuk membuat pola yang lebih menarik.

Praktikum 2.3: Multiple LED Pattern

Langkah 1: Siapkan Hardware

  • Pasang 3 LED pada GPIO18, GPIO19, GPIO21
  • Masing-masing dengan resistor 220Ω
  • Hubungkan ke breadboard dengan rapi

Langkah 2: Buat File multi_led.py

# multi_led.py - Kontrol multiple LED
import machine
import utime

class MultiLEDController:
    """Controller untuk multiple LED"""

    def __init__(self, pin_list):
        """Inisialisasi dengan list pin GPIO"""
        self.leds = []
        self.pin_numbers = pin_list

        # Inisialisasi setiap LED
        for pin in pin_list:
            led = machine.Pin(pin, machine.Pin.OUT)
            led.off()  # Pastikan LED mati di awal
            self.leds.append(led)

        print(f"Multi LED controller initialized with pins: {pin_list}")

    def all_on(self):
        """Nyalakan semua LED"""
        for led in self.leds:
            led.on()
        print("All LEDs ON")

    def all_off(self):
        """Matikan semua LED"""
        for led in self.leds:
            led.off()
        print("All LEDs OFF")

    def set_pattern(self, pattern):
        """Set pola LED berdasarkan list boolean"""
        if len(pattern) != len(self.leds):
            print("Error: Pattern length mismatch")
            return

        for i, state in enumerate(pattern):
            if state:
                self.leds[i].on()
            else:
                self.leds[i].off()

    def running_light(self, cycles=3, delay=0.2):
        """Efek running light"""
        print(f"Running light effect - {cycles} cycles")

        for cycle in range(cycles):
            # Maju
            for i in range(len(self.leds)):
                self.all_off()
                self.leds[i].on()
                utime.sleep(delay)

            # Mundur
            for i in range(len(self.leds)-2, 0, -1):
                self.all_off()
                self.leds[i].on()
                utime.sleep(delay)

        self.all_off()

    def wave_effect(self, cycles=2, delay=0.15):
        """Efek gelombang"""
        print(f"Wave effect - {cycles} cycles")

        patterns = [
            [True, False, False],
            [True, True, False],
            [False, True, True],
            [False, False, True],
            [False, True, False],
        ]

        for cycle in range(cycles):
            for pattern in patterns:
                self.set_pattern(pattern)
                utime.sleep(delay)

        self.all_off()

    def blink_all(self, times=5, delay=0.3):
        """Blink semua LED bersamaan"""
        print(f"Blinking all LEDs {times} times")

        for i in range(times):
            self.all_on()
            utime.sleep(delay)
            self.all_off()
            utime.sleep(delay)

# Test multiple LED
if __name__ == "__main__":
    # Inisialisasi 3 LED
    led_pins = [18, 19, 21]
    multi_led = MultiLEDController(led_pins)

    print("=== Multiple LED Test Started ===")

    # Test basic operations
    multi_led.all_on()
    utime.sleep(1)
    multi_led.all_off()
    utime.sleep(1)

    # Test patterns
    patterns = [
        [True, False, False],
        [False, True, False],
        [False, False, True],
        [True, True, False],
        [True, False, True],
        [False, True, True],
        [True, True, True]
    ]

    for pattern in patterns:
        multi_led.set_pattern(pattern)
        utime.sleep(0.5)

    multi_led.all_off()
    utime.sleep(1)

    # Test effects
    multi_led.running_light(2, 0.15)
    utime.sleep(1)

    multi_led.wave_effect(2, 0.1)
    utime.sleep(1)

    multi_led.blink_all(3, 0.2)

    print("=== Multiple LED Test Complete ===")

Bagian 3: Digital Input - Button Control

3.1 Dasar-dasar Digital Input

Digital input digunakan untuk membaca sinyal digital dari sensor, switch, atau button. ESP32 dapat membaca level HIGH (3.3V) atau LOW (0V).

Konsep Pull-up dan Pull-down:

  • Pull-up: Resistor menghubungkan pin ke VCC (3.3V)
  • Pull-down: Resistor menghubungkan pin ke GND (0V)
  • Internal Pull: ESP32 memiliki pull-up/down internal yang dapat diaktifkan
  • Floating Input: Input tanpa pull resistor, tidak stabil

3.2 Rangkaian Button dengan ESP32

Button dapat dihubungkan dengan dua metode: active HIGH atau active LOW. Active LOW lebih umum digunakan.

Praktikum 3.1: Rangkaian Button Dasar

Langkah 1: Siapkan Komponen

  • Push button atau tactile switch
  • Resistor 10KΩ (untuk external pull-down, opsional)
  • Kabel jumper
  • Breadboard

Langkah 2: Rangkaian Active LOW (dengan internal pull-up)

Button: satu kaki ke GPIO, kaki lainnya ke GND
ESP32: GPIO pin dikonfigurasi dengan PULL_UP internal

Langkah 3: Test Button dengan REPL

>>> import machine
>>> import utime
>>> 
>>> # Button dengan pull-up internal (active LOW)
>>> button = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
>>> 
>>> # Test button
>>> while True:
...     if button.value() == 0:  # Button ditekan
...         print("Button pressed!")
...     utime.sleep_ms(100)
... 
# Tekan Ctrl+C untuk keluar

3.3 Program Button Input

Membuat program untuk membaca input button dengan proper debouncing dan event handling.

Praktikum 3.2: Button Input dengan Debouncing

Langkah 1: Buat File button_input.py

# button_input.py - Program button input dengan debouncing
import machine
import utime

class ButtonInput:
    """Class untuk handling button input"""

    def __init__(self, pin_number, pull_up=True, debounce_ms=50):
        """Inisialisasi button input"""
        self.pin_number = pin_number
        self.debounce_ms = debounce_ms
        self.last_state = 1 if pull_up else 0
        self.last_time = 0
        self.pull_up = pull_up

        # Konfigurasi pin
        if pull_up:
            self.button = machine.Pin(pin_number, machine.Pin.IN, machine.Pin.PULL_UP)
        else:
            self.button = machine.Pin(pin_number, machine.Pin.IN, machine.Pin.PULL_DOWN)

        print(f"Button initialized on GPIO{pin_number} ({'pull-up' if pull_up else 'pull-down'})")

    def read_raw(self):
        """Baca nilai raw button"""
        return self.button.value()

    def is_pressed(self):
        """Cek apakah button ditekan (dengan debouncing)"""
        current_time = utime.ticks_ms()
        current_state = self.button.value()

        # Tentukan nilai pressed berdasarkan pull configuration
        pressed_value = 0 if self.pull_up else 1

        # Debouncing logic
        if current_state != self.last_state:
            if utime.ticks_diff(current_time, self.last_time) > self.debounce_ms:
                self.last_state = current_state
                self.last_time = current_time

                return current_state == pressed_value

        return False

    def wait_for_press(self, timeout_ms=None):
        """Tunggu button ditekan"""
        start_time = utime.ticks_ms()

        print("Waiting for button press...")

        while True:
            if self.is_pressed():
                print("Button pressed!")
                return True

            # Cek timeout
            if timeout_ms and utime.ticks_diff(utime.ticks_ms(), start_time) > timeout_ms:
                print("Timeout waiting for button press")
                return False

            utime.sleep_ms(10)

    def count_presses(self, duration_ms=10000):
        """Hitung jumlah penekanan button dalam durasi tertentu"""
        start_time = utime.ticks_ms()
        count = 0

        print(f"Counting button presses for {duration_ms}ms...")

        while utime.ticks_diff(utime.ticks_ms(), start_time) < duration_ms:
            if self.is_pressed():
                count += 1
                print(f"Press count: {count}")

                # Tunggu button dilepas
                while self.read_raw() == (0 if self.pull_up else 1):
                    utime.sleep_ms(10)

            utime.sleep_ms(10)

        print(f"Total presses: {count}")
        return count

# Test button input
if __name__ == "__main__":
    # Inisialisasi button pada GPIO4 dengan pull-up
    button = ButtonInput(4, pull_up=True)

    print("=== Button Input Test Started ===")

    # Test 1: Simple press detection
    print("\nTest 1: Press the button 3 times")
    for i in range(3):
        button.wait_for_press()
        print(f"Press {i+1}/3 detected!")
        utime.sleep(0.5)

    # Test 2: Count presses
    print("\nTest 2: Press the button as many times as you can in 5 seconds")
    total_presses = button.count_presses(5000)
    print(f"You pressed the button {total_presses} times!")

    print("=== Button Input Test Complete ===")

3.4 Multiple Button Input

Mengelola multiple button input untuk membuat interface yang lebih kompleks.

Praktikum 3.3: Multiple Button dengan Menu

Langkah 1: Siapkan Hardware

  • Pasang 3 button pada GPIO4, GPIO5, GPIO23
  • Hubungkan satu kaki ke GPIO, kaki lainnya ke GND
  • Akan menggunakan pull-up internal

Langkah 2: Buat File multi_button.py

# multi_button.py - Multiple button input dengan menu
import machine
import utime

class MultiButtonMenu:
    """Class untuk menu dengan multiple button"""

    def __init__(self, button_pins):
        """Inisialisasi multiple button"""
        self.buttons = []
        self.button_names = ['Button 1', 'Button 2', 'Button 3']

        # Inisialisasi setiap button
        for pin in button_pins:
            button = machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP)
            self.buttons.append(button)

        # State tracking untuk debouncing
        self.last_states = [1] * len(self.buttons)
        self.last_times = [0] * len(self.buttons)
        self.debounce_ms = 50

        print(f"Multi-button menu initialized with {len(self.buttons)} buttons")

    def get_button_press(self):
        """Mendapatkan button yang ditekan (dengan debouncing)"""
        current_time = utime.ticks_ms()

        for i, button in enumerate(self.buttons):
            current_state = button.value()

            # Deteksi perubahan state (dari 1 ke 0 untuk active LOW)
            if current_state == 0 and self.last_states[i] == 1:
                if utime.ticks_diff(current_time, self.last_times[i]) > self.debounce_ms:
                    self.last_states[i] = current_state
                    self.last_times[i] = current_time
                    return i  # Return index button yang ditekan

            # Update state
            if current_state != self.last_states[i]:
                if utime.ticks_diff(current_time, self.last_times[i]) > self.debounce_ms:
                    self.last_states[i] = current_state
                    self.last_times[i] = current_time

        return None

    def show_menu(self):
        """Tampilkan menu"""
        print("\n" + "="*40)
        print("         BUTTON MENU")
        print("="*40)
        print("Button 1 (GPIO4): Option A")
        print("Button 2 (GPIO5): Option B") 
        print("Button 3 (GPIO23): Exit")
        print("="*40)
        print("Press any button to select...")

    def handle_menu_selection(self, button_index):
        """Handle pemilihan menu"""
        if button_index == 0:
            print("\n>>> Option A Selected!")
            print("Executing Option A...")
            # Simulasi task Option A
            for i in range(3):
                print(f"Option A task {i+1}/3")
                utime.sleep(0.5)
            print("Option A complete!")

        elif button_index == 1:
            print("\n>>> Option B Selected!")
            print("Executing Option B...")
            # Simulasi task Option B
            for i in range(5):
                print(f"Option B progress: {(i+1)*20}%")
                utime.sleep(0.3)
            print("Option B complete!")

        elif button_index == 2:
            print("\n>>> Exit Selected!")
            return False  # Signal untuk keluar

        return True  # Continue menu

    def run_menu(self):
        """Jalankan menu interaktif"""
        running = True
        self.show_menu()

        while running:
            button_pressed = self.get_button_press()

            if button_pressed is not None:
                print(f"\n{self.button_names[button_pressed]} pressed!")
                running = self.handle_menu_selection(button_pressed)

                if running:
                    print("\nPress any button for next action...")
                    utime.sleep(0.5)  # Delay untuk clarity

            utime.sleep_ms(10)  # Polling delay

        print("Menu exited. Goodbye!")

    def button_test(self):
        """Test semua button"""
        print("\n=== Button Test Mode ===")
        print("Press each button to test. Press all buttons together to exit.")

        while True:
            button_pressed = self.get_button_press()

            if button_pressed is not None:
                print(f"{self.button_names[button_pressed]} works!")

            # Cek apakah semua button ditekan bersamaan
            all_pressed = all(button.value() == 0 for button in self.buttons)
            if all_pressed:
                print("All buttons pressed! Exiting test mode...")
                break

            utime.sleep_ms(10)

# Test multi-button menu
if __name__ == "__main__":
    # Inisialisasi menu dengan 3 button
    button_pins = [4, 5, 23]
    menu = MultiButtonMenu(button_pins)

    print("=== Multi-Button Menu Test ===")

    # Test individual buttons first
    menu.button_test()
    utime.sleep(1)

    # Run interactive menu
    menu.run_menu()

Bagian 4: Kombinasi Input/Output - Interactive Applications

4.1 LED Control dengan Button

Menggabungkan button input dengan LED output untuk membuat aplikasi interaktif sederhana.

Praktikum 4.1: Button Controlled LED

Langkah 1: Siapkan Hardware

  • 1 LED dengan resistor pada GPIO18
  • 1 Button pada GPIO4 (pull-up internal)
  • Hubungkan semuanya ke breadboard

Langkah 2: Buat File button_led.py

# button_led.py - LED control dengan button
import machine
import utime

class ButtonLEDController:
    """Controller untuk LED yang dikontrol button"""

    def __init__(self, led_pin, button_pin):
        """Inisialisasi LED dan button"""
        # Setup LED
        self.led = machine.Pin(led_pin, machine.Pin.OUT)
        self.led.off()
        self.led_state = False

        # Setup button dengan pull-up internal
        self.button = machine.Pin(button_pin, machine.Pin.IN, machine.Pin.PULL_UP)

        # Debouncing variables
        self.last_button_state = 1
        self.last_debounce_time = 0
        self.debounce_delay = 50

        print(f"Button-LED controller initialized")
        print(f"LED: GPIO{led_pin}, Button: GPIO{button_pin}")

    def read_button_debounced(self):
        """Baca button dengan debouncing"""
        current_state = self.button.value()
        current_time = utime.ticks_ms()

        # Deteksi perubahan state
        if current_state != self.last_button_state:
            if utime.ticks_diff(current_time, self.last_debounce_time) > self.debounce_delay:
                self.last_button_state = current_state
                self.last_debounce_time = current_time

                # Return True jika button baru ditekan (1 -> 0 transition)
                return current_state == 0

        return False

    def toggle_led(self):
        """Toggle LED state"""
        self.led_state = not self.led_state
        if self.led_state:
            self.led.on()
            print("LED ON")
        else:
            self.led.off()
            print("LED OFF")

    def mode_toggle(self):
        """Mode: Toggle LED setiap kali button ditekan"""
        print("=== Toggle Mode ===")
        print("Press button to toggle LED. Press and hold for 3 seconds to exit.")

        hold_start_time = None

        while True:
            # Cek button press untuk toggle
            if self.read_button_debounced():
                self.toggle_led()
                hold_start_time = None  # Reset hold timer

            # Cek button hold untuk exit
            if self.button.value() == 0:  # Button sedang ditekan
                if hold_start_time is None:
                    hold_start_time = utime.ticks_ms()
                elif utime.ticks_diff(utime.ticks_ms(), hold_start_time) > 3000:
                    print("Exiting toggle mode...")
                    break
            else:
                hold_start_time = None

            utime.sleep_ms(10)

    def mode_momentary(self):
        """Mode: LED nyala selama button ditekan"""
        print("=== Momentary Mode ===")
        print("LED will be on while button is pressed.")
        print("Press and hold for 3 seconds to exit.")

        hold_start_time = None
        exit_mode = False

        while not exit_mode:
            button_state = self.button.value()

            if button_state == 0:  # Button ditekan
                if not self.led_state:
                    self.led.on()
                    self.led_state = True
                    print("LED ON (momentary)")

                # Cek untuk exit
                if hold_start_time is None:
                    hold_start_time = utime.ticks_ms()
                elif utime.ticks_diff(utime.ticks_ms(), hold_start_time) > 3000:
                    print("Exiting momentary mode...")
                    exit_mode = True

            else:  # Button tidak ditekan
                if self.led_state:
                    self.led.off()
                    self.led_state = False
                    print("LED OFF (momentary)")

                hold_start_time = None

            utime.sleep_ms(10)

    def mode_blink_on_press(self, blink_count=3):
        """Mode: LED blink beberapa kali setiap button ditekan"""
        print("=== Blink on Press Mode ===")
        print(f"LED will blink {blink_count} times when button is pressed.")
        print("Press and hold for 3 seconds to exit.")

        hold_start_time = None

        while True:
            if self.read_button_debounced():
                print(f"Button pressed! Blinking LED {blink_count} times...")

                # Blink LED
                for i in range(blink_count):
                    self.led.on()
                    utime.sleep(0.2)
                    self.led.off()
                    utime.sleep(0.2)

                print("Blink complete!")
                hold_start_time = None

            # Cek button hold untuk exit
            if self.button.value() == 0:
                if hold_start_time is None:
                    hold_start_time = utime.ticks_ms()
                elif utime.ticks_diff(utime.ticks_ms(), hold_start_time) > 3000:
                    print("Exiting blink mode...")
                    break
            else:
                hold_start_time = None

            utime.sleep_ms(10)

        # Pastikan LED mati saat keluar
        self.led.off()
        self.led_state = False

# Test button-LED controller
if __name__ == "__main__":
    # Inisialisasi controller
    controller = ButtonLEDController(led_pin=18, button_pin=4)

    print("=== Button-LED Controller Test ===")
    print("Testing different modes...")

    # Test mode toggle
    controller.mode_toggle()
    utime.sleep(1)

    # Test mode momentary
    controller.mode_momentary()
    utime.sleep(1)

    # Test mode blink
    controller.mode_blink_on_press(3)

    print("=== All tests complete ===")

4.2 Multi-Button LED Pattern Controller

Membuat controller yang lebih kompleks dengan multiple button untuk mengontrol pattern LED.

Praktikum 4.2: Advanced LED Pattern Controller

Langkah 1: Siapkan Hardware

  • 3 LED pada GPIO18, GPIO19, GPIO21
  • 3 Button pada GPIO4, GPIO5, GPIO23
  • Resistor untuk LED (220Ω each)

Langkah 2: Buat File advanced_controller.py

# advanced_controller.py - Advanced LED pattern controller
import machine
import utime
import _thread

class AdvancedLEDController:
    """Advanced controller untuk LED patterns dengan multiple buttons"""

    def __init__(self, led_pins, button_pins):
        """Inisialisasi LEDs dan buttons"""
        # Setup LEDs
        self.leds = []
        for pin in led_pins:
            led = machine.Pin(pin, machine.Pin.OUT)
            led.off()
            self.leds.append(led)

        # Setup buttons
        self.buttons = []
        for pin in button_pins:
            button = machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP)
            self.buttons.append(button)

        # State variables
        self.current_pattern = 0
        self.pattern_running = False
        self.pattern_thread = None
        self.button_states = [1] * len(button_pins)
        self.last_button_times = [0] * len(button_pins)
        self.debounce_ms = 50

        # Pattern definitions
        self.patterns = [
            self.pattern_off,
            self.pattern_all_blink,
            self.pattern_running_light,
            self.pattern_wave,
            self.pattern_random,
        ]

        self.pattern_names = [
            "All OFF",
            "All Blink",
            "Running Light",
            "Wave Effect",
            "Random Pattern"
        ]

        print(f"Advanced LED Controller initialized")
        print(f"LEDs: {led_pins}")
        print(f"Buttons: {button_pins}")

    def get_button_press(self):
        """Deteksi button press dengan debouncing"""
        current_time = utime.ticks_ms()

        for i, button in enumerate(self.buttons):
            current_state = button.value()

            # Deteksi falling edge (button press)
            if current_state == 0 and self.button_states[i] == 1:
                if utime.ticks_diff(current_time, self.last_button_times[i]) > self.debounce_ms:
                    self.button_states[i] = current_state
                    self.last_button_times[i] = current_time
                    return i

            # Update state tracking
            if current_state != self.button_states[i]:
                if utime.ticks_diff(current_time, self.last_button_times[i]) > self.debounce_ms:
                    self.button_states[i] = current_state
                    self.last_button_times[i] = current_time

        return None

    def set_led_pattern(self, pattern):
        """Set pola LED berdasarkan list boolean"""
        for i, state in enumerate(pattern):
            if i < len(self.leds):
                if state:
                    self.leds[i].on()
                else:
                    self.leds[i].off()

    def all_leds_off(self):
        """Matikan semua LED"""
        for led in self.leds:
            led.off()

    # Pattern functions
    def pattern_off(self):
        """Pattern: Semua LED mati"""
        self.all_leds_off()

    def pattern_all_blink(self):
        """Pattern: Semua LED blink bersamaan"""
        while self.pattern_running and self.current_pattern == 1:
            self.set_led_pattern([True, True, True])
            utime.sleep(0.5)
            if not self.pattern_running or self.current_pattern != 1:
                break
            self.set_led_pattern([False, False, False])
            utime.sleep(0.5)

    def pattern_running_light(self):
        """Pattern: Running light effect"""
        while self.pattern_running and self.current_pattern == 2:
            for i in range(len(self.leds)):
                if not self.pattern_running or self.current_pattern != 2:
                    break
                pattern = [False] * len(self.leds)
                pattern[i] = True
                self.set_led_pattern(pattern)
                utime.sleep(0.3)

    def pattern_wave(self):
        """Pattern: Wave effect"""
        wave_patterns = [
            [True, False, False],
            [True, True, False],
            [False, True, True],
            [False, False, True],
            [False, True, False],
        ]

        while self.pattern_running and self.current_pattern == 3:
            for pattern in wave_patterns:
                if not self.pattern_running or self.current_pattern != 3:
                    break
                self.set_led_pattern(pattern)
                utime.sleep(0.2)

    def pattern_random(self):
        """Pattern: Random LED states"""
        import urandom

        while self.pattern_running and self.current_pattern == 4:
            pattern = []
            for i in range(len(self.leds)):
                pattern.append(urandom.randint(0, 1) == 1)

            self.set_led_pattern(pattern)
            utime.sleep(0.3)

            if not self.pattern_running or self.current_pattern != 4:
                break

    def start_pattern(self, pattern_index):
        """Mulai pattern tertentu"""
        if pattern_index >= len(self.patterns):
            return

        # Stop pattern yang sedang berjalan
        self.stop_pattern()

        self.current_pattern = pattern_index
        print(f"Starting pattern: {self.pattern_names[pattern_index]}")

        if pattern_index == 0:
            # Pattern OFF tidak perlu thread
            self.patterns[0]()
        else:
            # Pattern lain berjalan di thread terpisah
            self.pattern_running = True
            try:
                _thread.start_new_thread(self.patterns[pattern_index], ())
            except:
                # Fallback jika threading tidak tersedia
                print("Threading not available, running pattern in main loop")
                self.patterns[pattern_index]()

    def stop_pattern(self):
        """Stop pattern yang sedang berjalan"""
        self.pattern_running = False
        utime.sleep(0.1)  # Beri waktu thread untuk berhenti
        self.all_leds_off()

    def show_status(self):
        """Tampilkan status saat ini"""
        print(f"\nCurrent Pattern: {self.pattern_names[self.current_pattern]}")
        print(f"Pattern Running: {self.pattern_running}")
        print("Button mapping:")
        print("- Button 1 (GPIO4): Next pattern")
        print("- Button 2 (GPIO5): Previous pattern") 
        print("- Button 3 (GPIO23): Stop/Start toggle")

    def run_controller(self):
        """Main loop controller"""
        print("=== Advanced LED Controller Started ===")
        self.show_status()

        try:
            while True:
                button_pressed = self.get_button_press()

                if button_pressed == 0:  # Button 1: Next pattern
                    next_pattern = (self.current_pattern + 1) % len(self.patterns)
                    self.start_pattern(next_pattern)
                    self.show_status()

                elif button_pressed == 1:  # Button 2: Previous pattern
                    prev_pattern = (self.current_pattern - 1) % len(self.patterns)
                    self.start_pattern(prev_pattern)
                    self.show_status()

                elif button_pressed == 2:  # Button 3: Stop/Start toggle
                    if self.pattern_running:
                        print("Stopping pattern...")
                        self.stop_pattern()
                    else:
                        print("Restarting pattern...")
                        self.start_pattern(self.current_pattern)
                    self.show_status()

                utime.sleep_ms(10)

        except KeyboardInterrupt:
            print("\nController stopped by user")
        finally:
            self.stop_pattern()
            print("All patterns stopped")

# Test advanced controller
if __name__ == "__main__":
    # Inisialisasi controller
    led_pins = [18, 19, 21]
    button_pins = [4, 5, 23]

    controller = AdvancedLEDController(led_pins, button_pins)

    # Jalankan controller
    controller.run_controller()

Bagian 5: PWM (Pulse Width Modulation) Dasar

5.1 Pengenalan PWM

PWM adalah teknik untuk mengontrol daya dengan mengubah lebar pulsa sinyal digital. ESP32 memiliki hardware PWM yang dapat digunakan untuk mengontrol kecerahan LED, kecepatan motor, atau menghasilkan sinyal analog.

Karakteristik PWM ESP32:

  • Channels: 16 channel PWM independen
  • Resolution: 1-16 bit (umumnya 8-bit untuk LED)
  • Frequency: 1 Hz - 40 MHz
  • Duty Cycle: 0-100% (0-1023 untuk 10-bit)

5.2 LED Brightness Control dengan PWM

Menggunakan PWM untuk mengontrol kecerahan LED secara smooth.

Praktikum 5.1: PWM LED Brightness Control

Langkah 1: Siapkan Hardware

  • 1 LED dengan resistor 220Ω pada GPIO18
  • 2 Button pada GPIO4 dan GPIO5

Langkah 2: Buat File pwm_led.py

# pwm_led.py - PWM LED brightness control
import machine
import utime

class PWMLEDController:
    """Controller untuk mengontrol brightness LED dengan PWM"""

    def __init__(self, led_pin, button_up_pin, button_down_pin):
        """Inisialisasi PWM LED controller"""
        # Setup PWM untuk LED
        self.pwm_led = machine.PWM(machine.Pin(led_pin))
        self.pwm_led.freq(1000)  # 1kHz frequency
        self.pwm_led.duty(0)     # Start dengan brightness 0

        # Setup buttons
        self.btn_up = machine.Pin(button_up_pin, machine.Pin.IN, machine.Pin.PULL_UP)
        self.btn_down = machine.Pin(button_down_pin, machine.Pin.IN, machine.Pin.PULL_UP)

        # State variables
        self.brightness = 0      # Current brightness (0-1023)
        self.max_brightness = 1023
        self.min_brightness = 0
        self.step_size = 50

        # Debouncing
        self.last_up_state = 1
        self.last_down_state = 1
        self.last_up_time = 0
        self.last_down_time = 0
        self.debounce_ms = 100

        print(f"PWM LED Controller initialized on GPIO{led_pin}")
        print(f"Up button: GPIO{button_up_pin}, Down button: GPIO{button_down_pin}")

    def set_brightness(self, brightness):
        """Set brightness LED (0-1023)"""
        # Clamp brightness ke range yang valid
        brightness = max(self.min_brightness, min(self.max_brightness, brightness))

        self.brightness = brightness
        self.pwm_led.duty(brightness)

        # Hitung persentase untuk display
        percentage = int((brightness / self.max_brightness) * 100)
        print(f"Brightness: {brightness}/1023 ({percentage}%)")

    def increase_brightness(self):
        """Naikkan brightness"""
        new_brightness = self.brightness + self.step_size
        self.set_brightness(new_brightness)

    def decrease_brightness(self):
        """Turunkan brightness"""
        new_brightness = self.brightness - self.step_size
        self.set_brightness(new_brightness)

    def check_button_up(self):
        """Cek button up dengan debouncing"""
        current_state = self.btn_up.value()
        current_time = utime.ticks_ms()

        if current_state == 0 and self.last_up_state == 1:
            if utime.ticks_diff(current_time, self.last_up_time) > self.debounce_ms:
                self.last_up_state = current_state
                self.last_up_time = current_time
                return True

        if current_state != self.last_up_state:
            if utime.ticks_diff(current_time, self.last_up_time) > self.debounce_ms:
                self.last_up_state = current_state
                self.last_up_time = current_time

        return False

    def check_button_down(self):
        """Cek button down dengan debouncing"""
        current_state = self.btn_down.value()
        current_time = utime.ticks_ms()

        if current_state == 0 and self.last_down_state == 1:
            if utime.ticks_diff(current_time, self.last_down_time) > self.debounce_ms:
                self.last_down_state = current_state
                self.last_down_time = current_time
                return True

        if current_state != self.last_down_state:
            if utime.ticks_diff(current_time, self.last_down_time) > self.debounce_ms:
                self.last_down_state = current_state
                self.last_down_time = current_time

        return False

    def auto_fade(self, duration_ms=3000):
        """Auto fade in dan fade out"""
        print("Starting auto fade effect...")

        steps = 50
        delay_ms = duration_ms // (steps * 2)  # *2 untuk fade in + fade out
        brightness_step = self.max_brightness // steps

        # Fade in
        print("Fading in...")
        for i in range(steps + 1):
            brightness = i * brightness_step
            self.set_brightness(brightness)
            utime.sleep_ms(delay_ms)

        # Fade out
        print("Fading out...")
        for i in range(steps, -1, -1):
            brightness = i * brightness_step
            self.set_brightness(brightness)
            utime.sleep_ms(delay_ms)

        print("Auto fade complete!")

    def breathing_effect(self, cycles=3):
        """Efek breathing (fade in-out berulang)"""
        print(f"Starting breathing effect - {cycles} cycles...")

        for cycle in range(cycles):
            print(f"Breathing cycle {cycle + 1}/{cycles}")

            # Fade in
            for brightness in range(0, self.max_brightness, 20):
                self.set_brightness(brightness)
                utime.sleep_ms(20)

            # Fade out
            for brightness in range(self.max_brightness, 0, -20):
                self.set_brightness(brightness)
                utime.sleep_ms(20)

        self.set_brightness(0)
        print("Breathing effect complete!")

    def manual_control_mode(self):
        """Mode kontrol manual dengan buttons"""
        print("=== Manual Control Mode ===")
        print("Up button: Increase brightness")
        print("Down button: Decrease brightness")
        print("Press both buttons together to exit")

        while True:
            # Cek button presses
            if self.check_button_up():
                self.increase_brightness()

            if self.check_button_down():
                self.decrease_brightness()

            # Exit condition: kedua button ditekan bersamaan
            if self.btn_up.value() == 0 and self.btn_down.value() == 0:
                print("Both buttons pressed. Exiting manual control...")
                break

            utime.sleep_ms(10)

    def demo_mode(self):
        """Demo berbagai efek PWM"""
        print("=== PWM LED Demo ===")

        # Set ke brightness minimum
        self.set_brightness(0)
        utime.sleep(1)

        # Demo 1: Step brightness
        print("\nDemo 1: Step brightness increases")
        for brightness in range(0, self.max_brightness + 1, 100):
            self.set_brightness(brightness)
            utime.sleep(0.5)

        utime.sleep(1)

        # Demo 2: Auto fade
        print("\nDemo 2: Auto fade effect")
        self.auto_fade(2000)

        utime.sleep(1)

        # Demo 3: Breathing effect
        print("\nDemo 3: Breathing effect")
        self.breathing_effect(2)

        print("\nDemo complete!")

    def cleanup(self):
        """Cleanup PWM resources"""
        self.pwm_led.deinit()
        print("PWM resources cleaned up")

# Test PWM LED controller
if __name__ == "__main__":
    # Inisialisasi controller
    controller = PWMLEDController(led_pin=18, button_up_pin=4, button_down_pin=5)

    try:
        print("=== PWM LED Controller Test ===")

        # Demo mode
        controller.demo_mode()
        utime.sleep(2)

        # Manual control mode
        controller.manual_control_mode()

    except KeyboardInterrupt:
        print("\nTest stopped by user")
    finally:
        controller.cleanup()
        print("Test complete!")

5.3 Buzzer Control dengan PWM

Menggunakan PWM untuk mengontrol buzzer dan menghasilkan nada yang berbeda.

Praktikum 5.2: PWM Buzzer Musical Notes

Langkah 1: Siapkan Hardware

  • 1 Buzzer aktif pada GPIO22
  • 2 Button pada GPIO4 dan GPIO5

Langkah 2: Buat File pwm_buzzer.py

# pwm_buzzer.py - PWM buzzer untuk musical notes
import machine
import utime

class PWMBuzzerController:
    """Controller untuk buzzer dengan PWM"""

    def __init__(self, buzzer_pin, button1_pin, button2_pin):
        """Inisialisasi PWM buzzer controller"""
        # Setup PWM untuk buzzer
        self.buzzer = machine.PWM(machine.Pin(buzzer_pin))
        self.buzzer.duty(0)  # Start silent

        # Setup buttons
        self.btn1 = machine.Pin(button1_pin, machine.Pin.IN, machine.Pin.PULL_UP)
        self.btn2 = machine.Pin(button2_pin, machine.Pin.IN, machine.Pin.PULL_UP)

        # Musical notes frequencies (in Hz)
        self.notes = {
            'C4': 262,   'C#4': 277,  'D4': 294,   'D#4': 311,
            'E4': 330,   'F4': 349,   'F#4': 370,  'G4': 392,
            'G#4': 415,  'A4': 440,   'A#4': 466,  'B4': 494,
            'C5': 523,   'C#5': 554,  'D5': 587,   'D#5': 622,
            'E5': 659,   'F5': 698,   'F#5': 740,  'G5': 784,
            'G#5': 831,  'A5': 880,   'A#5': 932,  'B5': 988
        }

        # Simple melodies
        self.melodies = {
            'scale': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
            'twinkle': ['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4',
                       'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4'],
            'happy_birthday': ['C4', 'C4', 'D4', 'C4', 'F4', 'E4']
        }

        # Button debouncing
        self.btn1_last_state = 1
        self.btn2_last_state = 1
        self.btn1_last_time = 0
        self.btn2_last_time = 0
        self.debounce_ms = 200

        print(f"PWM Buzzer Controller initialized on GPIO{buzzer_pin}")

    def play_tone(self, frequency, duration_ms=500, duty=512):
        """Mainkan nada dengan frekuensi tertentu"""
        if frequency > 0:
            self.buzzer.freq(frequency)
            self.buzzer.duty(duty)
            print(f"Playing tone: {frequency} Hz for {duration_ms} ms")
        else:
            self.buzzer.duty(0)  # Silent
            print(f"Silent for {duration_ms} ms")

        utime.sleep_ms(duration_ms)
        self.buzzer.duty(0)  # Stop sound

    def play_note(self, note, duration_ms=500):
        """Mainkan note musical"""
        if note in self.notes:
            frequency = self.notes[note]
            self.play_tone(frequency, duration_ms)
            print(f"Played note: {note}")
        else:
            print(f"Unknown note: {note}")

    def play_melody(self, melody_name, note_duration=400):
        """Mainkan melody"""
        if melody_name in self.melodies:
            melody = self.melodies[melody_name]
            print(f"Playing melody: {melody_name}")

            for note in melody:
                self.play_note(note, note_duration)
                utime.sleep_ms(50)  # Gap between notes

            print(f"Melody {melody_name} complete!")
        else:
            print(f"Unknown melody: {melody_name}")

    def play_scale(self, start_note='C4', octaves=1):
        """Mainkan scale naik turun"""
        scale_notes = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
        octave = int(start_note[-1])

        print(f"Playing scale from {start_note}, {octaves} octave(s)")

        # Scale naik
        for oct in range(octaves):
            for note in scale_notes:
                note_name = f"{note}{octave + oct}"
                if note_name in self.notes:
                    self.play_note(note_name, 300)

        # Scale turun
        for oct in range(octaves - 1, -1, -1):
            for note in reversed(scale_notes):
                note_name = f"{note}{octave + oct}"
                if note_name in self.notes:
                    self.play_note(note_name, 300)

    def check_button1(self):
        """Cek button 1 press"""
        current_state = self.btn1.value()
        current_time = utime.ticks_ms()

        if current_state == 0 and self.btn1_last_state == 1:
            if utime.ticks_diff(current_time, self.btn1_last_time) > self.debounce_ms:
                self.btn1_last_state = current_state
                self.btn1_last_time = current_time
                return True

        if current_state != self.btn1_last_state:
            if utime.ticks_diff(current_time, self.btn1_last_time) > self.debounce_ms:
                self.btn1_last_state = current_state
                self.btn1_last_time = current_time

        return False

    def check_button2(self):
        """Cek button 2 press"""
        current_state = self.btn2.value()
        current_time = utime.ticks_ms()

        if current_state == 0 and self.btn2_last_state == 1:
            if utime.ticks_diff(current_time, self.btn2_last_time) > self.debounce_ms:
                self.btn2_last_state = current_state
                self.btn2_last_time = current_time
                return True

        if current_state != self.btn2_last_state:
            if utime.ticks_diff(current_time, self.btn2_last_time) > self.debounce_ms:
                self.btn2_last_state = current_state
                self.btn2_last_time = current_time

        return False

    def interactive_mode(self):
        """Mode interaktif dengan buttons"""
        print("=== Interactive Buzzer Mode ===")
        print("Button 1: Play random melody")
        print("Button 2: Play scale")
        print("Press both buttons to exit")

        melodies = list(self.melodies.keys())
        melody_index = 0

        while True:
            if self.check_button1():
                # Play melody
                melody_name = melodies[melody_index]
                self.play_melody(melody_name)
                melody_index = (melody_index + 1) % len(melodies)

            if self.check_button2():
                # Play scale
                self.play_scale('C4', 1)

            # Exit condition
            if self.btn1.value() == 0 and self.btn2.value() == 0:
                print("Both buttons pressed. Exiting...")
                break

            utime.sleep_ms(10)

    def demo_sounds(self):
        """Demo berbagai suara"""
        print("=== Buzzer Demo ===")

        # Demo 1: Individual tones
        print("\nDemo 1: Individual tones")
        test_frequencies = [262, 330, 392, 523, 659, 784]
        for freq in test_frequencies:
            self.play_tone(freq, 400)
            utime.sleep_ms(100)

        utime.sleep(1)

        # Demo 2: Scale
        print("\nDemo 2: Musical scale")
        self.play_scale('C4', 1)

        utime.sleep(1)

        # Demo 3: Melody
        print("\nDemo 3: Twinkle Twinkle Little Star")
        self.play_melody('twinkle')

        print("\nDemo complete!")

    def cleanup(self):
        """Cleanup PWM resources"""
        self.buzzer.duty(0)
        self.buzzer.deinit()
        print("Buzzer PWM cleaned up")

# Test PWM buzzer controller
if __name__ == "__main__":
    # Inisialisasi controller
    controller = PWMBuzzerController(buzzer_pin=22, button1_pin=4, button2_pin=5)

    try:
        print("=== PWM Buzzer Controller Test ===")

        # Demo sounds
        controller.demo_sounds()
        utime.sleep(2)

        # Interactive mode
        controller.interactive_mode()

    except KeyboardInterrupt:
        print("\nTest stopped by user")
    finally:
        controller.cleanup()
        print("Test complete!")

Bagian 6: Project Integration - Simple IoT Device Simulator

6.1 Project: Smart LED Controller

Menggabungkan semua konsep yang telah dipelajari untuk membuat smart LED controller yang simulate perangkat IoT sederhana.

Praktikum 6.1: Complete Smart Controller

Langkah 1: Siapkan Hardware Lengkap

  • 3 LED (Red, Green, Blue) pada GPIO18, GPIO19, GPIO21
  • 1 Buzzer pada GPIO22
  • 3 Button pada GPIO4, GPIO5, GPIO23
  • 1 LED status pada GPIO2 (built-in jika tersedia)

Langkah 2: Buat File smart_controller.py

# smart_controller.py - Complete smart LED controller
import machine
import utime
import urandom

class SmartLEDController:
    """Smart LED Controller dengan multiple modes dan features"""

    def __init__(self):
        """Inisialisasi smart controller"""
        # LED Setup (RGB)
        self.led_red = machine.PWM(machine.Pin(18))
        self.led_green = machine.PWM(machine.Pin(19))
        self.led_blue = machine.PWM(machine.Pin(21))
        self.status_led = machine.Pin(2, machine.Pin.OUT)

        # Set PWM frequency
        self.led_red.freq(1000)
        self.led_green.freq(1000)
        self.led_blue.freq(1000)

        # Buzzer setup
        self.buzzer = machine.PWM(machine.Pin(22))
        self.buzzer.duty(0)

        # Button setup
        self.btn_mode = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
        self.btn_action = machine.Pin(5, machine.Pin.IN, machine.Pin.PULL_UP)
        self.btn_power = machine.Pin(23, machine.Pin.IN, machine.Pin.PULL_UP)

        # State variables
        self.current_mode = 0
        self.power_on = True
        self.brightness = 512  # 50% brightness
        self.color_index = 0

        # Mode definitions
        self.modes = [
            "Manual Color",
            "Auto Rainbow", 
            "Breathing Effect",
            "Party Mode",
            "Alarm Mode"
        ]

        # Colors (R, G, B) dalam duty cycle 0-1023
        self.colors = [
            (1023, 0, 0),      # Red
            (0, 1023, 0),      # Green
            (0, 0, 1023),      # Blue
            (1023, 1023, 0),   # Yellow
            (1023, 0, 1023),   # Magenta
            (0, 1023, 1023),   # Cyan
            (1023, 512, 0),    # Orange
            (1023, 1023, 1023) # White
        ]

        self.color_names = [
            "Red", "Green", "Blue", "Yellow", 
            "Magenta", "Cyan", "Orange", "White"
        ]

        # Debouncing
        self.button_states = [1, 1, 1]
        self.last_button_times = [0, 0, 0]
        self.debounce_ms = 200

        # Sound frequencies
        self.beep_freq = 1000
        self.alarm_freq = 800

        print("Smart LED Controller initialized!")
        self.show_status()

    def play_beep(self, duration_ms=100):
        """Mainkan beep pendek"""
        self.buzzer.freq(self.beep_freq)
        self.buzzer.duty(256)
        utime.sleep_ms(duration_ms)
        self.buzzer.duty(0)

    def play_alarm(self, duration_ms=2000):
        """Mainkan alarm sound"""
        alarm_duration = duration_ms // 200  # 200ms per cycle

        for i in range(alarm_duration):
            # High tone
            self.buzzer.freq(self.alarm_freq)
            self.buzzer.duty(256)
            utime.sleep_ms(100)

            # Low tone
            self.buzzer.freq(self.alarm_freq // 2)
            self.buzzer.duty(256)
            utime.sleep_ms(100)

        self.buzzer.duty(0)

    def set_rgb_color(self, red, green, blue):
        """Set warna RGB LED"""
        if not self.power_on:
            red = green = blue = 0

        # Apply brightness scaling
        brightness_factor = self.brightness / 1023.0

        self.led_red.duty(int(red * brightness_factor))
        self.led_green.duty(int(green * brightness_factor))
        self.led_blue.duty(int(blue * brightness_factor))

    def set_color_by_index(self, index):
        """Set warna berdasarkan index"""
        if 0 <= index < len(self.colors):
            r, g, b = self.colors[index]
            self.set_rgb_color(r, g, b)
            return self.color_names[index]
        return "Unknown"

    def all_leds_off(self):
        """Matikan semua LED"""
        self.set_rgb_color(0, 0, 0)

    def check_buttons(self):
        """Check semua button dengan debouncing"""
        current_time = utime.ticks_ms()
        buttons = [self.btn_mode, self.btn_action, self.btn_power]
        pressed_buttons = []

        for i, button in enumerate(buttons):
            current_state = button.value()

            # Detect button press (falling edge)
            if current_state == 0 and self.button_states[i] == 1:
                if utime.ticks_diff(current_time, self.last_button_times[i]) > self.debounce_ms:
                    self.button_states[i] = current_state
                    self.last_button_times[i] = current_time
                    pressed_buttons.append(i)

            # Update state
            if current_state != self.button_states[i]:
                if utime.ticks_diff(current_time, self.last_button_times[i]) > self.debounce_ms:
                    self.button_states[i] = current_state
                    self.last_button_times[i] = current_time

        return pressed_buttons

    def show_status(self):
        """Tampilkan status saat ini"""
        print(f"\n=== Smart LED Controller Status ===")
        print(f"Power: {'ON' if self.power_on else 'OFF'}")
        print(f"Mode: {self.modes[self.current_mode]}")
        print(f"Brightness: {int((self.brightness/1023)*100)}%")
        if self.current_mode == 0:
            print(f"Current Color: {self.color_names[self.color_index]}")
        print("Buttons: [Mode] [Action] [Power]")
        print("="*38)

    def mode_manual_color(self):
        """Mode: Manual color selection"""
        color_name = self.set_color_by_index(self.color_index)
        print(f"Manual Mode - Color: {color_name}")

    def mode_auto_rainbow(self):
        """Mode: Auto rainbow cycle"""
        print("Auto Rainbow Mode - Cycling colors...")

        for i in range(len(self.colors)):
            if not self.power_on:
                break

            color_name = self.set_color_by_index(i)
            print(f"Rainbow: {color_name}")

            # Check for button press during animation
            for _ in range(50):  # 500ms total, check every 10ms
                if self.check_buttons():
                    return  # Exit if button pressed
                utime.sleep_ms(10)

    def mode_breathing_effect(self):
        """Mode: Breathing effect dengan warna saat ini"""
        print("Breathing Effect Mode")

        base_color = self.colors[self.color_index]

        # Breathing cycle
        for brightness in list(range(0, 1023, 50)) + list(range(1023, 0, -50)):
            if not self.power_on:
                break

            # Scale color berdasarkan breathing brightness
            factor = brightness / 1023.0
            r = int(base_color[0] * factor)
            g = int(base_color[1] * factor)
            b = int(base_color[2] * factor)

            self.set_rgb_color(r, g, b)

            # Check for button press
            if self.check_buttons():
                return

            utime.sleep_ms(30)

    def mode_party_mode(self):
        """Mode: Party mode dengan random colors dan sounds"""
        print("Party Mode - Random colors and sounds!")

        # Random color
        random_r = urandom.randint(0, 1023)
        random_g = urandom.randint(0, 1023)
        random_b = urandom.randint(0, 1023)

        self.set_rgb_color(random_r, random_g, random_b)

        # Random beep
        if urandom.randint(0, 3) == 0:  # 25% chance untuk beep
            beep_freq = urandom.randint(500, 2000)
            self.buzzer.freq(beep_freq)
            self.buzzer.duty(128)
            utime.sleep_ms(50)
            self.buzzer.duty(0)

        utime.sleep_ms(200)

    def mode_alarm_mode(self):
        """Mode: Alarm dengan LED merah berkedip dan suara"""
        print("ALARM MODE ACTIVATED!")

        # Flash red LED
        if utime.ticks_ms() % 500 < 250:
            self.set_rgb_color(1023, 0, 0)  # Red
        else:
            self.set_rgb_color(0, 0, 0)     # Off

        # Alarm sound setiap 2 detik
        if utime.ticks_ms() % 2000 < 100:
            self.buzzer.freq(self.alarm_freq)
            self.buzzer.duty(256)
        else:
            self.buzzer.duty(0)

    def handle_mode_button(self):
        """Handle mode button press"""
        self.current_mode = (self.current_mode + 1) % len(self.modes)
        self.play_beep()
        print(f"Mode changed to: {self.modes[self.current_mode]}")
        self.show_status()

    def handle_action_button(self):
        """Handle action button press"""
        self.play_beep()

        if self.current_mode == 0:  # Manual color mode
            self.color_index = (self.color_index + 1) % len(self.colors)
            color_name = self.color_names[self.color_index]
            print(f"Color changed to: {color_name}")

        elif self.current_mode == 1:  # Auto rainbow
            print("Rainbow speed increased!")

        elif self.current_mode == 2:  # Breathing
            self.color_index = (self.color_index + 1) % len(self.colors)
            color_name = self.color_names[self.color_index]
            print(f"Breathing color changed to: {color_name}")

        elif self.current_mode == 3:  # Party mode
            print("Party intensity increased!")

        elif self.current_mode == 4:  # Alarm mode
            print("Alarm acknowledged!")

    def handle_power_button(self):
        """Handle power button press"""
        self.power_on = not self.power_on
        self.status_led.value(1 if self.power_on else 0)

        if self.power_on:
            self.play_beep()
            print("System POWERED ON")
        else:
            self.play_beep(200)
            self.all_leds_off()
            self.buzzer.duty(0)
            print("System POWERED OFF")

        self.show_status()

    def run_current_mode(self):
        """Jalankan mode yang sedang aktif"""
        if not self.power_on:
            self.all_leds_off()
            return

        if self.current_mode == 0:
            self.mode_manual_color()
        elif self.current_mode == 1:
            self.mode_auto_rainbow()
        elif self.current_mode == 2:
            self.mode_breathing_effect()
        elif self.current_mode == 3:
            self.mode_party_mode()
        elif self.current_mode == 4:
            self.mode_alarm_mode()

    def run_controller(self):
        """Main control loop"""
        print("=== Smart LED Controller Started ===")
        self.status_led.on()  # Power indicator
        self.play_beep()

        try:
            while True:
                # Check button presses
                pressed_buttons = self.check_buttons()

                for button_index in pressed_buttons:
                    if button_index == 0:    # Mode button
                        self.handle_mode_button()
                    elif button_index == 1:  # Action button
                        self.handle_action_button()
                    elif button_index == 2:  # Power button
                        self.handle_power_button()

                # Run current mode
                self.run_current_mode()

                utime.sleep_ms(10)

        except KeyboardInterrupt:
            print("\nController stopped by user")
        finally:
            self.cleanup()

    def cleanup(self):
        """Cleanup semua resources"""
        self.all_leds_off()
        self.buzzer.duty(0)
        self.status_led.off()

        # Deinitialize PWM
        self.led_red.deinit()
        self.led_green.deinit()
        self.led_blue.deinit()
        self.buzzer.deinit()

        print("Smart Controller cleaned up!")

# Test smart controller
if __name__ == "__main__":
    controller = SmartLEDController()
    controller.run_controller()

Langkah 3: Upload dan Test

  1. Upload file ke ESP32
  2. Pastikan semua hardware terhubung dengan benar
  3. Jalankan dengan: exec(open('smart_controller.py').read())

6.2 Troubleshooting dan Best Practices

Panduan untuk mengatasi masalah umum dan best practices dalam pengembangan aplikasi GPIO.

Praktikum 6.2: Diagnostic Tool

Langkah 1: Buat File diagnostic.py

# diagnostic.py - Diagnostic tool untuk GPIO ESP32
import machine
import utime
import gc

class ESP32Diagnostic:
    """Tool diagnostic untuk ESP32 GPIO"""

    def __init__(self):
        """Inisialisasi diagnostic tool"""
        self.test_pins = [2, 4, 5, 18, 19, 21, 22, 23]
        self.results = {}
        print("ESP32 GPIO Diagnostic Tool initialized")

    def test_pin_output(self, pin_num):
        """Test pin sebagai output"""
        try:
            pin = machine.Pin(pin_num, machine.Pin.OUT)

            # Test digital output
            pin.on()
            utime.sleep_ms(100)
            pin.off()

            # Test PWM (jika mendukung)
            try:
                pwm = machine.PWM(pin)
                pwm.freq(1000)
                pwm.duty(512)
                utime.sleep_ms(100)
                pwm.duty(0)
                pwm.deinit()
                pwm_support = True
            except:
                pwm_support = False

            return {
                'status': 'OK',
                'digital_out': True,
                'pwm_support': pwm_support
            }

        except Exception as e:
            return {
                'status': 'ERROR',
                'error': str(e),
                'digital_out': False,
                'pwm_support': False
            }

    def test_pin_input(self, pin_num):
        """Test pin sebagai input"""
        try:
            # Test dengan pull-up
            pin_up = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_UP)
            val_up = pin_up.value()

            # Test dengan pull-down
            pin_down = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_DOWN)
            val_down = pin_down.value()

            return {
                'status': 'OK',
                'pull_up_value': val_up,
                'pull_down_value': val_down,
                'pull_resistors': True
            }

        except Exception as e:
            return {
                'status': 'ERROR',
                'error': str(e),
                'pull_resistors': False
            }

    def test_system_info(self):
        """Test informasi sistem"""
        try:
            info = {
                'chip_id': machine.unique_id().hex(),
                'frequency': machine.freq(),
                'free_memory': gc.mem_free(),
                'allocated_memory': gc.mem_alloc(),
            }

            # Test built-in sensors
            try:
                import esp32
                info['hall_sensor'] = esp32.hall_sensor()
                info['temperature'] = esp32.raw_temperature()
            except:
                info['built_in_sensors'] = 'Not available'

            return info

        except Exception as e:
            return {'error': str(e)}

    def run_full_diagnostic(self):
        """Jalankan diagnostic lengkap"""
        print("\n" + "="*50)
        print("         ESP32 GPIO DIAGNOSTIC REPORT")
        print("="*50)

        # System info
        print("\n--- System Information ---")
        sys_info = self.test_system_info()
        for key, value in sys_info.items():
            print(f"{key}: {value}")

        # GPIO tests
        print(f"\n--- GPIO Pin Tests ---")
        print("Testing pins: {}".format(self.test_pins))

        for pin in self.test_pins:
            print(f"\nTesting GPIO{pin}:")

            # Test as output
            output_result = self.test_pin_output(pin)
            print(f"  Output Test: {output_result['status']}")
            if output_result['status'] == 'OK':
                print(f"    Digital Output: {'OK' if output_result['digital_out'] else 'FAIL'}")
                print(f"    PWM Support: {'OK' if output_result['pwm_support'] else 'NO'}")
            else:
                print(f"    Error: {output_result.get('error', 'Unknown')}")

            # Test as input
            input_result = self.test_pin_input(pin)
            print(f"  Input Test: {input_result['status']}")
            if input_result['status'] == 'OK':
                print(f"    Pull-up value: {input_result['pull_up_value']}")
                print(f"    Pull-down value: {input_result['pull_down_value']}")
            else:
                print(f"    Error: {input_result.get('error', 'Unknown')}")

            self.results[pin] = {
                'output': output_result,
                'input': input_result
            }

        # Summary
        print(f"\n--- Summary ---")
        working_pins = []
        failed_pins = []

        for pin, result in self.results.items():
            if (result['output']['status'] == 'OK' and 
                result['input']['status'] == 'OK'):
                working_pins.append(pin)
            else:
                failed_pins.append(pin)

        print(f"Working pins: {working_pins}")
        print(f"Failed pins: {failed_pins}")
        print(f"Success rate: {len(working_pins)}/{len(self.test_pins)} ({len(working_pins)/len(self.test_pins)*100:.1f}%)")

        print("\n" + "="*50)
        print("Diagnostic complete!")

        return self.results

    def interactive_pin_test(self):
        """Test interaktif untuk pin tertentu"""
        print("\n=== Interactive Pin Test ===")

        while True:
            try:
                pin_input = input("Enter GPIO pin number to test (or 'q' to quit): ")

                if pin_input.lower() == 'q':
                    break

                pin_num = int(pin_input)

                print(f"\nTesting GPIO{pin_num}...")

                # Test output
                print("Testing as output (LED should blink if connected):")
                pin = machine.Pin(pin_num, machine.Pin.OUT)
                for i in range(3):
                    pin.on()
                    print(f"  ON ({i+1}/3)")
                    utime.sleep(0.5)
                    pin.off()
                    print(f"  OFF ({i+1}/3)")
                    utime.sleep(0.5)

                # Test input
                print("Testing as input (press button if connected):")
                pin = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_UP)

                print("Reading pin state for 5 seconds...")
                start_time = utime.ticks_ms()
                last_state = None

                while utime.ticks_diff(utime.ticks_ms(), start_time) < 5000:
                    current_state = pin.value()
                    if current_state != last_state:
                        print(f"  Pin state: {current_state}")
                        last_state = current_state
                    utime.sleep_ms(100)

                print("Pin test complete!\n")

            except ValueError:
                print("Invalid pin number!")
            except Exception as e:
                print(f"Error testing pin: {e}")

# Main diagnostic program
if __name__ == "__main__":
    diagnostic = ESP32Diagnostic()

    print("ESP32 GPIO Diagnostic Tool")
    print("1. Run full diagnostic")
    print("2. Interactive pin test")

    try:
        choice = input("Select option (1 or 2): ")

        if choice == "1":
            diagnostic.run_full_diagnostic()
        elif choice == "2":
            diagnostic.interactive_pin_test()
        else:
            print("Invalid choice")

    except:
        # Fallback jika input() tidak tersedia di REPL
        print("Running full diagnostic...")
        diagnostic.run_full_diagnostic()