Alles über die Tonausgabe mit dem Pico W

Ton auf dem Pico W Titelbild

Das Raspberry Pi Pico W ist ein leistungsfähiges Mikrocontroller-Board, das auf dem eigenen Mikrocontroller-Chip des Raspberry Pi, dem RP2040, basiert. Sie können eine Auswahl an RP2040-basierten Mikrocontroller-Boards ausprobieren hier oder lesen Sie weiter, um alles zu erfahren, was Sie über die Tonausgabe mit dem Pico W wissen müssen.

Hast du jemals daran gedacht, einen Musikplayer zu bauen, einen Audio-Spektrum-Analysator zu entwickeln oder eine Beatbox zu bauen, bist aber auf Probleme gestoßen, Audio mit deinem Mikrocontroller abzuspielen? Dann ist dieser Artikel genau das Richtige für dich!

Hier werde ich Sie in das Universum der Pulsweitenmodulation (PWM) und in die Kommunikationsprotokolle für den Pico W einführen und Ihnen zeigen, wie Sie die Vorteile der drahtlosen Konnektivität des Pico W nutzen können.

Pico W Bild

Pulsweitenmodulation

Eine Möglichkeit, Töne auf dem Pico W (oder einem anderen Mikrocontroller) abzuspielen, ist die Pulsweitenmodulation, kurz PWM.

PWM ist eine Technik, mit der analoge Geräte durch ein digitales Signal gesteuert werden können. Obwohl der digitale Ausgang eines Mikrocontrollers immer nur ein- oder ausgeschaltet sein kann (0 oder 1), können wir mit PWM ein analoges Signal imitieren. (Es ist erwähnenswert, dass PWM kein echtes analoges Signal ist.) PWM erreicht dies durch schnelles Umschalten des Stromzustands zwischen ein und aus und steuert so den Strom.

Hier können Sie mehr über PWM erfahren!

Und sehen Sie sich unser Video an:

Da PWM äußerst effizient ist, nur wenig Strom verliert und extrem präzise ist, wird diese Technik in vielen Anwendungen zur Erzeugung verschiedener Audioeffekte eingesetzt.

Jeder Pin des Pico W ist PWM-fähig. Es ist nur eine Frage der Codierung!

Ich möchte Ihnen ein Beispiel geben, damit Sie PWM in Aktion sehen können.

Projekt

Zur Demonstration der Audioerzeugung mit PWM vom Pico W habe ich 2 Krokodilkabel mit männlichen Enden, ein Breadboard und Kopfhörer verwendet.

Wenn Sie das Löten vermeiden wollen, können Sie auch versuchen, das andere Ende der Krokodilkabel direkt an Ihren Kopfhörer oder Stereostecker anzuschließen (Ich empfehle dringend, keine Kopfhörer zu benutzen, um Ihre Trommelfelle zu schützen!).

Ein Pico W, angeschlossen an einen Kopfhörer

Ich habe das rote Krokodilkabel an den 23. Pin angeschlossen, der ein Masse-Pin ist (jeder Masse-Pin ist geeignet). Und PWM kann über jeden der GP-Pins erzeugt werden. Wie Sie auf dem Foto oben sehen können, verwende ich GP0.

Jeder moderne Kopfhörersatz oder Stereoklinkenstecker hat drei oder vier Abschnitte - die beiden äußeren sind für den linken und den rechten Ton. Daher habe ich das rote Krokodilkabel links und das schwarze Krokodilkabel rechts angeschlossen.

Wenn Sie so vorgehen, achten Sie darauf, dass sich Ihre Krokodilkabel nicht berühren.

Ein weiteres Bild des Pico W in Verbindung mit einem Kopfhörer

Sobald Sie mit der physischen Einrichtung fertig sind, müssen Sie als Nächstes die Audiodateien herunterladen.

Wenn Sie .wav-Dateien verwenden, stellen Sie sicher, dass sie den folgenden Spezifikationen entsprechen:

1. Kanal: Mono (nicht Stereo)

2. Bitrate: 22 kHz oder niedriger

3. Abtastung: 16 Bit

Hier ist ein Code-Beispiel für die Erzeugung von Audio mit .wav mit 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")

Wenn Sie .mp3-Dateien verwenden, die die gleichen Spezifikationen wie die .wav-Datei erfüllen müssen, können Sie sich an diesem Codebeispiel orientieren:

# 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

Ein weiterer Punkt, auf den Sie bei .mp3-Dateien achten sollten, ist die konstante Bitrate. Wenn der Ton verzerrt klingt, versuchen Sie, die Abtastrate und die Bitrate weiter zu verringern. Die höchste Bitrate, die ich erreichen konnte, war 192 kbps.

Wenn Sie MicroPython und nicht CircuitPython verwenden, können Sie das folgende Code-Beispiel verwenden, das 3 verschiedene Töne mit PWM erzeugt, indem Sie das Tastverhältnis einstellen und die Frequenz variieren.

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

Kommunikationsprotokolle

Damit der Pico W und externe Bauteile miteinander kommunizieren können, gibt es eine Reihe von verschiedenen Protokollen. Wenn Sie schon einmal mit der Maschinenbibliothek in MicroPython gearbeitet haben, wissen Sie, dass das Standard-Kommunikationsprotokoll für die GPIO-Pins des Pico W die GPIOZero-Bibliothek ist, die eine einfache Schnittstelle zur Steuerung und Überwachung der GPIO-Pins bietet. Darüber hinaus unterstützt der Pico W auch andere Kommunikationsprotokolle.

Hier möchte ich ein wenig über UART, SPI, I2C und I2S sprechen, da sie alle bestimmte Vor- und Nachteile bei der Arbeit mit Audio auf dem Pico W bieten.

Erstens ist das Universal Asynchronous Receiver/Transmitter-Protokoll (UART) theoretisch einfach zu implementieren, erfordert nur 2 Drähte und ist äußerst zuverlässig. Allerdings hat UART eine begrenzte Reichweite, eine begrenzte Bandbreite und kann nur die Kommunikation mit einem anderen Gerät unterstützen.

Außerdem habe ich bei meinen Recherchen herausgefunden, dass UART nicht für die Wiedergabe von Ton auf dem Pico W geeignet ist. Wenn Sie UART auf dem Pico W für die Audioausgabe verwendet haben, lassen Sie es mich bitte in den Kommentaren wissen!

SPI

Während UART die Aufgabe nicht erfüllen kann, bietet das Serial Peripheral Interface (SPI) hohe Datenübertragungsraten und ist äußerst zuverlässig. Der größte Nachteil besteht darin, dass SPI nur einen Controller zulässt und 4 Drähte verwendet. Ich konnte es mit dem folgenden Code zum Laufen bringen:

# 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

Das dritte Protokoll, auf das ich eingehen möchte, ist das I2C-Protokoll (Inter-Integrate Circuit), das nur zwei Drähte benötigt, einfach zu implementieren ist und wenig Strom benötigt. Wie UART hat auch I2C eine begrenzte Reichweite und eine begrenzte Bandbreite, aber im Gegensatz zu UART kann I2C mit mehreren Geräten kommunizieren. Am wichtigsten ist jedoch, dass ich I2C mit dem folgenden Code zum Laufen bringen kann:

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

Da I2C weniger komplex ist und mehrere Geräte an einem einzigen Bus unterstützt, ist es wahrscheinlich die bessere Wahl für einfachere Audioanwendungen, die geringere Datenübertragungen erfordern. Für eine qualitativ hochwertige Audiowiedergabe kann SPI jedoch besser geeignet sein, da es schnellere Datenübertragungsraten unterstützt.

I2S

Das vierte und letzte Protokoll, das ich besprechen möchte, ist Inter-Integrated Sound oder I2S, ein Protokoll, das für die Übertragung von Audiodaten optimiert wurde. I2S ist bei weitem das ideale Protokoll für die Erzeugung von Audiosignalen aus dem Pico W, da es eine hohe Audiokompatibilität bietet, Daten schnell überträgt, extrem zuverlässig ist und einfach zu verwenden ist. Darüber hinaus gibt es eine große Auswahl an Hardwarekomponenten, die I2S unterstützen, wie z.B. das Pimoroni Pico Audio Pack.

Pimoroni Pico Audio Pack

Um zu hören, wie sich I2S anhört, muss ich von MicroPython zu CircuitPython wechseln, da I2S in MicroPython noch nicht unterstützt wird. Aber man kann das Pico Audio Pack vorsichtig auf die Stiftleisten des Pico W schieben und dabei darauf achten, dass die 'USB'-Seite des Audio Packs in Richtung des Micro-USB des Pico W zeigt.

Schließen Sie Ihre Kopfhörer oder ein anderes Gerät Ihrer Wahl an, und das war's!

Und noch etwas: Vielleicht sollten Sie die Lautstärke ein wenig herunterdrehen (Sie werden mir später danken!)

Und hier ist der Beispielcode:

# 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

Da der Pico W kabellose Funktionen vorstellt, möchte ich Ihnen etwas zeigen, bei dem die kabellose Verbindung zum Einsatz kommt.

Dieser Code holt sich eine .mp3-Datei von einer URL und spielt sie nach Abschluss des Downloads aus einer Datei ab:

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

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

Beachten Sie, dass das Herunterladen der .mp3-Datei länger dauern kann als die tatsächliche Länge des Audios. Damit dies funktioniert, musste ich auch Schreibrechte aktivieren und den Speicher für den Download durch Garbage Collection in boot.py erhöhen:

# PiCockpit.com

import gc
import storage

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

Fügen Sie einen externen Speicher hinzu, drücken Sie ein paar Knöpfe und versuchen Sie, Musik von einigen Online-Radiosendern zu streamen. Dann kann Sie nichts mehr davon abhalten, Ihre Lieblingsmusik mit dem Raspberry Pi Pico W zu genießen.

Schlussfolgerung

Der Pico W bietet eine Reihe verschiedener Optionen für die Klangerzeugung. Achten Sie nur auf den begrenzten Platz, den der Pico W bietet.

Dieser Beitrag basiert auf einem Artikel, der ursprünglich von Zita B. geschrieben wurde.

3 Kommentare

  1. Veröffentlich von Rob Frohne am Dezember 19, 2023 um 9:23 pm

    Ich konnte den UART verwenden, um Soundbites über USB zu senden. Mit dem Pico USB kann man fast 6 Mbps erreichen. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Veröffentlich von Prof Patrick Palmer am Februar 23, 2024 um 8:07 pm

    Das ist sehr hilfreich! Sie gehen auf alle Methoden ein und nicht nur auf eine "Lieblingsmethode". Genau das, was ich für meine Vorlesung über fortgeschrittene Analogelektronik brauchte, in der ich den PICO für die meisten Experimente verwende. Grundtöne in PWM bis hin zu hochwertigen I2S-Funktionen! Ich hoffe, andere sehen meinen Kommentar!

  3. Veröffentlich von HarloTek am März 9, 2024 um 11:52 am

    Was ist mit Bluetooth?

Hinterlassen Sie einen Kommentar