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.

Video
Schema circuito

Codice sorgente
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)
182FAQ
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.
