Allt om ljudåtergivning med Pico W

Ljud på Pico W Titelbild

Raspberry Pi Pico W är ett kraftfullt mikrokontrollerkort byggt kring Raspberry Pis alldeles egna mikrokontrollerkrets, RP2040. Du kan kolla in ett urval av RP2040-baserade mikrokontrollerkort här eller fortsätt läsa för att få reda på allt du behöver veta om ljudåtergivning med Pico W.

Har du någonsin funderat på att göra en musikspelare, skapa en ljudspektrumanalysator eller bygga en beatbox, men stött på problem med att spela upp ljud med din mikrokontroller? Då behöver du inte leta längre, det här är artikeln för dig!

Här kommer jag att introducera dig till PWM (Pulse-Width Modulation), kommunikationsprotokoll för Pico W och hur du kan dra nytta av Pico W:s trådlösa anslutning.

Pico W Bild

Pulsbreddsmodulering

Ett sätt att spela upp ljud på Pico W (eller någon annan mikrokontroller för den delen) är genom Pulse-Width Modulation, eller PWM, som det förkortas.

PWM är en teknik som används för att styra analoga enheter med hjälp av en digital signal. Även om en mikrokontrollers digitala utgång bara kan vara på eller av (0 eller 1), kan vi med PWM efterlikna en analog signal. (Det är värt att nämna att PWM inte är en sann analog signal.) PWM uppnår detta genom att snabbt växla strömtillståndet mellan på och av och därigenom styra strömmen.

Du kan läsa mer om PWM här!

Och kolla in vår video:

Eftersom PWM är supereffektivt, förlorar väldigt lite ström och är extremt exakt, är det den teknik som används i många applikationer för att skapa olika ljudeffekter.

Varje stift på Pico W kan användas för PWM. Det är bara en fråga om kodning!

Låt mig gå igenom ett exempel så att du kan se PWM i aktion.

Projekt

För att demonstrera ljudgenerering med PWM från Pico W använde jag två krokodilkablar med hanändar, ett breadboard och hörlurar.

Om du vill undvika lödning kan du prova att klämma den andra änden av krokodilkablarna direkt på hörlurarna eller stereokontakten (Jag rekommenderar starkt att du inte använder hörlurar, bara för att skydda dina trumhinnor!).

En Pico W ansluten till hörlurar

Jag anslöt den röda krokodilkabeln till det 23:e stiftet, som är ett jordstift (vilket jordstift som helst duger). PWM kan genereras via vilket som helst av GP-stiften. Som du kan se på bilden ovan använder jag GP0.

Alla moderna hörlurar eller stereojack har tre eller fyra sektioner - där de två yttre är vänster ljud och höger ljud. Jag klippte därför den röda alligatorkabeln till vänster och den svarta alligatorkabeln till höger.

Om du använder den här inställningen måste du se till att dina krokodilkablar inte rör varandra.

En annan bild av Pico W ansluten till hörlurar

När du är klar med den fysiska installationen är nästa steg att ladda ner ljudfiler.

Om du använder .wav-filer ska du se till att de uppfyller följande specifikationer:

1. kanal: mono (inte stereo)

2. Bithastighet: 22 kHz eller lägre

3. Sampling: 16 bitar

Här är ett kodexempel för att generera ljud med .wav med 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")

Om du använder .mp3-filer, som måste uppfylla samma specifikationer som .wav-filen, kan du hänvisa till detta kodexempel:

# 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

Ytterligare en sak att tänka på när det gäller .mp3-filer är den konstanta bithastigheten. Om ljudet låter förvrängt kan du försöka sänka samplingsfrekvensen och bithastigheten ytterligare. Den högsta bithastighet jag kunde få var 192 kbps.

Om du använder MicroPython istället för CircuitPython kan du titta på följande kodexempel, som genererar 3 distinkta toner med PWM genom att ställa in arbetscykeln och variera frekvensen.

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

Kommunikationsprotokoll

För att Pico W och externa delar ska kunna kommunicera med varandra finns det ett antal olika protokoll tillgängliga. Om du någonsin har arbetat med maskinbiblioteket i MicroPython vet du att standardkommunikationsprotokollet för GPIO-stiften på Pico W är GPIOZero-biblioteket, som tillhandahåller ett enkelt gränssnitt för styrning och övervakning av GPIO-stiften. Dessutom stöder Pico W även andra kommunikationsprotokoll.

Här vill jag prata lite om UART, SPI, I2C och I2S, eftersom de alla erbjuder vissa fördelar och nackdelar när man arbetar med ljud på Pico W.

För det första är protokollet Universal Asynchronous Receiver/Transmitter, eller UART, teoretiskt sett enkelt att implementera, kräver bara två kablar och är mycket tillförlitligt. UART har dock begränsad räckvidd, begränsad bandbredd och kan bara stödja kommunikation med en annan enhet.

Dessutom fann jag under min forskning att UART inte är lämpligt för att spela upp ljud på Pico W. Om du har använt UART på Pico W för ljudutmatning, låt mig gärna veta i kommentarerna!

SPI

Medan UART inte klarar av jobbet, erbjuder Serial Peripheral Interface, eller SPI, höga dataöverföringshastigheter och är extremt tillförlitligt. De största nackdelarna är att SPI bara tillåter en styrenhet och använder 4 kablar. Jag lyckades få det att fungera med hjälp av följande kod:

# 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

Det tredje protokollet jag vill ta upp är Inter-Integrate Circuit, eller I2C, som bara behöver två kablar, är enkelt att implementera och kräver lite ström. Precis som UART har I2C begränsad räckvidd och begränsad bandbredd, men till skillnad från UART kan I2C kommunicera med flera enheter. Viktigast av allt är dock att jag kan få I2C att fungera med följande kod:

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

Eftersom I2C är mindre komplext och stöder flera enheter på en enda buss, är det förmodligen ett bättre val för enklare ljudtillämpningar som kräver lägre dataöverföringar. SPI kan dock vara mer lämpligt för ljuduppspelning av högre kvalitet, eftersom det stöder snabbare dataöverföringshastigheter.

I2S

Det fjärde och sista protokollet jag vill diskutera är Inter-Integrated Sound, eller I2S, som är ett protokoll som har optimerats för överföring av ljuddata. I2S är det absolut bästa protokollet för att generera ljud från Pico W, eftersom det ger högupplöst ljud, överför data snabbt, är extremt tillförlitligt och enkelt att använda. Dessutom finns det ett brett urval av hårdvarukomponenter som stöder I2S, t.ex. Ljudpaketet Pimoroni Pico.

Pimoroni Pico Ljudpaket

För att höra hur I2S låter måste jag byta från MicroPython till CircuitPython, eftersom I2S ännu inte stöds i MicroPython. Men du kan försiktigt trycka in Pico Audio Pack på Pico W:s kontaktdon och se till att "USB"-sidan på Audio Pack är vänd i samma riktning som Pico W:s mikro-USB.

Anslut dina hörlurar eller någon annan enhet som du vill ha, och det är klart!

En sak till: du kanske vill skruva ner volymen lite för den här gången (du kommer att tacka mig senare!)

Och här är exempelkoden:

# 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

Eftersom Pico W visar upp trådlösa funktioner, låt mig visa dig något som använder sig av trådlös anslutning.

Den här koden hämtar en .mp3 från en URL och spelar sedan upp den från en fil när nedladdningen är klar:

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

inställningar.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"

Observera att nedladdningen av .mp3-filen kan ta längre tid än den faktiska längden på ljudet. För att detta skulle fungera var jag också tvungen att aktivera skrivrättigheter och öka minnet för nedladdningen genom garbage collection i boot.py:

# PiCockpit.com

import gc
import storage

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

Lägg till extern lagring, ta fram några knappar och försök att strömma musik från några onlineradiostationer. Inget kan sedan hindra dig från att njuta av din favoritmusik med Raspberry Pi Pico W.

Slutsats

Pico W har ett antal olika alternativ för att producera ljud, se bara till att vara försiktig med det begränsade utrymme som Pico W erbjuder.

Detta inlägg är baserat på en artikel som ursprungligen skrevs av Zita B.

3 Kommentarer

  1. Rob Frohne den december 19, 2023 kl 9:23 e m

    Jag har kunnat använda UART för att skicka ljudbitar över USB. Du kan få nästan 6 Mbps med Pico USB. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer den februari 23, 2024 kl 8:07 e m

    Detta är till stor hjälp! Du täcker alla metoder, snarare än bara en "favorit". Precis vad jag behövde för att undervisa min klass om avancerad analog elektronik, där jag använder PICO någonstans för de flesta experiment. Grundläggande toner i PWM hela vägen till högkvalitativ I2S-funktion! Jag hoppas att andra ser min kommentar!

  3. HarloTek den mars 9, 2024 kl 11:52 f m

    Hur är det med Bluetooth?

Lämna en kommentar