Tout sur la sortie du son avec le Pico W

Le son sur le Pico W Image de titre

Le Raspberry Pi Pico W est une carte de microcontrôleur puissante construite autour de la puce de microcontrôleur propre au Raspberry Pi, le RP2040. Vous pouvez consulter une sélection de cartes à microcontrôleur basées sur le RP2040 ici ou continuez à lire pour découvrir tout ce que vous devez savoir sur la sortie du son avec le Pico W.

Avez-vous déjà envisagé de créer un lecteur de musique, un analyseur de spectre audio ou une boîte à rythmes, mais vous avez rencontré des problèmes pour lire de l'audio à l'aide de votre microcontrôleur ? Ne cherchez plus, cet article est fait pour vous !

Je vous présenterai ici l'univers de la modulation de largeur d'impulsion (PWM), les protocoles de communication pour le Pico W, et comment tirer parti de la connectivité sans fil du Pico W.

Pico W Image

Modulation de largeur d'impulsion

L'une des façons de produire du son sur le Pico W (ou tout autre microcontrôleur d'ailleurs) est la modulation de largeur d'impulsion, ou PWM, en abrégé.

Le PWM est une technique utilisée pour contrôler des appareils analogiques à l'aide d'un signal numérique. Bien que la sortie numérique d'un microcontrôleur ne puisse être qu'activée ou désactivée (0 ou 1), la MLI permet d'imiter un signal analogique (il convient de préciser que la MLI n'est pas un véritable signal analogique). (Il convient de préciser que le PWM n'est pas un véritable signal analogique.) Le PWM y parvient en commutant rapidement l'état de l'alimentation entre marche et arrêt et en contrôlant ainsi le courant.

Pour en savoir plus sur le PWM, cliquez ici !

Et regardez notre vidéo :

Le PWM étant très efficace, perdant très peu d'énergie et étant extrêmement précis, c'est la technique utilisée dans de nombreuses applications pour créer différents effets audio.

Chaque broche du Pico W est capable de PWM. C'est juste une question de codage !

Je vais vous donner un exemple pour que vous puissiez voir le PWM en action.

Projet

Pour démontrer la génération d'audio avec PWM à partir du Pico W, j'ai utilisé 2 câbles crocodiles avec des extrémités mâles, une planche à pain et des écouteurs.

Si vous voulez éviter les soudures, vous pouvez aussi essayer de brancher l'autre extrémité des câbles crocodiles directement sur vos écouteurs ou sur une prise stéréo (Je recommande vivement de ne pas utiliser d'écouteurs, afin de protéger vos tympans !).

Un Pico W relié à des écouteurs

J'ai branché le câble crocodile rouge sur la 23e broche, qui est une broche de masse (n'importe quelle broche de masse fera l'affaire). Le PWM peut être généré par n'importe quelle broche GP. Comme vous pouvez le voir sur la photo ci-dessus, j'utilise GP0.

Tout casque moderne ou prise stéréo comporte trois ou quatre sections, les deux extérieures étant l'audio gauche et l'audio droite. J'ai donc branché le câble crocodile rouge à gauche et le câble crocodile noir à droite.

Si vous suivez cette méthode, veillez à ce que vos câbles crocodiles ne se touchent pas.

Une autre image du Pico W relié à des écouteurs

Une fois l'installation physique terminée, l'étape suivante consiste à télécharger les fichiers audio.

Si vous utilisez des fichiers .wav, assurez-vous qu'ils répondent aux spécifications suivantes :

1. canal : mono (pas stéréo)

2. débit binaire : 22 kHz ou moins

3. échantillonnage : 16 bits

Voici un exemple de code pour générer de l'audio avec .wav en utilisant 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")

Si vous utilisez des fichiers .mp3, qui doivent répondre aux mêmes spécifications que le fichier .wav, vous pouvez vous référer à cet exemple de code :

# 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

Une autre chose à laquelle il faut faire attention avec les fichiers .mp3 est le débit binaire constant. Si le son est déformé, essayez d'abaisser la fréquence d'échantillonnage et le débit binaire. Le débit binaire le plus élevé que j'ai pu obtenir était de 192 kbps.

Si vous utilisez MicroPython plutôt que CircuitPython, vous pouvez vous tourner vers l'exemple de code suivant, qui génère 3 sons distincts avec PWM en réglant le rapport cyclique et en faisant varier la fréquence.

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

Protocoles de communication

Pour que la Pico W et les éléments externes puissent communiquer entre eux, il existe un certain nombre de protocoles différents. Si vous avez déjà travaillé avec la bibliothèque machine de MicroPython, vous savez que le protocole de communication standard pour les broches GPIO de la Pico W est la bibliothèque GPIOZero, qui fournit une interface simple pour contrôler et surveiller les broches GPIO. En outre, la Pico W prend également en charge d'autres protocoles de communication.

Ici, j'aimerais parler un peu de UART, SPI, I2C et I2S, car ils offrent tous certains avantages et inconvénients pour travailler avec l'audio sur le Pico W.

Tout d'abord, le protocole Universal Asynchronous Receiver/Transmitter, ou UART, est théoriquement simple à mettre en œuvre, ne nécessite que deux fils et est très fiable. Cependant, le protocole UART a une portée et une largeur de bande limitées et ne peut prendre en charge que la communication avec un seul autre appareil.

De plus, au cours de mes recherches, j'ai découvert que l'UART n'est pas adapté pour jouer du son sur le Pico W. Si vous avez utilisé l'UART sur le Pico W pour une sortie audio, n'hésitez pas à me le faire savoir dans les commentaires !

SPI

Si l'UART ne suffit pas, l'interface périphérique série, ou SPI, offre des taux de transfert de données élevés et est extrêmement fiable. Ses principaux inconvénients résident dans le fait que SPI n'autorise qu'un seul contrôleur et utilise 4 fils. J'ai réussi à le faire fonctionner en utilisant le code suivant :

# 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

Le troisième protocole que je souhaite aborder est le circuit d'inter-intégration, ou I2C, qui ne nécessite que deux fils, est simple à mettre en œuvre et requiert peu d'énergie. Comme UART, I2C a une portée et une bande passante limitées, mais contrairement à UART, I2C peut communiquer avec plusieurs appareils. Le plus important, cependant, c'est que je suis capable de faire fonctionner l'I2C avec le code suivant :

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

L'I2C étant moins complexe et prenant en charge plusieurs dispositifs sur un seul bus, il s'agit probablement du meilleur choix pour les applications audio plus simples qui nécessitent des transferts de données moins importants. Cependant, SPI peut être plus approprié pour une lecture audio de qualité supérieure, car il prend en charge des taux de transfert de données plus rapides.

I2S

Le quatrième et dernier protocole que je souhaite aborder est Inter-Integrated Sound, ou I2S, qui est un protocole optimisé pour le transport de données audio. I2S est de loin le protocole idéal pour générer de l'audio à partir de la Pico W, car il fournit de l'audio de haute fidélité, il transfère les données rapidement, il est extrêmement fiable et il est facile à utiliser. En outre, il existe une large sélection de composants matériels qui prennent en charge le protocole I2S, tels que le Pimoroni Pico Audio Pack.

Pimoroni Pico Audio Pack

Pour entendre le son de l'I2S, je dois passer de MicroPython à CircuitPython, car l'I2S n'est pas encore pris en charge par MicroPython. Mais vous pouvez pousser doucement le Pico Audio Pack sur les connecteurs du Pico W, en vous assurant que le côté 'USB' de l'Audio Pack est orienté vers le micro USB du Pico W.

Branchez vos écouteurs, ou tout autre appareil de votre choix, et le tour est joué !

Encore une chose : vous devriez peut-être baisser un peu le volume de votre ordinateur (vous me remercierez plus tard !).

Et voici l'exemple de code :

# 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

Puisque le Pico W présente des capacités sans fil, permettez-moi de vous montrer quelque chose qui utilise la connectivité sans fil.

Ce code récupère un .mp3 à partir d'une URL et le lit à partir d'un fichier une fois le téléchargement terminé :

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

Notez que le téléchargement du .mp3 peut prendre plus de temps que la durée réelle de l'audio. Pour que cela fonctionne, j'ai également dû activer les permissions d'écriture et augmenter la mémoire pour le téléchargement par le garbage collection dans boot.py :

# PiCockpit.com

import gc
import storage

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

Ajoutez un espace de stockage externe, sortez quelques boutons et essayez de diffuser de la musique à partir de stations de radio en ligne. Rien ne pourra alors vous empêcher de profiter de votre musique préférée avec le Raspberry Pi Pico W.

Conclusion

Le Pico W offre un certain nombre d'options différentes pour produire du son, mais il faut faire attention à l'espace limité qu'il offre.

Ce billet est basé sur un article écrit à l'origine par Zita B.

3 commentaires

  1. Rob Frohne sur décembre 19, 2023 à 9:23 pm

    J'ai pu utiliser l'UART pour envoyer des sons par USB. Vous pouvez obtenir presque 6Mbps avec le Pico USB. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer sur février 23, 2024 à 8:07 pm

    C'est très utile ! Vous couvrez toutes les méthodes, plutôt qu'une seule méthode "préférée". C'est exactement ce dont j'avais besoin pour mon cours d'électronique analogique avancée, dans lequel j'utilise le PICO quelque part pour la plupart des expériences. Des tonalités de base en PWM jusqu'à la fonction I2S de haute qualité ! J'espère que d'autres verront mon commentaire !

  3. HarloTek sur mars 9, 2024 à 11:52 am

    Qu'en est-il de Bluetooth ?

Laissez un commentaire