2026-07-18 mitch

For an idle project between jobs, I've been planning some kind of toy for Avery that will primarily be a lot of different buttons that play sounds, light up LED strips. For the "play sounds" part, I bought a VS1053b (https://www.vlsi.fi/fileadmin/datasheets/vs1053.pdf) on a so-called "Music Maker FeatherWing" board (https://www.adafruit.com/product/3357). Being a "featherwing", this board is pin-compatible with Adafruit's line of "feather" dev boards, which I've been using lately for projects. In this instance, I chose the ESP32 feather (https://www.adafruit.com/product/5400) in case I want to incorporate wifi or bluetooth into this project down the line. I chose MicroPython (https://micropython.org/) for the firmware platform.

SDCard

The first challenge I had with the Music Maker was not the audio codec at all, but the SD card. MicroPython implements machine.SDCard, which claims to support SD/MMC cards on the ESP32 via SPI, which is how the Music Maker is wired. However, their documentation (https://docs.micropython.org/en/latest/library/machine.SDCard.html#esp32) includes this caveat:

SPI mode makes use of a SPI host peripheral, which cannot concurrently be used for other SPI interactions.

This was a real fly in the ointment, since I exactly needed to concurrently access the audio codec on the same SPI bus. Fortunately, there is a separate SDCard implementation (https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py) in the non-built-in micropython-lib (https://docs.micropython.org/en/latest/reference/glossary.html#term-micropython-lib) that takes the SPI client as an argument, so, using that, I was able to use machine.SoftSPI along with the dedicated chip-select (CS) pin for the Music Maker's SD card and control both the VS1053b's chips and the SD card chip from the same SPI bus.

This was especially welcome, because it allowed me to use the filesystem drivers in MicroPython's vfs module (https://docs.micropython.org/en/latest/library/vfs.html), instead of the shoddy, read-only VFAT/FAT32 driver (and MBR/"dos" disklabel parser) that I wrote myself.

VLSI VS1053b Audio Codec

Now that I could open a file from the SDCard and read bytes from it, I kind of jumped into the middle of things and tried writing those bytes directly to the VS1053b's "data" chip (selected by pulling XDCS low). To my surprise and delight, this worked on the first try-- I could hear sound from the headphones I'd plugged into the Music Maker. The delight did not last long, however, since, when I tried playing longer sounds and especially music I discovered that the output was audibly slower than it should be, seemed to be skipping and clipping, and generally sounded awful.

The skipping was fixed by actually following the protocol alluded to in the datasheet (DS) section 7.3.1. Namely:

Note that when sending data through SDI you have to check the Data Request Pin DREQ at least after every 32 bytes

However, the sound was still sluggish and low-quality. My initial thought was that I was feeding the codec too slowly, so I implemented a larger buffer between the SDCard read and the codec write, but that had no effect. In retrospect, I shouldn't have bothered, since the first problem I had (skipping from overrunning the codec / not waiting on the DREQ pin) proved that I was writing too quickly, not too slowly. This issue had me baffled for a few hours, until I started looking for other driver implementations and ended up reading through the one provided by Adafruit, the merchant I'd bought the boards from. They provide a driver written in C++ (https://github.com/adafruit/Adafruit_VS1053_Library/blob/master/Adafruit_VS1053.cpp) and another in CircuitPython (https://github.com/adafruit/Adafruit_CircuitPython_VS1053/blob/main/adafruit_vs1053.py). Both implementations contain an uncommented and seemingly undocumented use of the CLOCKF SCI register as part of their reset process (from the C++: https://github.com/adafruit/Adafruit_VS1053_Library/blob/ee1b0de61107db2c0f45bce55d057f83e0ec07ed/Adafruit_VS1053.cpp#L521). Both Adafruit libraries write 0x6000 (bits 13 and 14) to this register. According to the documentation (DS 9.6.4) bits 13-15 are interpreted as a 3-bit "Clock multiplier", so these libraries set a clock multiplier of 3x when resetting the VS1053b. When I did the same in my implementation, it was like magic-- the lag vanished and the sound quality got substantially better.

Python driver

I love to reinvent the wheel as much as the next software engineer, so here is my MicroPython implementation of a VS1053(b) driver. It's worth noting that the CircuitPython driver (linked above) leads with the following caveat:

This is not currently working for audio playback of files.  Only sine
wave test currently works.  The problem is that pure Python code is currently
too slow to keep up with feeding data to the VS1053 fast enough.

I haven't spent any time with CircuitPython, but this statement isn't true of MicroPython, at least on the ESP32. I have been able to play full-length songs in both MP3 and OGG/Vorbis encoding with (to my ear) perfect quality and no skipping. In fact, when I print a log message any time DREQ comes down during the process of feeding bytes to the VS1053's SDI, it comes down very frequently, implying that my setup is more than fast enough to feed it, and, in fact, the codec is telling me to slow my roll.