Tutto sull'uscita del suono con il Pico W

Suono sul Pico W Titolo dell'immagine

Raspberry Pi Pico W è una potente scheda per microcontrollori costruita attorno al chip del microcontrollore Raspberry Pi, l'RP2040. È possibile consultare una selezione di schede per microcontrollori basate su RP2040 qui oppure continuate a leggere per scoprire tutto quello che c'è da sapere sull'emissione del suono con il Pico W.

Avete mai pensato di realizzare un lettore musicale, di creare un analizzatore di spettro audio o di costruire un beatbox, ma avete incontrato problemi nella riproduzione dell'audio con il vostro microcontrollore? Non cercate oltre, questo è l'articolo che fa per voi!

Qui vi introdurrò all'universo della modulazione di ampiezza degli impulsi (PWM), ai protocolli di comunicazione per il Pico W e a come sfruttare la connettività wireless di Pico W.

Immagine Pico W

Modulazione a larghezza di impulso

Un modo per riprodurre suoni sul Pico W (o su qualsiasi altro microcontrollore) è la modulazione di larghezza di impulso (PWM).

La PWM è una tecnica utilizzata per controllare dispositivi analogici utilizzando un segnale digitale. Sebbene l'uscita digitale di un microcontrollore possa essere sempre e solo accesa o spenta (0 o 1), con il PWM possiamo imitare un segnale analogico (vale la pena ricordare che il PWM non è un vero segnale analogico). (Vale la pena ricordare che il PWM non è un vero segnale analogico). Il PWM ottiene questo risultato commutando rapidamente lo stato di alimentazione tra acceso e spento e controllando così la corrente.

Per saperne di più sulla PWM, cliccate qui!

E guardate il nostro video:

Poiché la PWM è super efficiente, perde pochissima energia ed è estremamente precisa, questa è la tecnica utilizzata in molte applicazioni per creare diversi effetti audio.

Ogni pin del Pico W è in grado di gestire il PWM. È solo una questione di codifica!

Vi illustrerò un esempio per farvi vedere la PWM in azione.

Progetto

Per dimostrare la generazione di audio con PWM dal Pico W, ho utilizzato 2 cavi a coccodrillo con estremità maschio, una breadboard e delle cuffie.

In alternativa, se si vuole evitare di saldare, si può provare a collegare l'altra estremità dei cavi a coccodrillo direttamente alle cuffie o alla presa stereo (Consiglio vivamente di non usare le cuffie, per proteggere i timpani!).

Un Pico W collegato alle cuffie

Ho collegato il cavo a coccodrillo rosso al pin 23, che è un pin di terra (qualsiasi pin di terra va bene). Il PWM può essere generato attraverso uno qualsiasi dei pin GP. Come si può vedere nella foto qui sopra, sto usando GP0.

Ogni moderno set di cuffie o jack stereo ha tre o quattro sezioni: le due più esterne sono l'audio sinistro e l'audio destro. Pertanto, ho collegato il cavo a coccodrillo rosso a sinistra e il cavo a coccodrillo nero a destra.

Se seguite questa impostazione, fate attenzione che i cavi a coccodrillo non si tocchino.

Un'altra immagine del Pico W collegato alle cuffie

Una volta terminata la configurazione fisica, il passo successivo è il download dei file audio.

Se si utilizzano file .wav, assicurarsi che corrispondano alle seguenti specifiche:

1. canale: mono (non stereo)

2. velocità di trasmissione: 22 kHz o inferiore

3. campionamento: 16 bit

Ecco un esempio di codice per generare audio con .wav utilizzando 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 si utilizzano file .mp3, che devono soddisfare le stesse specifiche dei file .wav, si può fare riferimento a questo esempio di codice:

# 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

Un'altra cosa a cui prestare attenzione con i file .mp3 è la velocità di trasmissione costante. Se l'audio risulta distorto, provare a ridurre ulteriormente la frequenza di campionamento e il bit rate. Il bit rate più alto che sono riuscito a ottenere è 192 kbps.

Se si utilizza MicroPython piuttosto che CircuitPython, si può ricorrere al seguente esempio di codice, che genera 3 toni distinti con PWM impostando il duty cycle e variando la frequenza.

# 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)

Protocolli di comunicazione

Affinché il Pico W e le parti esterne possano comunicare tra loro, sono disponibili diversi protocolli. Se avete lavorato con la libreria machine di MicroPython, sapete che il protocollo di comunicazione standard per i pin GPIO di Pico W è la libreria GPIOZero, che fornisce una semplice interfaccia per il controllo e il monitoraggio dei pin GPIO. Inoltre, Pico W supporta anche altri protocolli di comunicazione.

In questa sede, vorrei parlare un po' di UART, SPI, I2C e I2S, in quanto tutti offrono alcuni vantaggi e svantaggi nel lavoro con l'audio su Pico W.

In primo luogo, il protocollo Universal Asynchronous Receiver/Transmitter, o UART, è teoricamente semplice da implementare, richiede solo 2 fili ed è altamente affidabile. Tuttavia, l'UART ha una portata e una larghezza di banda limitate e può supportare la comunicazione solo con un altro dispositivo.

Inoltre, nel corso delle mie ricerche, ho scoperto che l'UART non è adatto a riprodurre suoni sul Pico W. Se avete utilizzato l'UART sul Pico W per l'uscita audio, fatemelo sapere nei commenti!

SPI

Mentre l'UART non è sufficiente, l'interfaccia periferica seriale, o SPI, offre un'elevata velocità di trasferimento dei dati ed è estremamente affidabile. I suoi maggiori svantaggi sono legati al fatto che l'SPI consente un solo controllore e utilizza 4 fili. Sono riuscito a farlo funzionare utilizzando il seguente codice:

# 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

Il terzo protocollo su cui vorrei soffermarmi è il Circuito di integrazione, o I2C, che richiede solo 2 fili, è semplice da implementare e richiede poca energia. Come l'UART, l'I2C ha una portata limitata e una larghezza di banda limitata, ma a differenza dell'UART può comunicare con più dispositivi. Tuttavia, la cosa più importante è che sono riuscito a far funzionare I2C con il seguente codice:

# 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')

Poiché I2C è meno complesso e supporta più dispositivi su un singolo bus, è probabilmente la scelta migliore per le applicazioni audio più semplici che richiedono trasferimenti di dati inferiori. Tuttavia, SPI potrebbe essere più adatto per la riproduzione audio di qualità superiore, poiché supporta velocità di trasferimento dati più elevate.

I2S

Il quarto e ultimo protocollo di cui voglio parlare è Inter-Integrated Sound, o I2S, che è un protocollo ottimizzato per il trasferimento di dati audio. I2S è di gran lunga il protocollo ideale per la generazione di audio dal Pico W, perché fornisce audio ad alta fedeltà, trasferisce i dati rapidamente, è estremamente affidabile e facile da usare. Inoltre, esiste un'ampia scelta di componenti hardware che supportano I2S, come ad esempio il pacchetto audio Pimoroni Pico.

Pacchetto audio Pimoroni Pico

Per ascoltare il suono dell'I2S, devo passare da MicroPython a CircuitPython, poiché l'I2S non è ancora supportato da MicroPython. Tuttavia, è possibile spingere delicatamente il Pico Audio Pack sugli header del Pico W, assicurandosi che il lato 'USB' del Pico Audio Pack sia rivolto verso la direzione della micro USB del Pico W.

Collegate le cuffie o un altro dispositivo e il gioco è fatto!

Un'altra cosa: per questa volta è meglio abbassare un po' il volume (mi ringrazierete più tardi!).

Ecco il codice di esempio:

# 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

Dal momento che il Pico W sta mostrando le sue capacità wireless, permettetemi di mostrarvi qualcosa che fa uso della connettività wireless.

Questo codice acquisisce un mp3 da un URL e lo riproduce da un file una volta completato il download:

# 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"

Si noti che il download dell'mp3 potrebbe richiedere più tempo della durata effettiva dell'audio. Affinché funzioni, ho dovuto abilitare i permessi di scrittura e aumentare la memoria per il download tramite garbage collection in boot.py:

# PiCockpit.com

import gc
import storage

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

Aggiungete una memoria esterna, tirate fuori qualche pulsante e provate a riprodurre musica in streaming da alcune stazioni radio online. Nulla potrà impedirvi di ascoltare la vostra musica preferita con il Raspberry Pi Pico W.

Conclusione

Il Pico W offre una serie di opzioni diverse per la produzione del suono, ma bisogna fare attenzione allo spazio limitato che offre.

Questo post è basato su un articolo scritto originariamente da Zita B.

3 commenti

  1. Rob Frohne in Dicembre 19, 2023 il 9:23 pm

    Sono stato in grado di utilizzare l'UART per inviare i suoni tramite USB. È possibile ottenere quasi 6 Mbps con il Pico USB. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer in Febbraio 23, 2024 il 8:07 pm

    È molto utile! Copre tutti i metodi, anziché solo i "preferiti". Proprio quello che mi serviva per insegnare il mio corso di Elettronica Analogica Avanzata, dove utilizzo il PICO da qualche parte per la maggior parte degli esperimenti. Toni di base in PWM fino alla funzione I2S di alta qualità! Spero che altri vedano il mio commento!

  3. HarloTek in Marzo 9, 2024 il 11:52 am

    E il Bluetooth?

Lascia un commento