Все о выводе звука с помощью Pico W

Звук на Pico W Титульное изображение

Raspberry Pi Pico W - это мощная микроконтроллерная плата, построенная на базе собственной микросхемы Raspberry Pi - RP2040. Вы можете ознакомиться с подборкой плат микроконтроллеров на базе RP2040 здесь Или продолжайте читать, чтобы узнать все, что вам нужно знать о выводе звука с помощью Pico W.

Вы когда-нибудь задумывались о создании музыкального проигрывателя, анализатора спектра звука или битбокса, но сталкивались с проблемами при воспроизведении звука с помощью микроконтроллера? Тогда смотрите дальше - эта статья для вас!

Здесь я познакомлю вас со вселенной широтно-импульсной модуляции (ШИМ), протоколами связи для Pico W, а также расскажу о том, как использовать преимущества беспроводной связи Pico W.

Изображение Pico W

Широтно-импульсная модуляция

Одним из способов воспроизведения звука на Pico W (или любом другом микроконтроллере) является широтно-импульсная модуляция, или сокращенно ШИМ.

ШИМ - это метод управления аналоговыми устройствами с помощью цифрового сигнала. Хотя цифровой выход микроконтроллера может быть только включен или выключен (0 или 1), с помощью ШИМ мы можем имитировать аналоговый сигнал. (Следует отметить, что ШИМ не является настоящим аналоговым сигналом). ШИМ достигается за счет быстрого переключения состояния питания между включением и выключением и, таким образом, управления током.

Более подробно о ШИМ можно узнать здесь!

И посмотрите наш видеофильм:

Поскольку ШИМ отличается высокой эффективностью, малыми потерями энергии и высокой точностью, именно эта технология используется во многих приложениях для создания различных звуковых эффектов.

Каждый вывод Pico W способен работать с ШИМ. Это просто вопрос кодирования!

Давайте рассмотрим пример, чтобы вы могли увидеть ШИМ в действии.

Проект

Для демонстрации генерации звука с помощью ШИМ от Pico W я использовал 2 кабеля типа "крокодил" с наружными наконечниками, макетную плату и наушники.

В качестве альтернативы, если вы хотите избежать пайки, вы можете попробовать зажать другой конец кабеля "крокодил" непосредственно на наушниках или стереоштекере (Я настоятельно рекомендую не использовать наушники, чтобы не повредить барабанные перепонки!).

Pico W, подключенный к наушникам

Я подключил красный кабель "крокодил" к 23-му выводу, который является заземлением (подойдет любой заземляющий вывод). А ШИМ можно генерировать через любой из выводов GP. Как видно на фотографии выше, я использую GP0.

Любой современный наушник или стереоразъем имеет три или четыре секции - две крайние: левая и правая. Поэтому я подключил красный кабель "крокодил" к левому разъему, а черный - к правому.

При использовании этой схемы необходимо следить за тем, чтобы кабели "крокодил" не соприкасались.

Еще одно изображение Pico W, подключенного к наушникам

После завершения физической настройки следующим шагом будет загрузка аудиофайлов.

Если вы используете файлы .wav, убедитесь, что они соответствуют следующим требованиям:

1. канал: моно (не стерео)

2. скорость передачи данных: 22 кГц или ниже

3. дискретизация: 16 бит

Ниже приведен пример кода для генерации звука в формате .wav с помощью 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")

Если вы используете файлы .mp3, которые должны соответствовать тем же спецификациям, что и файл .wav, можно обратиться к этому примеру кода:

# 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

Еще один момент, на который следует обратить внимание при работе с файлами .mp3, - это постоянная скорость передачи данных. Если звук звучит искаженно, попробуйте еще снизить частоту дискретизации и битрейт. Наибольшая скорость передачи данных, которую мне удалось получить, составляла 192 кбит/с.

Если вы используете MicroPython, а не CircuitPython, то можете обратиться к следующему примеру кода, который генерирует 3 разных тональных сигнала с помощью ШИМ, задавая рабочий цикл и изменяя частоту.

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

Коммуникационные протоколы

Для того чтобы Pico W и внешние компоненты могли взаимодействовать друг с другом, существует несколько различных протоколов. Если вы когда-либо работали с библиотекой machine в MicroPython, то знаете, что стандартным протоколом связи для выводов GPIO на Pico W является библиотека GPIOZero, которая предоставляет простой интерфейс для управления и мониторинга выводов GPIO. Кроме того, Pico W поддерживает и другие коммуникационные протоколы.

Здесь я хочу немного рассказать о UART, SPI, I2C и I2S, поскольку все они имеют определенные преимущества и недостатки при работе с аудио на Pico W.

Во-первых, протокол универсального асинхронного приемника/передатчика (UART) теоретически прост в реализации, требует всего двух проводов и отличается высокой надежностью. Однако UART имеет ограниченный радиус действия, ограниченную пропускную способность и может поддерживать связь только с одним другим устройством.

Кроме того, в ходе своих исследований я обнаружил, что UART не подходит для воспроизведения звука на Pico W. Если вы использовали UART на Pico W для вывода звука, пожалуйста, сообщите мне об этом в комментариях!

SPI

Если UART не справляется с поставленной задачей, то последовательный периферийный интерфейс, или SPI, обеспечивает высокую скорость передачи данных и является очень надежным. Его основные недостатки связаны с тем, что SPI допускает использование только одного контроллера и использует 4 провода. Мне удалось заставить его работать, используя следующий код:

# 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

Третий протокол, который я хочу затронуть, - это Inter-Integrate Circuit, или I2C, который требует всего 2 провода, прост в реализации и потребляет мало энергии. Как и UART, I2C имеет ограниченный радиус действия и ограниченную пропускную способность, но, в отличие от UART, I2C может взаимодействовать с несколькими устройствами. Но самое главное, что мне удалось заставить I2C работать с помощью следующего кода:

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

Исходя из того, что I2C менее сложен и поддерживает работу нескольких устройств на одной шине, он, вероятно, является лучшим выбором для более простых аудиоприложений, требующих меньшей скорости передачи данных. Однако для воспроизведения звука более высокого качества может оказаться более подходящим SPI, поскольку он поддерживает более высокую скорость передачи данных.

I2S

Четвертый и последний протокол, который я хочу обсудить, - это Inter-Integrated Sound, или I2S, - протокол, оптимизированный для передачи аудиоданных. I2S, безусловно, является идеальным протоколом для генерации звука с Pico W, поскольку он обеспечивает высокое качество звука, быстро передает данные, очень надежен и прост в использовании. Кроме того, существует широкий выбор аппаратных компонентов, поддерживающих I2S, таких как Pimoroni Pico Audio Pack.

Pimoroni Pico Audio Pack

Чтобы услышать, как звучит I2S, мне придется переключиться с MicroPython на CircuitPython, поскольку I2S в MicroPython пока не поддерживается. Но вы можете аккуратно вставить Pico Audio Pack в разъемы Pico W, убедившись, что сторона "USB" Audio Pack направлена в сторону micro USB Pico W.

Подключите наушники или другое устройство - и все!

И еще одно: возможно, вам захочется немного убавить громкость (потом вы скажете мне спасибо!).

А вот пример кода:

# 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

Поскольку Pico W демонстрирует беспроводные возможности, позвольте мне показать вам кое-что, использующее беспроводное подключение.

Этот код захватывает .mp3 с URL-адреса, а затем воспроизводит его из файла после завершения загрузки:

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

Обратите внимание, что загрузка .mp3 может занять больше времени, чем реальная длина аудиофайла. Для того чтобы это сработало, мне также пришлось включить права на запись и увеличить память для загрузки за счет сборки мусора в boot.py:

# PiCockpit.com

import gc
import storage

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

Добавьте внешнюю память, нажимайте кнопки и пробуйте транслировать музыку с некоторых онлайн-радиостанций. После этого ничто не помешает вам наслаждаться любимой музыкой с помощью Raspberry Pi Pico W.

Заключение

Pico W обладает множеством различных возможностей для воспроизведения звука, только не забывайте об ограниченном пространстве, которое предлагает Pico W.

Это сообщение основано на статье, первоначально написанной Зитой Б.

3 комментариев

  1. Rob Frohne Декабрь 19, 2023 в 9:23 пп

    Я смог использовать UART для передачи звуковых фрагментов по USB. С помощью Pico USB можно получить почти 6 Мбит/с. https://github.com/earlephilhower/arduino-pico/discussions/1765

  2. Prof Patrick Palmer Февраль 23, 2024 в 8:07 пп

    Это очень полезно! Вы охватываете все методы, а не только "любимые". Как раз то, что мне было нужно для преподавания курса "Продвинутая аналоговая электроника", где я использую PICO для большинства экспериментов. Базовые сигналы в ШИМ, вплоть до высококачественных функций I2S! Надеюсь, другие увидят мой комментарий!

  3. HarloTek Март 9, 2024 в 11:52 дп

    А как насчет Bluetooth?

Комментировать