Cheap stereo line out I2S DAC for CircuitPython / Arduino synths

I am a big fan of these PCM5102A I2S DAC boards (3 for $15! affilliate) for playing around with audio synthesis, for example in this video about new `synthio` CircuitPython library or this repository about Mozzi Arduino synthesis. They have pretty high-quality audio output up to at least 16-bit @ 44.1kHz and separate out all the audio circuitry onto its own board with its own regulator! This makes it much less likely that your circuit’s noise will infect your audio. And the line out is strong enough to drive most headphones. The board itself is pretty small too at 29mm x 16mm.

Hooking the up PCM5102 board is pretty easy for the general case:

  • SCK – Gnd
  • BCK – I2S bit clock pin
  • DIN – I2S data pin
  • LCK – I2S word select pin
  • GND – Gnd
  • VIN – 3.3V

In the case of hooking up the PCM5102 board to an Adafruit QT Py RP2040, one way to do that is like this:

The corresponding CircuitPython setup using audiobusio.I2SOut() for the above wiring looks like:

import board, audiobusio
i2s_bck_pin = board.MOSI # PCM5102 BCK pin
i2s_lck_pin = board.MISO # PCM5102 LCK pin
i2s_dat_pin = board.SCK  # PCM5102 DIN pin
audio = audiobusio.I2SOut(bit_clock=i2s_bck_pin, 
                          word_select=i2s_lck_pin, 
                          data=i2s_dat_pin)

Note that the back of the PCM5102 board has several solder jumpers to configure the chip. Most vendors will set these to a good default value, but verify if they match the above inset photo above or the yellow box in the photo below.

Most of the information I gleaned about the PCM5102 board I got from this macsbug blog post. It is very handy. From it is also the schematic for the PCM5102 board, included below:

Here’s a complete SD-card based WAV player using a Raspberry Pi Pico and a PCM5102

And the CircuitPython code that plays WAVs off an SD card:

# i2s_sdcard_pico.py -- I2S Audio from SD Card on RP2040 Pico
# 20 May 2022 - @todbot / Tod Kurt

import time
import board, busio
import audiocore, audiomixer, audiobusio
import sdcardio, storage, os

# pin definitions
i2s_bclk = board.GP9  # BCK on PCM5102 (connect PCM5102 SCK pin to Gnd)
i2s_wsel = board.GP10 # LCK on PCM5102
i2s_data = board.GP11 # DIN on PCM5102
sd_mosi = board.GP19
sd_sck = board.GP18
sd_miso = board.GP16
sd_cs = board.GP17

# sd card setup
sd_spi = busio.SPI(clock=sd_sck, MOSI=sd_mosi, MISO=sd_miso)
sdcard = sdcardio.SDCard(sd_spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

# audio setup
audio = audiobusio. I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
                         bits_per_sample=16, samples_signed=True)
audio.play(mixer) # attach mixer to audio playback

# find all WAV files on SD card
wav_fnames =[]
for filename in os.listdir('/sd'):
    if filename.lower().endswith('.wav') and not filename.startswith('.'):
        wav_fnames.append("/sd/"+filename)
wav_fnames.sort()  # sort alphanumerically for mixtape numbered order

print("found WAVs:")
for fname in wav_fnames:
    print("  ", fname)

while True:
    # play WAV file one after the other
    for fname in wav_fnames:
        print("playing WAV", fname)
        wave = audiocore.WaveFile(open(fname, "rb"))
        mixer.voice[0].play(wave, loop=False )
        time.sleep(3)  # let WAV play a bit
        mixer.voice[0].stop()

QTPy-knob: Simple USB knob w/ CircuitPython

I like minimal solutions to problems. I was playing with a CircuitPython-enabled QT Py on a breadboard with and a rotary encoder and I ended up making a USB knob, like many others have done before. But I realized: waitaminute, I can literally just plug the encoder directly onto the QT Py…

Thus was born the QTPy-knob. It’s one of the simplest USB knobs I’ve come across and it’s because the happenstance that a rotary encoder can usefully be plugged directly into a QT Py board and that CircuitPython is so powerful now it’s just a few lines to go from rotary encoder pulses to sending arbitrary USB keyboard or mouse commands.

I really liked the Griffin PowerMate from over a decade ago, so I decided to design a 3d-printable enclosure that echos the design of the PowerMate, but that works with the restrictions of the QTPy+encoder stackup and a Neopixel ring. The result is pretty good I think.

All design files are in the qtpy-knob github repo.

The code is must easier now than the media-dial project I started cribbing from. This is due to the fact that CircuitPython now has native support for rotary encoders and this knob is trying to do much less than media-dial project.

The electrical wiring is virtually non-existent, only needing three wires if you elect to add a Neopixel ring.

The assembly is described in the video above a bit, but you can also see what’s up in this CAD animation (thanks to the Adafruit_CAD_Parts repo for making the enclosure design easier)

The result with opaque plastic and clear LED diffuser ends up looking pretty cool.