#169Mittel

Raspberry Pi Pico LED-Lagerfeuer (8x8-NeoPixel-Matrix + Potentiometer + Summer)

Ein Lagerfeuer, das weder brennt noch wärmt, aber knistert! Der **Raspberry Pi Pico** zeichnet auf einer **8x8-NeoPixel-Matrix** eine realistische Flamme, die in jedem Frame neu entsteht: Am Boden sprühen Funken, die Hitze kühlt nach oben ab und glüht von Rot bis Weiß. Das **Potentiometer** regelt die Flammenhöhe, der **Taster** wechselt zwischen Feuer, Gas, Magie und Amethyst, der **Summer** liefert das Knistern von Holz. Prozedurale Animation, ADC, PWM und MicroPython in einem Projekt.

0.0(0)
Raspberry Pi Pico LED-Lagerfeuer (8x8-NeoPixel-Matrix + Potentiometer + Summer)
Ein Lagerfeuer, das auf deinen Schreibtisch passt und nichts anbrennt. Auf der 8x8-Lichtmatrix entstehen die Flammen unten aus Funken und wechseln beim Aufsteigen von Rot über Gelb zu Weiß; mit einem Drehknopf machst du das Feuer kleiner oder größer. Ein kurzer Druck auf den Knopf färbt die Flamme blau, grün oder lila, ein langer schaltet das Knistern von Holz aus dem Summer ein oder aus.

Video

Schaltplan

Raspberry Pi Pico LED-Lagerfeuer (8x8-NeoPixel-Matrix + Potentiometer + Summer) - Circuit

Quellcode

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Raspberry Pi Pico LED-Lagerfeuer (8x8-NeoPixel-Matrix + Potentiometer + Summer)
4# ─────────────────────────────────────────
5# Realistische Feueranimation auf einer 8x8-NeoPixel-Matrix. Das
6# Potentiometer stellt die Flammenhöhe ein, ein kurzer Tastendruck
7# wechselt das Thema (Feuer, Gas, Magie, Amethyst) und ein langer Druck
8# schaltet den Knistersound um.
9#
10# Pins:
11#   GP2  -> Matrix DIN (VDD -> VBUS, VSS -> GND)
12#   GP26 -> Schleifer des Potentiometers (ADC0)
13#   GP14 -> Taste (anderes Beinchen an GND)
14#   GP17 -> Buzzer (+)
15
16from machine import Pin, PWM, ADC
17from neopixel import NeoPixel
18import time
19import random
20
21# ============ EINSTELLUNGEN ============
22W = 8                  # Spalten
23H = 8                  # Zeilen
24SERPENTINE = False     # True für im Zickzack verdrahtete Panels
25BRIGHTNESS = 0.4       # Wokwi zeichnet Farben unverändert, 0.4 zeigt die Flamme deutlich
26                       # 0.15-0.2 empfohlen an USB 2.0 (500 mA), für mehr Helligkeit externes 5V
27                       # verwenden
28FRAME_MS = 35          # Frame-Zeit (~28 FPS)
29LONG_PRESS_MS = 900    # Haltezeit zum Umschalten des Sounds
30
31# ============ HARDWARE ============
32matrix = NeoPixel(Pin(2), W * H)
33pot = ADC(26)                           # ADC0
34button = Pin(14, Pin.IN, Pin.PULL_UP)
35buzzer = PWM(Pin(17))
36buzzer.duty_u16(0)
37
38# ============ ZUSTAND ============
39# heat[x][y]: Spalte x, y = 0 ist die unterste Reihe; 0 = kalt, 255 = am heißesten
40heat = [[0] * H for _ in range(W)]
41theme = 0
42THEME_NAMES = ["Ates / Fire", "Gaz / Gas", "Buyu / Magic", "Ametist / Amethyst"]
43sound_on = True
44
45
46# ============ FARBPALETTE ============
47def heat_color(t):
48    # Hitze (0-255) je nach Thema auf eine Farbe abbilden: schwarz -> tief -> hell -> weiß
49    t192 = (t * 191) // 255
50    ramp = (t192 & 0x3F) << 2      # 0-252 Rampe pro Band
51    if t192 & 0x80:
52        r, g, b = 255, 255, ramp   # Am heißesten: Gelb -> Weiß
53    elif t192 & 0x40:
54        r, g, b = 255, ramp, 0     # Mitte: Rot -> Gelb
55    else:
56        r, g, b = ramp, 0, 0       # Kalt: Schwarz -> Rot
57    if theme == 1:
58        r, b = b, r                # Gas: blaue Flamme
59    elif theme == 2:
60        r, g = g, r                # Magie: grüne Flamme
61    elif theme == 3:
62        b = max(b, r * 3 // 4)     # Amethyst: lila Flamme
63        g = g // 2
64    return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
65
66
67def pixel_index(x, row):
68    # Zeile 0 = oberste Zeile
69    if SERPENTINE and row % 2 == 1:
70        return row * W + (W - 1 - x)
71    return row * W + x
72
73
74# ============ FEUERSIMULATION ============
75def fire_step(intensity):
76    # Intensität 0-1: Flammenhöhe. Hoch = weniger Kühlung + mehr Funken
77    cooling = int(115 - 55 * intensity)    # 115 (kurz) -> 60 (hoch)
78    sparking = int(70 + 150 * intensity)   # 70 -> 220 (Wahrscheinlichkeit)
79    max_cool = (cooling * 10) // H + 2
80
81    for x in range(W):
82        col = heat[x]
83        # Jede Zelle kühlt etwas ab
84        for y in range(H):
85            c = col[y] - random.getrandbits(8) % max_cool
86            col[y] = c if c > 0 else 0
87        # Wärme driftet nach oben und breitet sich aus
88        for y in range(H - 1, 1, -1):
89            col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
90        # Zufällige Funken an der Basis
91        if random.getrandbits(8) < sparking:
92            y = random.getrandbits(1)
93            col[y] = min(255, col[y] + 160 + random.getrandbits(6))
94
95    # 4) Lichtmischung mit Nachbarspalten: Die Flamme wogt natürlicher
96    for y in range(2, H):
97        row_vals = [heat[x][y] for x in range(W)]
98        for x in range(W):
99            left = row_vals[x - 1] if x > 0 else row_vals[x]
100            right = row_vals[x + 1] if x < W - 1 else row_vals[x]
101            heat[x][y] = (row_vals[x] * 2 + left + right) // 4
102
103
104def render():
105    for x in range(W):
106        for y in range(H):
107            matrix[pixel_index(x, H - 1 - y)] = heat_color(heat[x][y])
108    matrix.write()
109
110
111# ============ KNISTERSOUND ============
112def crackle(intensity):
113    # Zufällige sehr kurze "Ticks" = knisterndes Holz
114    if not sound_on:
115        return
116    if random.getrandbits(8) < int(10 + 50 * intensity):
117        buzzer.freq(1500 + random.getrandbits(12))   # 1500-5595 Hz
118        buzzer.duty_u16(6000)
119        time.sleep_ms(2 + random.getrandbits(2))
120        buzzer.duty_u16(0)
121
122
123def beep(freq, ms):
124    buzzer.freq(freq)
125    buzzer.duty_u16(20000)
126    time.sleep_ms(ms)
127    buzzer.duty_u16(0)
128
129
130# ============ TASTE ============
131btn_state = 1
132btn_raw = 1
133btn_changed = time.ticks_ms()
134press_start = 0
135long_done = False
136
137
138def handle_button():
139    # Kurzer Druck = Thema, langer Druck = Sound an/aus
140    global btn_state, btn_raw, btn_changed, press_start, long_done, theme, sound_on
141    now = time.ticks_ms()
142    raw = button.value()
143    if raw != btn_raw:
144        btn_raw = raw
145        btn_changed = now
146    if time.ticks_diff(now, btn_changed) > 25 and raw != btn_state:
147        btn_state = raw
148        if raw == 0:
149            press_start = now
150            long_done = False
151        elif not long_done:
152            theme = (theme + 1) % len(THEME_NAMES)
153            print("Tema / Theme:", THEME_NAMES[theme])
154            beep(1200, 30)
155    if btn_state == 0 and not long_done and time.ticks_diff(now, press_start) > LONG_PRESS_MS:
156        long_done = True
157        sound_on = not sound_on
158        print("Ses / Sound:", "ACIK / ON" if sound_on else "KAPALI / OFF")
159        beep(1600 if sound_on else 500, 80)
160
161
162# ============ HAUPTSCHLEIFE ============
163print("== BlueGrays · bluegrays.com ==")
164print("Pico 8x8 Kamp Atesi / Campfire")
165print("Pot = alev boyu / flame height | Kisa basis = tema / short press = theme | Uzun basis = ses / long press = sound")
166
167smooth = pot.read_u16() / 65535
168while True:
169    start = time.ticks_ms()
170    handle_button()
171
172    # Den Potiwert glätten (Jitter soll die Flamme nicht springen lassen)
173    smooth += (pot.read_u16() / 65535 - smooth) * 0.2
174    fire_step(smooth)
175    render()
176    crackle(smooth)
177
178    # Konstante Bildrate
179    spent = time.ticks_diff(time.ticks_ms(), start)
180    if spent < FRAME_MS:
181        time.sleep_ms(FRAME_MS - spent)
182

FAQ

Warum sollte ich die Helligkeit in der echten Schaltung senken?▾

Im Code steht `BRIGHTNESS = 0.4`, weil Wokwi die Farben unverändert zeichnet. Eine echte Matrix zieht bei großer Flamme rund 1 A, ein USB-2.0-Port liefert nur 500 mA. Über USB also 0.15–0.2 einstellen; für mehr Helligkeit die Matrix mit einem 5-V-Netzteil (2 A) versorgen und die GNDs verbinden.

Wie ändere ich Flammenhöhe und Farbe?▾

Dreh das Potentiometer nach links für Glut und nach rechts für Flammen bis zur obersten Reihe. Ein kurzer Tastendruck wechselt zwischen Feuer, Gas, Magie und Amethyst. In Wokwi klickst du auf das Poti und nutzt die Pfeiltasten; das Thema wechselst du mit T.

Wie schalte ich das Knistern aus oder mache es seltener?▾

Halte den Taster etwa eine Sekunde gedrückt: Ein hoher Piepton heißt Ton an, ein tiefer heißt Ton aus. Für seltenere Klicks verkleinerst du die Zahlen 10 und 50 im Ausdruck `10 + 50 * intensity` in `crackle()`. Für den Ton brauchst du einen passiven Summer.

Muss ich zusätzliche Bibliotheken installieren?▾

Nein. Die Module `neopixel`, `machine` und `random` sind in MicroPython für den Pico bereits enthalten. Spiele MicroPython auf den Pico und speichere den Code in Thonny als `main.py` darauf; dann startet das Feuer bei jedem Einschalten von selbst.

LED-Lagerfeuer mit Raspberry Pi Pico und 8x8-NeoPixel-Matrix | BlueGrays