Alles over geluidsuitvoer met de Pico W

Geluid op de Pico W Titelafbeelding

De Raspberry Pi Pico W is een krachtig microcontrollerbord dat is gebouwd rond de eigen microcontrollerchip van de Raspberry Pi, de RP2040. Je kunt een selectie van RP2040-gebaseerde microcontrollerborden bekijken hier Of lees verder om alles te weten te komen over geluidsuitvoer met de Pico W.

Heb je ooit overwogen om een muziekspeler te maken, een audiospectrumanalyzer te maken of een beatbox te bouwen, maar liep je tegen problemen aan bij het afspelen van audio met je microcontroller? Zoek dan niet verder, dit is het artikel voor jou!

Hier laat ik je kennismaken met het universum van Pulsbreedtemodulatie (PWM), communicatieprotocollen voor de Pico W en hoe je voordeel kunt halen uit de draadloze connectiviteit van de Pico W.

Pico W Afbeelding

Pulsbreedtemodulatie

Een manier om geluid af te spelen op de Pico W (of een andere microcontroller) is via Pulsbreedtemodulatie, kortweg PWM.

PWM is een techniek die wordt gebruikt om analoge apparaten te besturen met behulp van een digitaal signaal. Hoewel de digitale uitgang van een microcontroller alleen aan of uit (0 of 1) kan zijn, kunnen we met PWM een analoog signaal nabootsen. (Het is vermeldenswaard dat PWM geen echt analoog signaal is.) PWM bereikt dit door de voedingstoestand snel te schakelen tussen aan en uit en zo de stroom te regelen.

Je kunt hier meer te weten komen over PWM!

En bekijk onze video:

Omdat PWM superefficiënt is, heel weinig stroom verliest en uiterst nauwkeurig is, is dit de techniek die in veel toepassingen wordt gebruikt om verschillende audio-effecten te creëren.

Elke pin op de Pico W is geschikt voor PWM. Het is gewoon een kwestie van coderen!

Ik zal een voorbeeld geven zodat je PWM in actie kunt zien.

Project

Om het genereren van audio met PWM van de Pico W te demonstreren, gebruikte ik 2 krokodillenkabels met mannelijke uiteinden, een breadboard en een koptelefoon.

Als je niet wilt solderen, kun je ook proberen het andere uiteinde van de krokodillenkabels rechtstreeks op je koptelefoon of stereostekker te klemmen (Ik raad ten zeerste af om een koptelefoon te gebruiken, gewoon om je trommelvliezen te beschermen!).

Een Pico W aangesloten op een hoofdtelefoon

Ik heb de rode krokodillenkabel in de 23e pin gestoken, wat een massapin is (elke massapin is goed). En PWM kan worden gegenereerd via elk van de GP-pinnen. Zoals je op de foto hierboven kunt zien, gebruik ik GP0.

Elke moderne hoofdtelefoonset of stereoaansluiting heeft drie of vier secties, waarvan de buitenste twee links en rechts zijn. Ik heb daarom de rode krokodillenkabel aan de linkerkant geklemd en de zwarte krokodillenkabel aan de rechterkant.

Als je deze opstelling volgt, zorg er dan voor dat je krokodillenkabels elkaar niet raken.

Nog een afbeelding van de Pico W aangesloten op een hoofdtelefoon

Zodra je klaar bent met de fysieke installatie, is de volgende stap het downloaden van audiobestanden.

Als je .wav-bestanden gebruikt, zorg er dan voor dat ze voldoen aan de volgende specificaties:

1. kanaal: mono (niet stereo)

2. bitsnelheid: 22 kHz of lager

3. Bemonstering: 16 bit

Hier is een codevoorbeeld om audio met .wav te genereren met 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")

Als je .mp3-bestanden gebruikt, die aan dezelfde specificaties moeten voldoen als het .wav-bestand, kun je dit codevoorbeeld raadplegen:

# 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

Nog iets waar je op moet letten bij .mp3-bestanden is de constante bitsnelheid. Als de audio vervormd klinkt, probeer dan de sample rate en de bit rate verder te verlagen. De hoogste bitsnelheid die ik kon krijgen was 192 kbps.

Als je MicroPython gebruikt in plaats van CircuitPython, kun je het volgende codevoorbeeld gebruiken, dat 3 verschillende tonen genereert met PWM door de duty cycle in te stellen en de frequentie te variëren.

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

Communicatieprotocollen

Om de Pico W en externe onderdelen met elkaar te laten communiceren, zijn er een aantal verschillende protocollen beschikbaar. Als je ooit hebt gewerkt met de machinebibliotheek in MicroPython, dan weet je dat het standaard communicatieprotocol voor de GPIO-pinnen op de Pico W de GPIOZero-bibliotheek is, die een eenvoudige interface biedt voor het besturen en controleren van de GPIO-pinnen. Daarnaast ondersteunt de Pico W ook andere communicatieprotocollen.

Hier wil ik het hebben over UART, SPI, I2C en I2S, omdat ze allemaal bepaalde voor- en nadelen hebben bij het werken met audio op de Pico W.

Ten eerste is het Universal Asynchronous Receiver/Transmitter protocol, of UART, theoretisch eenvoudig te implementeren, vereist slechts 2 draden en is zeer betrouwbaar. UART heeft echter een beperkt bereik, een beperkte bandbreedte en kan slechts communicatie met één ander apparaat ondersteunen.

Verder heb ik tijdens mijn onderzoek ontdekt dat UART niet geschikt is voor het afspelen van geluid op de Pico W. Als je UART hebt gebruikt op de Pico W voor audio-uitvoer, laat het me dan weten in de comments!

SPI

Terwijl UART de klus niet klaart, biedt de Serial Peripheral Interface, of SPI, hoge gegevensoverdrachtsnelheden en is extreem betrouwbaar. Het grootste nadeel is dat SPI maar één controller toestaat en 4 draden gebruikt. Ik kreeg het aan de praat met de volgende code:

# 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

Het derde protocol dat ik wil bespreken is het Inter-Integrate Circuit, of I2C, dat slechts 2 draden nodig heeft, eenvoudig te implementeren is en weinig stroom verbruikt. Net als UART heeft I2C een beperkt bereik en een beperkte bandbreedte, maar in tegenstelling tot UART kan I2C communiceren met meerdere apparaten. Het belangrijkste is echter dat ik I2C werkend kan krijgen met de volgende code:

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

Gebaseerd op het feit dat I2C minder complex is en meerdere apparaten op een enkele bus ondersteunt, is het waarschijnlijk de betere keuze voor eenvoudigere audiotoepassingen die lagere gegevensoverdrachten vereisen. SPI kan echter geschikter zijn voor audioweergave van hogere kwaliteit, omdat het snellere gegevensoverdrachtsnelheden ondersteunt.

I2S

Het vierde en laatste protocol dat ik wil bespreken is Inter-Integrated Sound, of I2S, een protocol dat geoptimaliseerd is voor het transporteren van audiogegevens. I2S is verreweg het ideale protocol voor het genereren van audio uit de Pico W, omdat het hifi-audio levert, gegevens snel overdraagt, extreem betrouwbaar is en eenvoudig te gebruiken is. Bovendien is er een ruime keuze aan hardwarecomponenten die I2S ondersteunen, zoals de Pimoroni Pico Audio Pack.

Pimoroni Pico Audio Pack

Om te horen hoe I2S klinkt, moet ik overschakelen van MicroPython naar CircuitPython, omdat I2S nog niet wordt ondersteund in MicroPython. Maar je kunt het Pico Audio Pack voorzichtig op de headers van de Pico W duwen, waarbij je ervoor zorgt dat de 'USB'-kant van het Audio Pack in de richting van de micro USB van de Pico W wijst.

Sluit je hoofdtelefoon aan, of een ander apparaat dat je wilt, en klaar is kees!

Nog één ding: misschien wil je je volume een beetje zachter zetten voor deze (je zult me later dankbaar zijn!)

En hier is de voorbeeldcode:

# 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

Aangezien de Pico W draadloze mogelijkheden laat zien, zal ik je iets laten zien dat gebruik maakt van draadloze connectiviteit.

Deze code haalt een .mp3 op van een URL en speelt het vervolgens af vanuit een bestand zodra de download is voltooid:

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

instellingen.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"

Merk op dat het downloaden van de .mp3 langer kan duren dan de werkelijke lengte van de audio. Om dit te laten werken, moest ik ook schrijfrechten inschakelen en het geheugen voor de download vergroten door garbage collection in boot.py:

# PiCockpit.com

import gc
import storage

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

Voeg externe opslag toe, gebruik wat knoppen en probeer muziek te streamen van enkele online radiostations. Niets kan je er dan nog van weerhouden om van je favoriete muziek te genieten met de Raspberry Pi Pico W.

Conclusie

De Pico W heeft een aantal verschillende opties voor het produceren van geluid, maar let wel op de beperkte ruimte die de Pico W biedt.

Dit bericht is gebaseerd op een artikel dat oorspronkelijk is geschreven door Zita B.

3 Opmerkingen

  1. Rob Frohne op december 19, 2023 op 9:23 pm

    Ik heb de UART kunnen gebruiken om geluidsfragmenten over USB te versturen. Je kunt bijna 6Mbps halen met de Pico USB. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer op februari 23, 2024 op 8:07 pm

    Dit is super handig! Je behandelt alle methoden, in plaats van alleen een 'favoriet'. Precies wat ik nodig had voor het lesgeven in Analoge Elektronica voor Gevorderden, waarbij ik de PICO ergens voor gebruik voor de meeste experimenten. Basistonen in PWM tot en met hoogwaardige I2S-functies! Ik hoop dat anderen mijn commentaar zien!

  3. HarloTek op maart 9, 2024 op 11:52 am

    Hoe zit het met Bluetooth?

Laat een reactie achter