#169Menengah

Api Unggun LED Raspberry Pi Pico (Matriks NeoPixel 8x8 + Potensiometer + Buzzer)

Api unggun yang tidak membakar apa pun, tetapi tetap berderak! Raspberry Pi Pico menggambar nyala api yang realistis di matriks NeoPixel 8x8, dihitung ulang setiap frame: percikan muncul di dasar, panasnya mendingin ketika naik, dan berpendar dari merah sampai putih. Potensiometer mengatur tinggi api, tombol mengganti tema Api, Gas, Sihir, dan Ametis, dan buzzer menambahkan derak kayu terbakar. Animasi prosedural, ADC, PWM, dan MicroPython dalam satu proyek.

0.0(0)
1 selesai
Api Unggun LED Raspberry Pi Pico (Matriks NeoPixel 8x8 + Potensiometer + Buzzer)
Api unggun yang muat di mejamu dan tidak pernah membakar apa pun. Di matriks lampu 8x8, nyala api lahir dari percikan di bagian bawah dan berubah dari merah ke kuning lalu putih ketika naik, dan kamu memutar kenop untuk membuat apinya lebih kecil atau lebih besar. Tekanan singkat tombol mengubah api menjadi biru, hijau, atau ungu, dan tekanan panjang menyalakan atau mematikan bunyi derak kayu dari buzzer.

Video

Skema rangkaian

Api Unggun LED Raspberry Pi Pico (Matriks NeoPixel 8x8 + Potensiometer + Buzzer) - Circuit

Kode sumber

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Api Unggun LED Raspberry Pi Pico (Matriks NeoPixel 8x8 + Potensiometer + Buzzer)
4# ─────────────────────────────────────────
5# Animasi api realistis di matriks NeoPixel 8x8. Potensiometer mengatur
6# tinggi api, tekan tombol singkat mengganti tema (Fire, Gas, Magic,
7# Amethyst) dan tekan lama menyalakan/mematikan suara gemeretak.
8#
9# Pin:
10#   GP2  -> DIN matriks (VDD -> VBUS, VSS -> GND)
11#   GP26 -> Wiper potensiometer (ADC0)
12#   GP14 -> Tombol (kaki lainnya ke GND)
13#   GP17 -> Buzzer (+)
14
15from machine import Pin, PWM, ADC
16from neopixel import NeoPixel
17import time
18import random
19
20# ============ PENGATURAN ============
21W = 8                  # Kolom
22H = 8                  # Baris
23SERPENTINE = False     # True untuk panel dengan kabel zigzag
24BRIGHTNESS = 0.4       # Wokwi menggambar warna apa adanya, 0.4 menampilkan api dengan jelas
25                       # 0.15-0.2 disarankan di USB 2.0 (500 mA), pakai 5V eksternal untuk yang
26                       # lebih terang
27FRAME_MS = 35          # Waktu frame (~28 FPS)
28LONG_PRESS_MS = 900    # Waktu tahan untuk menyalakan/mematikan suara
29
30# ============ HARDWARE ============
31matrix = NeoPixel(Pin(2), W * H)
32pot = ADC(26)                           # ADC0
33button = Pin(14, Pin.IN, Pin.PULL_UP)
34buzzer = PWM(Pin(17))
35buzzer.duty_u16(0)
36
37# ============ STATUS ============
38# heat[x][y]: kolom x, y = 0 adalah baris bawah; 0 = dingin, 255 = paling panas
39heat = [[0] * H for _ in range(W)]
40theme = 0
41THEME_NAMES = ["Ates / Fire", "Gaz / Gas", "Buyu / Magic", "Ametist / Amethyst"]
42sound_on = True
43
44
45# ============ PALET WARNA ============
46def heat_color(t):
47    # Petakan panas (0-255) ke warna sesuai tema: hitam -> gelap -> terang -> putih
48    t192 = (t * 191) // 255
49    ramp = (t192 & 0x3F) << 2      # Ramp 0-252 per pita
50    if t192 & 0x80:
51        r, g, b = 255, 255, ramp   # Paling panas: kuning -> putih
52    elif t192 & 0x40:
53        r, g, b = 255, ramp, 0     # Tengah: merah -> kuning
54    else:
55        r, g, b = ramp, 0, 0       # Dingin: hitam -> merah
56    if theme == 1:
57        r, b = b, r                # Gas: api biru
58    elif theme == 2:
59        r, g = g, r                # Magic: api hijau
60    elif theme == 3:
61        b = max(b, r * 3 // 4)     # Amethyst: api ungu
62        g = g // 2
63    return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
64
65
66def pixel_index(x, row):
67    # baris 0 = baris atas
68    if SERPENTINE and row % 2 == 1:
69        return row * W + (W - 1 - x)
70    return row * W + x
71
72
73# ============ SIMULASI API ============
74def fire_step(intensity):
75    # intensity 0-1: tinggi api. Tinggi = pendinginan lebih sedikit + percikan lebih banyak
76    cooling = int(115 - 55 * intensity)    # 115 (pendek) -> 60 (tinggi)
77    sparking = int(70 + 150 * intensity)   # 70 -> 220 (probabilitas)
78    max_cool = (cooling * 10) // H + 2
79
80    for x in range(W):
81        col = heat[x]
82        # Setiap sel mendingin sedikit
83        for y in range(H):
84            c = col[y] - random.getrandbits(8) % max_cool
85            col[y] = c if c > 0 else 0
86        # Panas naik ke atas dan menyebar
87        for y in range(H - 1, 1, -1):
88            col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
89        # Percikan acak di dasar
90        if random.getrandbits(8) < sparking:
91            y = random.getrandbits(1)
92            col[y] = min(255, col[y] + 160 + random.getrandbits(6))
93
94    # 4) Sedikit pembauran dengan kolom tetangga: api bergelombang lebih alami
95    for y in range(2, H):
96        row_vals = [heat[x][y] for x in range(W)]
97        for x in range(W):
98            left = row_vals[x - 1] if x > 0 else row_vals[x]
99            right = row_vals[x + 1] if x < W - 1 else row_vals[x]
100            heat[x][y] = (row_vals[x] * 2 + left + right) // 4
101
102
103def render():
104    for x in range(W):
105        for y in range(H):
106            matrix[pixel_index(x, H - 1 - y)] = heat_color(heat[x][y])
107    matrix.write()
108
109
110# ============ SUARA GEMERETAK ============
111def crackle(intensity):
112    # "ticks" sangat pendek secara acak = kayu yang bergemeretak
113    if not sound_on:
114        return
115    if random.getrandbits(8) < int(10 + 50 * intensity):
116        buzzer.freq(1500 + random.getrandbits(12))   # 1500-5595 Hz
117        buzzer.duty_u16(6000)
118        time.sleep_ms(2 + random.getrandbits(2))
119        buzzer.duty_u16(0)
120
121
122def beep(freq, ms):
123    buzzer.freq(freq)
124    buzzer.duty_u16(20000)
125    time.sleep_ms(ms)
126    buzzer.duty_u16(0)
127
128
129# ============ TOMBOL ============
130btn_state = 1
131btn_raw = 1
132btn_changed = time.ticks_ms()
133press_start = 0
134long_done = False
135
136
137def handle_button():
138    # Tekan singkat = tema, tekan lama = suara nyala/mati
139    global btn_state, btn_raw, btn_changed, press_start, long_done, theme, sound_on
140    now = time.ticks_ms()
141    raw = button.value()
142    if raw != btn_raw:
143        btn_raw = raw
144        btn_changed = now
145    if time.ticks_diff(now, btn_changed) > 25 and raw != btn_state:
146        btn_state = raw
147        if raw == 0:
148            press_start = now
149            long_done = False
150        elif not long_done:
151            theme = (theme + 1) % len(THEME_NAMES)
152            print("Tema / Theme:", THEME_NAMES[theme])
153            beep(1200, 30)
154    if btn_state == 0 and not long_done and time.ticks_diff(now, press_start) > LONG_PRESS_MS:
155        long_done = True
156        sound_on = not sound_on
157        print("Ses / Sound:", "ACIK / ON" if sound_on else "KAPALI / OFF")
158        beep(1600 if sound_on else 500, 80)
159
160
161# ============ LOOP UTAMA ============
162print("== BlueGrays · bluegrays.com ==")
163print("Pico 8x8 Kamp Atesi / Campfire")
164print("Pot = alev boyu / flame height | Kisa basis = tema / short press = theme | Uzun basis = ses / long press = sound")
165
166smooth = pot.read_u16() / 65535
167while True:
168    start = time.ticks_ms()
169    handle_button()
170
171    # Haluskan nilai pot (getaran nilai tidak boleh membuat api melompat)
172    smooth += (pot.read_u16() / 65535 - smooth) * 0.2
173    fire_step(smooth)
174    render()
175    crackle(smooth)
176
177    # Frame rate konstan
178    spent = time.ticks_diff(time.ticks_ms(), start)
179    if spent < FRAME_MS:
180        time.sleep_ms(FRAME_MS - spent)
181

FAQ

Kenapa aku harus menurunkan kecerahan di rangkaian sungguhan?

Kode memakai `BRIGHTNESS = 0.4` karena Wokwi menggambar warna persis seperti yang dikirim. Matriks sungguhan bisa menarik sekitar 1 A dengan api besar, sementara port USB 2.0 hanya menyuplai 500 mA. Dengan USB, atur ke 0,15–0,2; untuk kecerahan lebih tinggi, beri daya matriks dari adaptor 5V 2A dan sambungkan ground-nya.

Bagaimana cara mengubah tinggi dan warna api?

Putar potensiometer ke kiri untuk bara yang berpendar dan ke kanan untuk api yang mencapai puncak matriks. Tekanan singkat tombol berganti antara Api, Gas, Sihir, dan Ametis. Di Wokwi, klik potensiometer dan pakai tombol panah, lalu tekan T untuk mengganti tema.

Bagaimana cara mematikan bunyi derak atau membuatnya lebih jarang?

Tahan tombol sekitar satu detik: bunyi bip tinggi berarti suara menyala, bunyi bip rendah berarti mati. Untuk derak yang lebih jarang, turunkan angka 10 dan 50 di `10 + 50 * intensity` di dalam `crackle()`. Pakai buzzer pasif untuk suaranya.

Apakah aku perlu menginstal library?

Tidak. Modul `neopixel`, `machine`, dan `random` sudah ikut dalam MicroPython untuk Pico. Pasang MicroPython di Pico dan simpan kodenya sebagai `main.py` di Thonny; api lalu menyala sendiri setiap kali Pico dinyalakan.