#169Intermédiaire

Feu de camp LED avec Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potentiomètre + Buzzer)

Un feu de camp qui ne brûle pas, ne chauffe pas, mais crépite ! Le **Raspberry Pi Pico** dessine sur une **matrice NeoPixel 8x8** une flamme réaliste recalculée à chaque image : des étincelles naissent à la base, la chaleur refroidit en montant et passe du rouge au blanc. Le **potentiomètre** règle la hauteur de la flamme, le **bouton** alterne entre Feu, Gaz, Magie et Améthyste, et le **buzzer** ajoute le crépitement du bois. Animation procédurale, ADC, PWM et MicroPython dans un seul projet.

0.0(0)
Feu de camp LED avec Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potentiomètre + Buzzer)
Un feu de camp qui tient sur ton bureau et ne brûle rien. Sur la matrice lumineuse 8x8, les flammes naissent d'étincelles en bas et passent du rouge au jaune puis au blanc en montant ; un bouton rotatif te permet de baisser ou d'agrandir le feu. Un appui court sur le bouton rend la flamme bleue, verte ou violette, et un appui long active ou coupe le crépitement du bois joué par le buzzer.

Vidéo

Schéma du circuit

Feu de camp LED avec Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potentiomètre + Buzzer) - Circuit

Code source

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Feu de camp LED avec Raspberry Pi Pico (Matrice NeoPixel 8x8 + Potentiomètre + Buzzer)
4# ─────────────────────────────────────────
5# Animation réaliste de feu sur une matrice NeoPixel 8x8. Le potentiomètre
6# règle la hauteur de la flamme, un appui court change le thème (Feu, Gaz,
7# Magie, Améthyste) et un appui long active/désactive le son de
8# crépitement.
9#
10# Broches :
11#   GP2  -> DIN de la matrice (VDD -> VBUS, VSS -> GND)
12#   GP26 -> Curseur du potentiomètre (ADC0)
13#   GP14 -> Bouton (autre patte sur GND)
14#   GP17 -> Buzzer (+)
15
16from machine import Pin, PWM, ADC
17from neopixel import NeoPixel
18import time
19import random
20
21# ============ RÉGLAGES ============
22W = 8                  # Colonnes
23H = 8                  # Rangées
24SERPENTINE = False     # True pour les panneaux câblés en zigzag
25BRIGHTNESS = 0.4       # Wokwi dessine les couleurs telles quelles, 0.4 montre bien la flamme
26                       # 0.15-0.2 recommandé sur USB 2.0 (500 mA), utilise du 5V externe pour plus
27                       # de luminosité
28FRAME_MS = 35          # Durée d'une image (~28 FPS)
29LONG_PRESS_MS = 900    # Durée de maintien pour activer/désactiver le son
30
31# ============ MATÉRIEL ============
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# ============ ÉTAT ============
39# heat[x][y] : colonne x, y = 0 est la rangée du bas ; 0 = froid, 255 = le plus chaud
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 DE COULEURS ============
47def heat_color(t):
48    # Associer la chaleur (0-255) à une couleur selon le thème : noir -> foncé -> vif -> blanc
49    t192 = (t * 191) // 255
50    ramp = (t192 & 0x3F) << 2      # Rampe de 0-252 par bande
51    if t192 & 0x80:
52        r, g, b = 255, 255, ramp   # Le plus chaud : jaune -> blanc
53    elif t192 & 0x40:
54        r, g, b = 255, ramp, 0     # Milieu : rouge -> jaune
55    else:
56        r, g, b = ramp, 0, 0       # Froid : noir -> rouge
57    if theme == 1:
58        r, b = b, r                # Gaz : flamme bleue
59    elif theme == 2:
60        r, g = g, r                # Magie : flamme verte
61    elif theme == 3:
62        b = max(b, r * 3 // 4)     # Améthyste : flamme violette
63        g = g // 2
64    return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
65
66
67def pixel_index(x, row):
68    # rangée 0 = rangée du haut
69    if SERPENTINE and row % 2 == 1:
70        return row * W + (W - 1 - x)
71    return row * W + x
72
73
74# ============ SIMULATION DE FEU ============
75def fire_step(intensity):
76    # intensité 0-1 : hauteur de la flamme. Haute = moins de refroidissement + plus d'étincelles
77    cooling = int(115 - 55 * intensity)    # 115 (court) -> 60 (grand)
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        # Chaque cellule refroidit un peu
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        # La chaleur monte et se diffuse
88        for y in range(H - 1, 1, -1):
89            col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
90        # Étincelles aléatoires à la 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) Mélange de lumière avec les colonnes voisines : la flamme ondule plus naturellement
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# ============ SON DE CRÉPITEMENT ============
112def crackle(intensity):
113    # De très courts "tics" aléatoires = bois qui crépite
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# ============ BOUTON ============
131btn_state = 1
132btn_raw = 1
133btn_changed = time.ticks_ms()
134press_start = 0
135long_done = False
136
137
138def handle_button():
139    # Appui court = thème, appui long = son activé/désactivé
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# ============ BOUCLE 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    # Lisser la valeur du potentiomètre (la gigue ne doit pas faire sauter la flamme)
173    smooth += (pot.read_u16() / 65535 - smooth) * 0.2
174    fire_step(smooth)
175    render()
176    crackle(smooth)
177
178    # Fréquence d'images constante
179    spent = time.ticks_diff(time.ticks_ms(), start)
180    if spent < FRAME_MS:
181        time.sleep_ms(FRAME_MS - spent)
182

FAQ

Pourquoi baisser la luminosité sur le vrai montage ?▾

Le code utilise `BRIGHTNESS = 0.4` car Wokwi affiche les couleurs telles quelles. Une vraie matrice peut tirer environ 1 A avec une grande flamme, alors qu'un port USB 2.0 ne fournit que 500 mA. Sur USB, mets 0.15–0.2 ; pour plus de lumière, alimente la matrice en 5V 2A et relie les masses (GND).

Comment changer la hauteur et la couleur de la flamme ?▾

Tourne le potentiomètre à gauche pour des braises, à droite pour des flammes qui montent jusqu'en haut de la matrice. Un appui court sur le bouton passe de Feu à Gaz, Magie puis Améthyste. Sur Wokwi, clique sur le potentiomètre et utilise les flèches ; la touche T change le thème.

Comment couper le crépitement ou le rendre plus rare ?▾

Maintiens le bouton environ une seconde : un bip aigu signifie son activé, un bip grave son coupé. Pour un crépitement plus rare, diminue le 10 et le 50 de `10 + 50 * intensity` dans `crackle()`. Utilise un buzzer passif pour le son.

Faut-il installer une bibliothèque ?▾

Non. Les modules `neopixel`, `machine` et `random` sont inclus dans MicroPython pour le Pico. Installe MicroPython sur le Pico, puis enregistre le code dessus sous le nom `main.py` avec Thonny ; le feu démarre tout seul à chaque mise sous tension.

Feu de camp LED avec Raspberry Pi Pico et matrice NeoPixel 8x8 | BlueGrays