#169Intermedio

Falò LED con Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potenziometro + Buzzer)

Un falò che non brucia e non scalda, ma scoppietta! Il **Raspberry Pi Pico** disegna su una **matrice NeoPixel 8x8** una fiamma realistica ricalcolata a ogni fotogramma: alla base nascono scintille, il calore si raffredda salendo e passa dal rosso al bianco. Il **potenziometro** regola l'altezza della fiamma, il **pulsante** passa tra i temi Fuoco, Gas, Magia e Ametista e il **buzzer** aggiunge lo scoppiettio della legna. Animazione procedurale, ADC, PWM e MicroPython in un solo progetto.

0.0(0)
Falò LED con Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potenziometro + Buzzer)
Un falò che sta sulla scrivania e non brucia niente. Sulla matrice di luci 8x8 le fiamme nascono da scintille in basso e salendo passano dal rosso al giallo al bianco; con una manopola rendi il fuoco più basso o più alto. Una pressione breve del pulsante colora la fiamma di blu, verde o viola, una lunga accende o spegne lo scoppiettio della legna che esce dal buzzer.

Video

Schema circuito

Falò LED con Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potenziometro + Buzzer) - Circuit

Codice sorgente

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Falò LED con Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potenziometro + Buzzer)
4# ─────────────────────────────────────────
5# Animazione realistica del fuoco su una matrice NeoPixel 8x8. Il
6# potenziometro imposta l'altezza della fiamma, una breve pressione del
7# pulsante cambia tema (Fuoco, Gas, Magia, Ametista) e una pressione lunga
8# attiva/disattiva il suono di crepitio.
9#
10# Pin:
11#   GP2  -> DIN della matrice (VDD -> VBUS, VSS -> GND)
12#   GP26 -> Cursore del potenziometro (ADC0)
13#   GP14 -> Pulsante (altro piedino a GND)
14#   GP17 -> Buzzer (+)
15
16from machine import Pin, PWM, ADC
17from neopixel import NeoPixel
18import time
19import random
20
21# ============ IMPOSTAZIONI ============
22W = 8                  # Colonne
23H = 8                  # Righe
24SERPENTINE = False     # True per i pannelli cablati a zigzag
25BRIGHTNESS = 0.4       # Wokwi disegna i colori così come sono, 0.4 mostra chiaramente la fiamma
26                       # 0.15-0.2 consigliato su USB 2.0 (500 mA), usa 5V esterni per una luminosità
27                       # maggiore
28FRAME_MS = 35          # Durata del frame (~28 FPS)
29LONG_PRESS_MS = 900    # Tempo di pressione per attivare/disattivare il suono
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# ============ STATO ============
39# heat[x][y]: colonna x, y = 0 è la riga in basso; 0 = freddo, 255 = il più caldo
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# ============ PALETTE DEI COLORI ============
47def heat_color(t):
48    # Mappare il calore (0-255) su un colore in base al tema: nero -> scuro -> luminoso -> bianco
49    t192 = (t * 191) // 255
50    ramp = (t192 & 0x3F) << 2      # Rampa 0-252 per banda
51    if t192 & 0x80:
52        r, g, b = 255, 255, ramp   # Il più caldo: giallo -> bianco
53    elif t192 & 0x40:
54        r, g, b = 255, ramp, 0     # Al centro: rosso -> giallo
55    else:
56        r, g, b = ramp, 0, 0       # Freddo: nero -> rosso
57    if theme == 1:
58        r, b = b, r                # Gas: fiamma blu
59    elif theme == 2:
60        r, g = g, r                # Magia: fiamma verde
61    elif theme == 3:
62        b = max(b, r * 3 // 4)     # Ametista: fiamma viola
63        g = g // 2
64    return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
65
66
67def pixel_index(x, row):
68    # riga 0 = riga in alto
69    if SERPENTINE and row % 2 == 1:
70        return row * W + (W - 1 - x)
71    return row * W + x
72
73
74# ============ SIMULAZIONE DEL FUOCO ============
75def fire_step(intensity):
76    # intensità 0-1: altezza della fiamma. Alta = meno raffreddamento + più scintille
77    cooling = int(115 - 55 * intensity)    # 115 (basso) -> 60 (alto)
78    sparking = int(70 + 150 * intensity)   # 70 -> 220 (probabilità)
79    max_cool = (cooling * 10) // H + 2
80
81    for x in range(W):
82        col = heat[x]
83        # Ogni cella si raffredda un po'
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        # Il calore sale e si diffonde
88        for y in range(H - 1, 1, -1):
89            col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
90        # Scintille casuali alla base
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) Fusione della luce con le colonne vicine: la fiamma ondeggia in modo più naturale
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# ============ SUONO DI CREPITIO ============
112def crackle(intensity):
113    # "Tic" casuali molto brevi = legna che crepita
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# ============ PULSANTE ============
131btn_state = 1
132btn_raw = 1
133btn_changed = time.ticks_ms()
134press_start = 0
135long_done = False
136
137
138def handle_button():
139    # Pressione breve = tema, pressione lunga = suono acceso/spento
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# ============ CICLO PRINCIPALE ============
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    # Smorzare il valore del potenziometro (il jitter non deve far saltare la fiamma)
173    smooth += (pot.read_u16() / 65535 - smooth) * 0.2
174    fire_step(smooth)
175    render()
176    crackle(smooth)
177
178    # Frame rate costante
179    spent = time.ticks_diff(time.ticks_ms(), start)
180    if spent < FRAME_MS:
181        time.sleep_ms(FRAME_MS - spent)
182

FAQ

Perché devo abbassare la luminosità nel circuito reale?▾

Il codice usa `BRIGHTNESS = 0.4` perché Wokwi disegna i colori così come sono. Una matrice reale può assorbire circa 1 A con la fiamma al massimo, mentre una porta USB 2.0 ne dà 500 mA. Via USB imposta 0.15–0.2; per più luce alimenta la matrice a 5V 2A e collega insieme le masse (GND).

Come cambio l'altezza e il colore della fiamma?▾

Gira il potenziometro a sinistra per le braci e a destra per fiamme che arrivano in cima alla matrice. Una pressione breve del pulsante passa tra Fuoco, Gas, Magia e Ametista. Su Wokwi clicca sul potenziometro e usa le frecce; il tema si cambia con il tasto T.

Come spengo lo scoppiettio o lo rendo più raro?▾

Tieni premuto il pulsante per circa un secondo: un bip acuto significa suono attivo, uno grave suono spento. Per uno scoppiettio più raro riduci il 10 e il 50 in `10 + 50 * intensity` dentro `crackle()`. Per il suono serve un buzzer passivo.

Devo installare qualche libreria?▾

No. I moduli `neopixel`, `machine` e `random` sono già inclusi in MicroPython per il Pico. Installa MicroPython sul Pico e salva il codice su di esso come `main.py` con Thonny; il falò partirà da solo a ogni accensione.

Falò LED con Raspberry Pi Pico e matrice NeoPixel 8x8 | BlueGrays