Tudo sobre a saída de som utilizando o Pico W

Som no Pico W Título Imagem

A Raspberry Pi Pico W é uma poderosa placa de microcontrolador construída em torno do chip microcontrolador próprio da Raspberry Pi, o RP2040. Pode consultar uma seleção de placas de microcontroladores baseadas no RP2040 aqui ou continue a ler para descobrir tudo o que precisa de saber sobre a saída de som utilizando o Pico W.

Já pensou em fazer um leitor de música, criar um analisador de espetro áudio ou construir uma beatbox, mas teve problemas em reproduzir áudio com o seu microcontrolador? Então não procures mais, este é o artigo para ti!

Aqui, vou apresentar-lhe o universo da modulação por largura de pulso (PWM), os protocolos de comunicação para o Pico W e como tirar partido da conetividade sem fios do Pico W.

Imagem do Pico W

Modulação por largura de pulso

Uma forma de reproduzir som no Pico W (ou em qualquer outro microcontrolador) é através da modulação por largura de pulso, ou PWM, para abreviar.

PWM é uma técnica utilizada para controlar dispositivos analógicos utilizando um sinal digital. Embora a saída digital de um microcontrolador só possa estar ligada ou desligada (0 ou 1), com o PWM, podemos imitar um sinal analógico. (Vale a pena mencionar que o PWM não é um verdadeiro sinal analógico.) O PWM consegue isto ao alternar rapidamente o estado de energia entre ligado e desligado, controlando assim a corrente.

Pode saber mais sobre PWM aqui!

E veja o nosso vídeo:

Como o PWM é super eficiente, perde muito pouca energia e é extremamente preciso, esta é a técnica utilizada em muitas aplicações para criar diferentes efeitos de áudio.

Todos os pinos do Pico W têm capacidade para PWM. É apenas uma questão de codificação!

Vou dar um exemplo para que possa ver o PWM em ação.

Projeto

Para demonstrar a geração de áudio com PWM a partir do Pico W, utilizei 2 cabos de crocodilo com extremidades macho, uma placa de ensaio e auscultadores.

Em alternativa, se quiser evitar soldar, pode tentar prender a outra extremidade dos cabos de crocodilo diretamente nos auscultadores ou na ficha estéreo (Recomendo vivamente a não utilização de auscultadores, só para proteger os tímpanos!).

Um Pico W ligado a auscultadores

Liguei o cabo de crocodilo vermelho ao pino 23, que é um pino de terra (qualquer pino de terra serve). E o PWM pode ser gerado através de qualquer um dos pinos GP. Como se pode ver na foto acima, estou a usar o GP0.

Qualquer conjunto de auscultadores moderno ou tomada estéreo tem três ou quatro secções - sendo as duas exteriores o áudio esquerdo e o áudio direito. Assim, prendi o cabo de crocodilo vermelho à esquerda e o cabo de crocodilo preto à direita.

Se seguir esta configuração, tenha cuidado para que os cabos de crocodilo não se toquem.

Outra imagem do Pico W ligado a auscultadores

Uma vez terminada a configuração física, o passo seguinte é descarregar os ficheiros de áudio.

Se estiver a utilizar ficheiros .wav, certifique-se de que correspondem às seguintes especificações:

1. canal: mono (não estéreo)

2. taxa de bits: 22 kHz ou inferior

3. amostragem: 16 bits

Aqui está um exemplo de código para gerar áudio com .wav usando CircuitPython:

# PiCockpit.com
# audio output via digital PWM

import board
# loads a wav file for audio playback
from audiocore import WaveFile
# the audiopwmio module contains classes to provide access to audio ID
from audiopwmio import PWMAudioOut

# outputs an analog audio signal by varying the PWM duty cycle under the hood
audio_out = PWMAudioOut(board.GP0)

def play_wave(filename):
    with open(filename, "rb") as wave_file:
        sample = WaveFile(wave_file)
        audio_out.play(sample, loop=True)
# outputs an analog audio signal by varying the PWM duty cycle under the hood
audio_out = PWMAudioOut(board.GP0)

def play_wave(filename):
    with open(filename, "rb") as wave_file:
        sample = WaveFile(wave_file)
        audio_out.play(sample, loop=True)

        while audio_out.playing:
            pass

# audio will loop until program interruption
play_wave("meditation-music.wav")

Se estiver a utilizar ficheiros .mp3, que têm de cumprir as mesmas especificações que o ficheiro .wav, pode consultar este exemplo de código:

# PiCockpit.com
# audio output via digital PWM

import board
# loads an mp3 file for audio playback
from audiomp3 import MP3Decoder
# the audiopwmio module contains classes to provide access to audio IO
from audiopwmio import PWMAudioOut

# outputs an analog audio signal by varying the PWM duty cycle under the hood
audio_out = PWMAudioOut(board.GP0)

mp3 = MP3Decoder("meditation-music.mp3")

audio_out.play(mp3)
while audio_out.playing:
    pass

Um aspeto adicional a ter em atenção nos ficheiros .mp3 é a taxa de bits constante. Se o áudio parecer distorcido, tente baixar ainda mais a taxa de amostragem e a taxa de bits. A taxa de bits mais elevada que consegui obter foi de 192 kbps.

Se estiveres a usar MicroPython em vez de CircuitPython, podes recorrer ao seguinte exemplo de código, que gera 3 tons distintos com PWM, definindo o ciclo de trabalho e variando a frequência.

# PiCockpit.com

from machine import Pin, PWM
from utime import sleep

# lower right corner with USB connector on top
SPEAKER_PIN = 16

# create a PWM object on this pin
speaker = PWM(Pin(SPEAKER_PIN))

# the time each tone will be on
ON_TIME = .25
# the time between tones
OFF_TIME = .1

# low tone
speaker.duty_u16(1000)
speaker.freq(300)
sleep(ON_TIME)
speaker.duty_u16(0)
sleep(OFF_TIME)

# high tone
speaker.duty_u16(1000)
speaker.freq(800)
sleep(ON_TIME)
speaker.duty_u16(0)
sleep(OFF_TIME)

# medium tone
speaker.duty_u16(1000)
speaker.freq(400)
sleep(ON_TIME)

# turn off the PWM
speaker.duty_u16(0)

Protocolos de comunicação

Para que o Pico W e os componentes externos comuniquem entre si, existem vários protocolos disponíveis. Se alguma vez trabalhou com a biblioteca de máquinas em MicroPython, sabe que o protocolo de comunicação padrão para os pinos GPIO no Pico W é a biblioteca GPIOZero, que fornece uma interface simples para controlar e monitorizar os pinos GPIO. Além disso, o Pico W também suporta outros protocolos de comunicação.

Aqui, quero falar um pouco sobre UART, SPI, I2C e I2S, pois todos eles oferecem certas vantagens e desvantagens no trabalho com áudio no Pico W.

Em primeiro lugar, o protocolo Universal Asynchronous Receiver/Transmitter, ou UART, é teoricamente simples de implementar, requer apenas 2 fios e é altamente fiável. No entanto, a UART tem alcance e largura de banda limitados e só pode suportar a comunicação com um outro dispositivo.

Além disso, ao longo da minha pesquisa, descobri que a UART não é adequada para reproduzir som no Pico W. Se já utilizou a UART no Pico W para saída de áudio, diga-me nos comentários!

SPI

Enquanto a UART não dá conta do recado, a Serial Peripheral Interface, ou SPI, oferece altas taxas de transferência de dados e é extremamente fiável. As suas maiores desvantagens giram em torno do facto de a SPI só permitir um controlador e usar 4 fios. Consegui pô-la a funcionar usando o seguinte código:

# PiCockpit.com
# registers addresses for the PCM5102A module
REG_POWER_CONTROL = 0x02
REG_MODE_CONTROL = 0x03
REG_DAC_CONTROL = 0x0A
REG_DAC_VOLUME = 0x0B

# powers on the PCM5102A module
cs_pin.value(0)
spi.write(bytes([REG_POWER_CONTROL, 0x08]))
cs_pin.value(1)

# sets the mode to I2S
cs_pin.value(0)
spi.write(bytes([REG_MODE_CONTROL, 0x04]))
cs_pin.value(1)

# sets the DAC control bits
cs_pin.value(0)
spi.write(bytes([REG_DAC_CONTROL, 0x00]))
cs_spin.value(1)

# sets the DAC volume
cs_pin.value(0)
spi.write(bytes([REG_DAC_VOLUME, 0x88]))
cs_pin.value(1)

# generates a sine save for testing
sample_rate = 44100
frequency = 440
duration = 5
samples = int(sample_rate * duration)
amplitude = 0x7FFF
sine_wave = bytearray(samplex * 2)
for i in range(samples):
    value = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
    sine_wave[i*2] = value & 0xFF
    sine_wave[i*2+1] = (value >> 8) & 0xFF

# writes the sine wave to the PCM5102A module
cs_pin.value(0)
spi.write(sine_wave)
cs_pin.value(1)

# Waits for the sound to finish playing
utime.sleep(duration)

# Powers off the PCM5102A module
cs_pin.value(0)
spi.write(bytes([REG_POWER_CONTROL, 0x00]))
cs_pin.value(1)

I2C

O terceiro protocolo que quero abordar é o Inter-Integrate Circuit, ou I2C, que só precisa de 2 fios, é simples de implementar e requer pouca energia. Tal como o UART, o I2C tem um alcance e uma largura de banda limitados, mas ao contrário do UART, o I2C pode comunicar com vários dispositivos. O mais importante, no entanto, é que eu consigo fazer o I2C funcionar com o seguinte código:

# PiCockpit.com

import machine
import utime

# I2C bus
i2c = machine.I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)

# I2C address of the PCM5102A module
i2c_address = 0x4C

# register addresses for the PCM5102A module
REG_POWER_CONTROL = 0x02
REG_MODE_CONTROL = 0x03
REG_DAC_CONTROL = 0x0A
REG_DAC_VOLUME = 0x0B

# powers on the PCM5102A module
i2c.writeto_mem(i2c_address, REG_POWER_CONTROL, b'\x08')

# sets the mode to I2S
i2c.writeto_mem(i2c_address, REG_MODE_CONTROL, b'\x04')

# sets the DAC controls
i2c.writeto_mem(i2c_address, REG_DAC_CONTROL, b'\x00')

# sets the DAC volume
i2c.writeto_mem(i2c_address, REG_DAC_VOLUME, b'\x88')

# generates a sine wave to test
sample_rate = 44100
frequency = 440
duration = 5
samples = int(sample_rate * duration)
amplitude = 0x7FFF
sine_wave = bytearray(samples * 2)
for i in range(samples):
    value = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
    sine_wave[i*2] = value & 0xFF
    sine_wave[i*2+1] = (value >> 8) & 0xFF

# writes the sine wave to the PCM5102A module
i2c.writeto(i2c_address, sine_wave)

# waits for the sound to finish playing
utime.sleep(duration)

# power of the PCM5102A module
i2c.writeto_mem(i2c_address, REG_POWER_CONTROL, b'\x00')

Com base no facto de o I2C ser menos complexo e suportar vários dispositivos num único barramento, é provavelmente a melhor escolha para aplicações de áudio mais simples que requerem transferências de dados mais baixas. No entanto, o SPI pode ser mais adequado para a reprodução de áudio de maior qualidade, uma vez que suporta taxas de transferência de dados mais rápidas.

I2S

O quarto e último protocolo que quero discutir é o Inter-Integrated Sound, ou I2S, que é um protocolo que foi optimizado para o transporte de dados de áudio. O I2S é de longe o protocolo ideal para gerar áudio a partir do Pico W, porque fornece áudio de alta fidelidade, transfere dados rapidamente, é extremamente fiável e é fácil de usar. Além disso, existe uma vasta seleção de componentes de hardware que suportam I2S, tais como o Pimoroni Pico Audio Pack.

Pacote áudio Pimoroni Pico

Para ouvir o som do I2S, tenho de mudar do MicroPython para o CircuitPython, uma vez que o I2S ainda não é suportado pelo MicroPython. Mas podes empurrar suavemente o Pico Audio Pack para os conectores do Pico W, certificando-te de que o lado 'USB' do Audio Pack está virado para a direção do micro USB do Pico W.

Ligue os seus auscultadores, ou outro dispositivo que deseje, e já está!

Mais uma coisa: é melhor baixar um pouco o volume para ouvir esta música (depois agradece-me!)

E aqui está o código de exemplo:

# PiCockpit.com

import audiobusio
import audiocore
import board
import array
import time
import math

# sets up I2S, pointing at the correct pins
i2s = audiobusio.I2SOut(board.GP10, board.GP11, board.GP9)

# generates one period of sine wave
# calculates sine wave's length with floor division based on the sample rate and frequency
length = 8000 // 400

# creates an array of signed 16-bit integers based on the length
sine_wave =array.array("H", [0] * length)

for i in range(length):
# calculates sample value with the sin function
    sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 14) + 2 ** 14)

# creates a raw audio sample buffer in memory using the audiocore library
sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000)

i2s.play(sine_wave, loop=True)
time.sleep(1)
i2s.stop()

Wi-Fi

Uma vez que o Pico W está a apresentar capacidades sem fios, deixem-me mostrar-vos algo que utiliza a conetividade sem fios.

Este código pega num .mp3 de um URL e depois reproduz-o a partir de um ficheiro quando o download estiver concluído:

# PiCockpit.com

import adafruit_requests
import wifi
import socketpool
import ssl
import board
import audiomp3
import audiobusio
import os

# sets up I2S for sound output
audio = audiobusio.I2SOut(board.GP10, board.GP11, board.GP9)

# url for the mp3 file to download, 1 minute mp3
mp3_url = 'https://codeskulptor-demos.commondatastorage.googleapis.com/descent/background%20music.mp3'

# chunk size for downloading the mp3 file in parts
chunk_size = 1024

# connects to SSID
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))

pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

# requests the url and writes the mp3 to a file
try:
    doc = requests.get(mp3_url)
    # opens the file in binary write mode
    with open('sound.mp3', 'wb') as f:
    # writes the file in chunks
    for chunk in doc.iter_content(chunk_size=chunk_size):
        f.write(chunk)
except Exception as e:
    print("Connection error:\n", str(e))

# plays the mp3 file from flash memory
try:
    # creates an mp3 object
    mp3 = audiomp3.MP3Decoder(open("sound.mp3", "rb"))
    # optimizes the sample rate for your setup before playing
    mp3.sample_rate=24000
    # mp3.bits_per_sample=50
    audio.play(mp3)

    while audio.playing:
        pass

    print("Done playing!")
except Exception as e:
    print("Error in creating mp3 or playing audio:\n", str(e))

settings.toml:

# PiCockpit.com
# SSID is your network name
# replace myssid with your wi-fi name
WIFI_SSID = "myssid"

# replace mypassword with your wifi password
WIFI_PASSWORD - "mypassword"

Note que o download do .mp3 pode demorar mais do que a duração real do áudio. Para que isso funcionasse, eu também tive que habilitar permissões de escrita e aumentar a memória para o download através da coleta de lixo no boot.py:

# PiCockpit.com

import gc
import storage

gc.collect(150000)
storage.remount("/", False)

Adicione armazenamento externo, use alguns botões e tente transmitir música de algumas estações de rádio online. Nada o poderá impedir de desfrutar da sua música favorita com o Raspberry Pi Pico W.

Conclusão

O Pico W tem uma série de opções diferentes para produzir som, mas é preciso ter cuidado com o espaço limitado que o Pico W oferece.

Este post é baseado num artigo originalmente escrito por Zita B.

3 comentários

  1. Rob Frohne em Dezembro 19, 2023 às 9:23 pm

    Consegui utilizar a UART para enviar sons por USB. É possível obter quase 6 Mbps com o Pico USB. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer em Fevereiro 23, 2024 às 8:07 pm

    Isto é muito útil! Abrange todos os métodos, em vez de apenas um "favorito". Era mesmo o que eu precisava para dar a minha aula de Eletrónica Analógica Avançada, onde utilizo o PICO algures para a maioria das experiências. Tons básicos em PWM até à função I2S de alta qualidade! Espero que outros vejam o meu comentário!

  3. HarloTek em Março 9, 2024 às 11:52 am

    E quanto ao Bluetooth?

Deixe um comentário