#169Intermediário

Fogueira de LEDs com Raspberry Pi Pico (matriz NeoPixel 8x8 + potenciômetro + buzzer)

Uma fogueira que não queima nem aquece, mas estala! O Raspberry Pi Pico desenha uma chama realista em uma matriz NeoPixel 8x8, recalculada a cada quadro: surgem faíscas na base, o calor se dissipa ao subir e o brilho vai do vermelho ao branco. Um potenciômetro ajusta a altura da chama, um botão alterna entre os temas Fogo, Gás, Magia e Ametista, e um buzzer acrescenta os estalos da lenha queimando. Animação procedural, ADC, PWM e MicroPython em um só projeto.

0.0(0)
1 concluído
Fogueira de LEDs com Raspberry Pi Pico (matriz NeoPixel 8x8 + potenciômetro + buzzer)
Uma fogueira que cabe na sua mesa e nunca queima nada. Na matriz luminosa 8x8, as chamas nascem de faíscas na base e passam do vermelho ao amarelo e ao branco conforme sobem; você gira um botão para diminuir ou aumentar o fogo. Um toque curto no botão deixa a chama azul, verde ou roxa, e um toque longo liga ou desliga o som de lenha estalando no buzzer.

Vídeo

Esquema do circuito

Fogueira de LEDs com Raspberry Pi Pico (matriz NeoPixel 8x8 + potenciômetro + buzzer) - Circuit

Código-fonte

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Fogueira de LEDs com Raspberry Pi Pico (matriz NeoPixel 8x8 + potenciômetro + buzzer)
4# ─────────────────────────────────────────
5# Animação realista de fogo em uma matriz NeoPixel 8x8. O potenciômetro
6# ajusta a altura da chama, um aperto curto no botão troca o tema (Fire,
7# Gas, Magic, Amethyst: fogo, gás, magia, ametista) e um aperto longo liga
8# ou desliga o som de estalos.
9#
10# Pinos:
11#   GP2  -> DIN da matriz (VDD -> VBUS, VSS -> GND)
12#   GP26 -> Cursor do potenciômetro (ADC0)
13#   GP14 -> Botão (outra perna no GND)
14#   GP17 -> Buzzer (+)
15
16from machine import Pin, PWM, ADC
17from neopixel import NeoPixel
18import time
19import random
20
21# ============ CONFIGURAÇÕES ============
22W = 8                  # Colunas
23H = 8                  # Linhas
24SERPENTINE = False     # True para painéis ligados em zigue-zague
25BRIGHTNESS = 0.4       # O Wokwi desenha as cores como estão; 0.4 mostra bem a chama
26                       # 0.15-0.2 recomendado em USB 2.0 (500 mA); para mais brilho, use 5V externo
27FRAME_MS = 35          # Tempo do quadro (~28 FPS)
28LONG_PRESS_MS = 900    # Tempo segurando para ligar/desligar o som
29
30# ============ HARDWARE ============
31matrix = NeoPixel(Pin(2), W * H)
32pot = ADC(26)                           # ADC0
33button = Pin(14, Pin.IN, Pin.PULL_UP)
34buzzer = PWM(Pin(17))
35buzzer.duty_u16(0)
36
37# ============ ESTADO ============
38# heat[x][y]: coluna x, y = 0 é a linha de baixo; 0 = frio, 255 = mais quente
39heat = [[0] * H for _ in range(W)]
40theme = 0
41THEME_NAMES = ["Ates / Fire", "Gaz / Gas", "Buyu / Magic", "Ametist / Amethyst"]
42sound_on = True
43
44
45# ============ PALETA DE CORES ============
46def heat_color(t):
47    # Mapeia o calor (0-255) para uma cor conforme o tema: preto -> escuro -> claro -> branco
48    t192 = (t * 191) // 255
49    ramp = (t192 & 0x3F) << 2      # Rampa de 0-252 por faixa
50    if t192 & 0x80:
51        r, g, b = 255, 255, ramp   # Mais quente: amarelo -> branco
52    elif t192 & 0x40:
53        r, g, b = 255, ramp, 0     # Meio: vermelho -> amarelo
54    else:
55        r, g, b = ramp, 0, 0       # Frio: preto -> vermelho
56    if theme == 1:
57        r, b = b, r                # Gas (gás): chama azul
58    elif theme == 2:
59        r, g = g, r                # Magic (magia): chama verde
60    elif theme == 3:
61        b = max(b, r * 3 // 4)     # Amethyst (ametista): chama roxa
62        g = g // 2
63    return (int(r * BRIGHTNESS), int(g * BRIGHTNESS), int(b * BRIGHTNESS))
64
65
66def pixel_index(x, row):
67    # row 0 = linha de cima
68    if SERPENTINE and row % 2 == 1:
69        return row * W + (W - 1 - x)
70    return row * W + x
71
72
73# ============ SIMULAÇÃO DO FOGO ============
74def fire_step(intensity):
75    # intensity 0-1: altura da chama. Alto = menos resfriamento + mais faíscas
76    cooling = int(115 - 55 * intensity)    # 115 (baixa) -> 60 (alta)
77    sparking = int(70 + 150 * intensity)   # 70 -> 220 (probabilidade)
78    max_cool = (cooling * 10) // H + 2
79
80    for x in range(W):
81        col = heat[x]
82        # Cada célula esfria um pouco
83        for y in range(H):
84            c = col[y] - random.getrandbits(8) % max_cool
85            col[y] = c if c > 0 else 0
86        # O calor sobe e se espalha
87        for y in range(H - 1, 1, -1):
88            col[y] = (col[y - 1] + col[y - 2] + col[y - 2]) // 3
89        # Faíscas aleatórias na base
90        if random.getrandbits(8) < sparking:
91            y = random.getrandbits(1)
92            col[y] = min(255, col[y] + 160 + random.getrandbits(6))
93
94    # 4) Mistura leve com as colunas vizinhas: a chama ondula de forma mais natural
95    for y in range(2, H):
96        row_vals = [heat[x][y] for x in range(W)]
97        for x in range(W):
98            left = row_vals[x - 1] if x > 0 else row_vals[x]
99            right = row_vals[x + 1] if x < W - 1 else row_vals[x]
100            heat[x][y] = (row_vals[x] * 2 + left + right) // 4
101
102
103def render():
104    for x in range(W):
105        for y in range(H):
106            matrix[pixel_index(x, H - 1 - y)] = heat_color(heat[x][y])
107    matrix.write()
108
109
110# ============ SOM DE ESTALOS ============
111def crackle(intensity):
112    # "Tiques" aleatórios bem curtos = madeira estalando
113    if not sound_on:
114        return
115    if random.getrandbits(8) < int(10 + 50 * intensity):
116        buzzer.freq(1500 + random.getrandbits(12))   # 1500-5595 Hz
117        buzzer.duty_u16(6000)
118        time.sleep_ms(2 + random.getrandbits(2))
119        buzzer.duty_u16(0)
120
121
122def beep(freq, ms):
123    buzzer.freq(freq)
124    buzzer.duty_u16(20000)
125    time.sleep_ms(ms)
126    buzzer.duty_u16(0)
127
128
129# ============ BOTÃO ============
130btn_state = 1
131btn_raw = 1
132btn_changed = time.ticks_ms()
133press_start = 0
134long_done = False
135
136
137def handle_button():
138    # Aperto curto = tema, aperto longo = som ligado/desligado
139    global btn_state, btn_raw, btn_changed, press_start, long_done, theme, sound_on
140    now = time.ticks_ms()
141    raw = button.value()
142    if raw != btn_raw:
143        btn_raw = raw
144        btn_changed = now
145    if time.ticks_diff(now, btn_changed) > 25 and raw != btn_state:
146        btn_state = raw
147        if raw == 0:
148            press_start = now
149            long_done = False
150        elif not long_done:
151            theme = (theme + 1) % len(THEME_NAMES)
152            print("Tema / Theme:", THEME_NAMES[theme])
153            beep(1200, 30)
154    if btn_state == 0 and not long_done and time.ticks_diff(now, press_start) > LONG_PRESS_MS:
155        long_done = True
156        sound_on = not sound_on
157        print("Ses / Sound:", "ACIK / ON" if sound_on else "KAPALI / OFF")
158        beep(1600 if sound_on else 500, 80)
159
160
161# ============ LOOP PRINCIPAL ============
162print("== BlueGrays · bluegrays.com ==")
163print("Pico 8x8 Kamp Atesi / Campfire")
164print("Pot = alev boyu / flame height | Kisa basis = tema / short press = theme | Uzun basis = ses / long press = sound")
165
166smooth = pot.read_u16() / 65535
167while True:
168    start = time.ticks_ms()
169    handle_button()
170
171    # Suaviza o valor do pot (a oscilação não deve fazer a chama pular)
172    smooth += (pot.read_u16() / 65535 - smooth) * 0.2
173    fire_step(smooth)
174    render()
175    crackle(smooth)
176
177    # Taxa de quadros constante
178    spent = time.ticks_diff(time.ticks_ms(), start)
179    if spent < FRAME_MS:
180        time.sleep_ms(FRAME_MS - spent)
181

Perguntas frequentes

Por que devo reduzir o brilho no circuito real?

O código usa `BRIGHTNESS = 0.4` porque o Wokwi exibe as cores exatamente como são enviadas. Uma matriz real pode consumir cerca de 1 A com uma chama grande, enquanto uma porta USB 2.0 fornece 500 mA. Com alimentação por USB, ajuste o valor para 0.15–0.2; para mais brilho, alimente a matriz com uma fonte de 5V 2A e conecte os GNDs.

Como mudar a altura e a cor da chama?

Gire o potenciômetro para a esquerda para deixar apenas as brasas acesas e para a direita para ter chamas que chegam ao topo da matriz. Um toque curto no botão alterna entre Fogo, Gás, Magia e Ametista. No Wokwi, clique no potenciômetro e use as teclas de seta; pressione T para mudar o tema.

Como desligar os estalos ou torná-los menos frequentes?

Mantenha o botão pressionado por cerca de um segundo: um bipe agudo indica que o som está ligado, e um bipe grave indica que está desligado. Para reduzir a frequência dos estalos, diminua os valores 10 e 50 de `10 + 50 * intensity` dentro de `crackle()`. Use um buzzer passivo para o som.

Preciso instalar alguma biblioteca?

Não. Os módulos `neopixel`, `machine` e `random` já vêm com o MicroPython para o Pico. Instale o MicroPython no Pico e, no Thonny, salve o código nele como `main.py`; assim, a fogueira inicia automaticamente toda vez que o Pico é ligado.