Tudo sobre a saída de som utilizando o Pico W
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.
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!).
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.
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.
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.
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
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!
E quanto ao Bluetooth?