Logótipo

PiezoPiano com Raspberry Pi Pico W

Protótipo de piano electrónico com sensores piezoeléctricos, protecção por díodo zener e reprodução de sons através de DFPlayer Mini.

Raspberry Pi Pico W piezoeléctrico ADC zener 3,3 V DFPlayer Mini Python
./visao_geral_piezopiano.txt

1Ideia do projecto

O projecto consiste na construção de um pequeno piano electrónico em que cada tecla é representada por um disco piezoeléctrico. Ao sofrer uma pancada ou vibração, o piezo gera um impulso eléctrico que é lido pelo Raspberry Pi Pico W.

Depois de detectado o impacto, o Pico W pode acender um LED, registar dados, ou enviar um comando ao DFPlayer Mini para reproduzir uma nota musical em formato MP3.

2Objectivos educativos

  • Explorar o funcionamento de sensores piezoeléctricos.
  • Medir sinais analógicos com o ADC do Raspberry Pi Pico W.
  • Compreender a necessidade de protecção das entradas de 3,3 V.
  • Relacionar electrónica, programação, som e aquisição de dados.
  • Construir um protótipo musical interactivo com aplicação prática.

3Arquitectura geral

Piezo → circuito de protecção → Raspberry Pi Pico W → DFPlayer Mini → coluna

A solução inicial utiliza três entradas analógicas do Pico W: GP26, GP27 e GP28. Para mais teclas, podem ser usadas entradas digitais ou um multiplexador analógico.

./circuito_piezo_protegido.txt

4Ligação de um piezo

O piezo não deve ser ligado directamente ao GPIO. Como pode gerar picos de tensão superiores a 3,3 V, é usado um circuito simples de limitação e descarga.

Piezo (+)
   |
 [10 kΩ]
   |
   ●------[1 kΩ]------ GPIO/ADC do Pico
   |
 [1 MΩ]
   |
  GND

● ---- cátodo do zener 3,3 V
GND -- ânodo do zener 3,3 V

Piezo (-) ---- GND

5Função dos componentes

ComponenteFunção
10 kΩLimita a corrente que vem do piezo.
1 MΩFunciona como pulldown e descarrega o piezo, evitando leituras flutuantes.
1 kΩProtecção extra antes da entrada do Pico.
Zener 3,3 VLimita a tensão aplicada ao GPIO/ADC.

6Orientação do zener

O díodo zener deve ser ligado inversamente polarizado: a faixa preta, que identifica o cátodo, fica do lado do sinal; o outro terminal fica ligado ao GND.

SINAL / ponto ● ----|<|---- GND
                  zener 3,3 V
Atenção: se o zener for ligado no sentido directo, começa a conduzir perto de 0,7 V e corta o sinal demasiado cedo.
./ligacoes_pico_w.txt

7Entradas analógicas usadas

EntradaGPIOPino físicoUso
A0 / ADC0GP2631Piezo 1
A1 / ADC1GP2732Piezo 2
A2 / ADC2GP2834Piezo 3

8Importância do GND comum

Todos os GND do circuito devem estar ligados ao GND do Pico W. Isto inclui o negativo dos piezos, a resistência de 1 MΩ, o ânodo do zener e o GND do DFPlayer Mini.

GND dos piezos
GND do zener
GND do DFPlayer
GND do Pico W
→ todos ligados em comum

9Leitura digital ou analógica?

Com leitura analógica é possível medir a intensidade do impacto. Com leitura digital apenas se detecta se houve ou não toque. Para o estudo de tensão, deve ser usada a entrada analógica GP26.

./dfplayer_mini_uart.txt

10Ligações UART

DFPlayer MiniRaspberry Pi Pico W
VCCVBUS / 5 V
GNDGND
RXGP0 / UART0 TX
TXGP1 / UART0 RX
SPK1Coluna
SPK2Coluna
A coluna deve ser ligada entre SPK1 e SPK2. Não deve ser ligada ao GND.

11Ficheiros MP3

No cartão microSD, criar a pasta mp3 e colocar ficheiros numerados:

/mp3/0001.mp3
/mp3/0002.mp3
/mp3/0003.mp3

Cada ficheiro pode corresponder a uma nota musical ou efeito sonoro.

PiezoEntrada Pico WFicheiro reproduzido
Piezo 1GP26 / A00001.mp3
Piezo 2GP27 / A10002.mp3
Piezo 3GP28 / A20003.mp3
./estudo_tensao_piezo.csv

12Objectivo do ensaio

Para caracterizar o comportamento do piezo, foi proposta uma aquisição de dados através da entrada analógica GP26. Durante alguns segundos, são aplicadas pancadas no piezo e o Pico W grava os valores do ADC e a tensão calculada num ficheiro CSV.

13Conversão ADC para tensão

tensao = valor_adc × 3.3 / 65535

O ADC do Pico devolve valores entre 0 e 65535, correspondentes aproximadamente ao intervalo entre 0 V e 3,3 V.

14Ressalva sobre o zener

Nota técnica: os valores registados representam a tensão protegida que chega à entrada analógica do Raspberry Pi Pico W, e não a tensão máxima real que o piezo poderia produzir em circuito aberto. Como existe um díodo zener de 3,3 V, picos superiores a esse valor podem aparecer limitados ou saturados no gráfico.

15Dados recolhidos e gráfico do ensaio

O ensaio realizado no piezo ligado ao GP26 / A0 registou 1553 amostras durante aproximadamente 5 segundos. A tensão máxima medida foi de cerca de 2,158 V, correspondente ao valor ADC 42858, no instante aproximado de 4257 ms.

Gráfico da resposta do piezo ligado ao GP26
ParâmetroValor
Amostras1553
Duração~5 s
Pico máximo2,158 V
ADC máximo42858
Instante do pico4257 ms

16Interpretação esperada

O gráfico mostra picos rápidos nos momentos de impacto, seguidos de uma descida acentuada e recuperação gradual para a tensão de repouso. Neste ensaio, os picos ficaram abaixo de 3,3 V, mas a leitura continua condicionada pelo circuito de protecção. Caso sejam aplicados impactos mais fortes, o zener pode limitar os valores superiores, protegendo o ADC do Pico W.

./codigo_base.py

17Registo CSV no Pico W

from machine import ADC, Pin
import time

piezo = ADC(Pin(26))
led = Pin("LED", Pin.OUT)

FICHEIRO = "piezo_gp26.csv"
INTERVALO_MS = 2
DURACAO_MS = 5000

def adc_para_tensao(valor_adc):
    return valor_adc * 3.3 / 65535

time.sleep(2)
led.value(1)

inicio = time.ticks_ms()

with open(FICHEIRO, "w") as ficheiro:
    ficheiro.write("tempo_ms,adc,tensao_v\n")

    while time.ticks_diff(time.ticks_ms(), inicio) < DURACAO_MS:
        tempo_ms = time.ticks_diff(time.ticks_ms(), inicio)
        valor_adc = piezo.read_u16()
        tensao = adc_para_tensao(valor_adc)

        ficheiro.write("{{}},{{}},{{:.4f}}\n".format(tempo_ms, valor_adc, tensao))
        time.sleep_ms(INTERVALO_MS)

led.value(0)
print("Ficheiro gravado:", FICHEIRO)

18Gráfico em Python

import csv
import matplotlib.pyplot as plt

FICHEIRO = "piezo_gp26.csv"

tempos = []
tensoes = []
adcs = []

with open(FICHEIRO, "r") as ficheiro:
    leitor = csv.DictReader(ficheiro)

    for linha in leitor:
        tempos.append(float(linha["tempo_ms"]))
        adcs.append(int(linha["adc"]))
        tensoes.append(float(linha["tensao_v"]))

tensao_maxima = max(tensoes)
indice_pico = tensoes.index(tensao_maxima)
tempo_pico = tempos[indice_pico]
adc_maximo = adcs[indice_pico]

print("Resumo do ensaio")
print("----------------")
print("Tensão máxima medida:", round(tensao_maxima, 3), "V")
print("ADC máximo:", adc_maximo)
print("Tempo do pico máximo:", tempo_pico, "ms")
print("Número de amostras:", len(tensoes))

plt.figure(figsize=(12, 6))
plt.plot(tempos, tensoes, label="Tensão medida")
plt.scatter(tempo_pico, tensao_maxima, label="Pico máximo")

plt.title("Resposta do piezo ligado ao GP26")
plt.xlabel("Tempo (ms)")
plt.ylabel("Tensão no ADC (V)")
plt.grid(True)
plt.legend()
plt.tight_layout()

plt.savefig("grafico_piezo_gp26.png", dpi=300)
plt.show()

19Reprodução MP3 com três piezos

Este código permite associar cada piezo a uma música diferente no DFPlayer Mini. Assim, ao tocar no piezo ligado ao GP26, é reproduzido o ficheiro 0001.mp3; ao tocar no piezo ligado ao GP27, é reproduzido o ficheiro 0002.mp3; e ao tocar no piezo ligado ao GP28, é reproduzido o ficheiro 0003.mp3.

/mp3/0001.mp3  → Piezo 1 / GP26
/mp3/0002.mp3  → Piezo 2 / GP27
/mp3/0003.mp3  → Piezo 3 / GP28
from machine import Pin, ADC, UART
import time

# -------------------------------------------------
# Piezos ligados às entradas analógicas do Pico W
# -------------------------------------------------
piezo1 = ADC(Pin(26))   # A0 / GP26
piezo2 = ADC(Pin(27))   # A1 / GP27
piezo3 = ADC(Pin(28))   # A2 / GP28

# LED interno do Pico W
led = Pin("LED", Pin.OUT)

# -------------------------------------------------
# DFPlayer Mini ligado à UART0
# GP0 = TX do Pico  -> RX do DFPlayer
# GP1 = RX do Pico  <- TX do DFPlayer
# -------------------------------------------------
uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))


def dfplayer_cmd(cmd, param=0):
    start = 0x7E
    version = 0xFF
    length = 0x06
    feedback = 0x00
    end = 0xEF

    param_high = (param >> 8) & 0xFF
    param_low = param & 0xFF

    checksum = 0 - (version + length + cmd + feedback + param_high + param_low)
    checksum &= 0xFFFF

    packet = bytes([
        start,
        version,
        length,
        cmd,
        feedback,
        param_high,
        param_low,
        (checksum >> 8) & 0xFF,
        checksum & 0xFF,
        end
    ])

    uart.write(packet)


def set_volume(volume):
    # Volume entre 0 e 30
    dfplayer_cmd(0x06, volume)


def play_track(track_number):
    # Toca /mp3/0001.mp3, /mp3/0002.mp3, etc.
    dfplayer_cmd(0x12, track_number)


# -------------------------------------------------
# Configuração dos toques
# -------------------------------------------------
LIMIAR = 8000          # baixa se não detectar; aumenta se disparar sozinho
BLOQUEIO_MS = 350     # evita várias leituras do mesmo toque

ultimo_toque_1 = 0
ultimo_toque_2 = 0
ultimo_toque_3 = 0

# Espera pelo arranque do DFPlayer Mini
time.sleep(2)
set_volume(24)

print("Sistema pronto.")
print("Piezo 1 / GP26 -> 0001.mp3")
print("Piezo 2 / GP27 -> 0002.mp3")
print("Piezo 3 / GP28 -> 0003.mp3")


# -------------------------------------------------
# Ciclo principal
# -------------------------------------------------
while True:
    agora = time.ticks_ms()

    valor1 = piezo1.read_u16()
    valor2 = piezo2.read_u16()
    valor3 = piezo3.read_u16()

    # Valores no terminal para afinar o limiar
    print("P1:", valor1, " P2:", valor2, " P3:", valor3)

    if valor1 > LIMIAR and time.ticks_diff(agora, ultimo_toque_1) > BLOQUEIO_MS:
        print("Piezo 1 tocado -> Música 1")
        led.value(1)
        play_track(1)
        time.sleep_ms(80)
        led.value(0)
        ultimo_toque_1 = agora

    if valor2 > LIMIAR and time.ticks_diff(agora, ultimo_toque_2) > BLOQUEIO_MS:
        print("Piezo 2 tocado -> Música 2")
        led.value(1)
        play_track(2)
        time.sleep_ms(80)
        led.value(0)
        ultimo_toque_2 = agora

    if valor3 > LIMIAR and time.ticks_diff(agora, ultimo_toque_3) > BLOQUEIO_MS:
        print("Piezo 3 tocado -> Música 3")
        led.value(1)
        play_track(3)
        time.sleep_ms(80)
        led.value(0)
        ultimo_toque_3 = agora

    time.sleep_ms(20)
Nota: se o sistema disparar sem toque, aumenta o valor de LIMIAR. Se não detectar os impactos, baixa esse valor.

20Comandos úteis

cd /home/alvaro/Documentos/Escola/piezo
nano grafico_piezo.py
python3 grafico_piezo.py
xdg-open grafico_piezo_gp26.png