Raspberry Pi Pico LED Kamp Ateşi (8x8 NeoPixel Matris + Potansiyometre + Buzzer)
Yakmayan, ısıtmayan ama çıtırdayan bir kamp ateşi! **Raspberry Pi Pico**, **8x8 NeoPixel matriste** her karede yeniden hesaplanan gerçekçi bir alev çizer: tabanda kıvılcımlar çıkar, ısı yükselirken soğur ve kırmızıdan beyaza renk alır. **Potansiyometre** alevin boyunu ayarlar, **buton** Ateş, Gaz, Büyü ve Ametist temaları arasında geçer, **buzzer** odun çıtırtısı sesi verir. Prosedürel animasyon, ADC, PWM ve MicroPython tek projede.

Video
Devre Şeması

Kaynak Kodu
1# ─────────────────────────────────────────
2# BlueGrays · bluegrays.com
3# Raspberry Pi Pico LED Kamp Ateşi (8x8 NeoPixel Matris + Potansiyometre + Buzzer)
4# ─────────────────────────────────────────
5# 8x8 NeoPixel matriste gerçekçi ateş animasyonu. Potansiyometre alevin
6# boyunu ayarlar, kısa buton basışı renk temasını değiştirir (Ateş, Gaz,
7# Büyü, Ametist), uzun basış çıtırtı sesini açar/kapatır.
8#
9# Pinler:
10# GP2 -> 8x8 matris DIN
11# GP26 -> Potansiyometre orta bacak (ADC0)
12# GP14 -> Buton (diğer ucu GND)
13# GP17 -> Buzzer (+)
14
15from machine import Pin, PWM, ADC
16from neopixel import NeoPixel
17import time
18import random
19
20# ============ AYARLAR ============
21W = 8 # Sütun
22H = 8 # Satır
23SERPENTINE = False # Zigzag kablolu panel ise True
24BRIGHTNESS = 0.4 # 0-1; Wokwi renkleri birebir çizer, 0.4 alevi net gösterir
25 # USB 2.0 (500 mA) ile 0.15-0.2 önerilir, daha parlak için harici 5V
26FRAME_MS = 35 # Kare süresi (~28 FPS)
27LONG_PRESS_MS = 900 # Ses aç/kapat için basılı tutma
28
29# ============ DONANIM ============
30matrix = NeoPixel(Pin(2), W * H)
31pot = ADC(26) # ADC0
32button = Pin(14, Pin.IN, Pin.PULL_UP)
33buzzer = PWM(Pin(17))
34buzzer.duty_u16(0)
35
36# ============ DURUM ============
37# heat[x][y]: sütun x, y = 0 en alt satır; 0 = soğuk, 255 = en sıcak
38heat = [[0] * H for _ in range(W)]
39theme = 0
40THEME_NAMES = ["Ates / Fire", "Gaz / Gas", "Buyu / Magic", "Ametist / Amethyst"]
41sound_on = True
42
43
44# ============ RENK PALETİ ============
45def heat_color(t):
46 # Isıyı (0-255) temaya göre renge çevir: siyah -> koyu -> parlak -> beyaz
47 t192 = (t * 191) // 255
48 ramp = (t192 & 0x3F) << 2 # Her bölgede 0-252 rampa
49 if t192 & 0x80:
50 r, g, b = 255, 255, ramp # En sıcak: sarı -> beyaz
51 elif t192 & 0x40:
52 r, g, b = 255, ramp, 0 # Orta: kırmızı -> sarı
53 else:
54 r, g, b = ramp, 0, 0 # Soğuk: siyah -> kırmızı
55 if theme == 1:
56 r, b = b, r # Gaz: mavi alev
57 elif theme == 2:
58 r, g = g, r # Büyü: yeşil alev
59 elif theme == 3:
60 b = max(b, r * 3 // 4) # Ametist: mor alev
61 g = g // 2
62 return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
63
64
65def pixel_index(x, row):
66 # row 0 = üst satır
67 if SERPENTINE and row % 2 == 1:
68 return row * W + (W - 1 - x)
69 return row * W + x
70
71
72# ============ ATEŞ SİMÜLASYONU ============
73def fire_step(intensity):
74 # intensity 0-1: alev boyu. Yüksek = az soğutma + çok kıvılcım
75 cooling = int(115 - 55 * intensity) # 115 (kısa alev) -> 60 (uzun)
76 sparking = int(70 + 150 * intensity) # 70 -> 220 (/255 olasılık)
77 max_cool = (cooling * 10) // H + 2
78
79 for x in range(W):
80 col = heat[x]
81 # 1) Her hücre biraz soğur
82 for y in range(H):
83 c = col[y] - random.getrandbits(8) % max_cool
84 col[y] = c if c > 0 else 0
85 # 2) Isı yukarı taşınır ve yayılır
86 for y in range(H - 1, 1, -1):
87 col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
88 # 3) Tabanda rastgele kıvılcım
89 if random.getrandbits(8) < sparking:
90 y = random.getrandbits(1)
91 col[y] = min(255, col[y] + 160 + random.getrandbits(6))
92
93 # 4) Komşu sütunlarla hafif karışım: alev daha doğal dalgalanır
94 for y in range(2, H):
95 row_vals = [heat[x][y] for x in range(W)]
96 for x in range(W):
97 left = row_vals[x - 1] if x > 0 else row_vals[x]
98 right = row_vals[x + 1] if x < W - 1 else row_vals[x]
99 heat[x][y] = (row_vals[x] * 2 + left + right) // 4
100
101
102def render():
103 for x in range(W):
104 for y in range(H):
105 matrix[pixel_index(x, H - 1 - y)] = heat_color(heat[x][y])
106 matrix.write()
107
108
109# ============ ÇITIRTI SESİ ============
110def crackle(intensity):
111 # Rastgele çok kısa "tik" sesleri = odun çıtırtısı
112 if not sound_on:
113 return
114 if random.getrandbits(8) < int(10 + 50 * intensity):
115 buzzer.freq(1500 + random.getrandbits(12)) # 1500-5595 Hz
116 buzzer.duty_u16(6000)
117 time.sleep_ms(2 + random.getrandbits(2))
118 buzzer.duty_u16(0)
119
120
121def beep(freq, ms):
122 buzzer.freq(freq)
123 buzzer.duty_u16(20000)
124 time.sleep_ms(ms)
125 buzzer.duty_u16(0)
126
127
128# ============ BUTON ============
129btn_state = 1
130btn_raw = 1
131btn_changed = time.ticks_ms()
132press_start = 0
133long_done = False
134
135
136def handle_button():
137 # Kısa basış = tema, uzun basış = ses aç/kapat
138 global btn_state, btn_raw, btn_changed, press_start, long_done, theme, sound_on
139 now = time.ticks_ms()
140 raw = button.value()
141 if raw != btn_raw:
142 btn_raw = raw
143 btn_changed = now
144 if time.ticks_diff(now, btn_changed) > 25 and raw != btn_state:
145 btn_state = raw
146 if raw == 0:
147 press_start = now
148 long_done = False
149 elif not long_done:
150 theme = (theme + 1) % len(THEME_NAMES)
151 print("Tema / Theme:", THEME_NAMES[theme])
152 beep(1200, 30)
153 if btn_state == 0 and not long_done and time.ticks_diff(now, press_start) > LONG_PRESS_MS:
154 long_done = True
155 sound_on = not sound_on
156 print("Ses / Sound:", "ACIK / ON" if sound_on else "KAPALI / OFF")
157 beep(1600 if sound_on else 500, 80)
158
159
160# ============ ANA DÖNGÜ ============
161print("== BlueGrays · bluegrays.com ==")
162print("Pico 8x8 Kamp Atesi / Campfire")
163print("Pot = alev boyu / flame height | Kisa basis = tema / short press = theme | Uzun basis = ses / long press = sound")
164
165smooth = pot.read_u16() / 65535
166while True:
167 start = time.ticks_ms()
168 handle_button()
169
170 # Pot değerini yumuşat (titreme alevi zıplatmasın)
171 smooth += (pot.read_u16() / 65535 - smooth) * 0.2
172 fire_step(smooth)
173 render()
174 crackle(smooth)
175
176 # Sabit kare hızı
177 spent = time.ticks_diff(time.ticks_ms(), start)
178 if spent < FRAME_MS:
179 time.sleep_ms(FRAME_MS - spent)
180Sıkça Sorulanlar
Gerçek devrede parlaklığı neden düşürmeliyim?▾
Koddaki `BRIGHTNESS = 0.4`, Wokwi renkleri olduğu gibi çizdiği için seçildi. Gerçek matris büyük alevde yaklaşık 1 A çekebilir, USB 2.0 portu ise 500 mA verir. USB'den besliyorsan 0.15–0.2 yap; daha parlak için matrisi 5V 2A adaptörden besle ve GND'leri birleştir.
Alevin boyunu ve rengini nasıl değiştiririm?▾
Potansiyometreyi sola çevirince yalnızca kor kalır, sağa çevirince alev matrisin tepesine yükselir. Butona kısa basış temayı Ateş, Gaz, Büyü ve Ametist arasında değiştirir. Wokwi'de pota tıklayıp ok tuşlarını, tema için T tuşunu kullan.
Çıtırtı sesini nasıl kapatırım ya da seyreltirim?▾
Butonu yaklaşık 1 saniye basılı tut: ince bip sesin açıldığını, kalın bip kapandığını gösterir. Çıtırtıyı seyreltmek için `crackle()` içindeki `10 + 50 * intensity` ifadesinde 10 ve 50'yi küçült. Ses için pasif buzzer kullan.
Kod için ek kütüphane kurmam gerekiyor mu?▾
Hayır. `neopixel`, `machine` ve `random` modülleri Pico'nun MicroPython sürümünde hazır gelir. Pico'ya MicroPython yükle, Thonny'de kodu Pico'ya `main.py` adıyla kaydet; Pico her açılışta ateşi kendiliğinden başlatır.
