Skip to content

jeroen94704/TeensyDSPDAC

Repository files navigation

WARNING

This is a work in progress. Feel free to build your own (I've verified the design works), but be aware this will not be a plug-and-play experience, as the design needs some work.

Introduction

This is a 4-channel USB DAC with advanced DSP capabilities. The DSP functionality is provided by a Teensy 4.0 and the DAC stage is built around a pair of TI PCM5102A's.

Beauty shot of the PCB rendered by KiCad

The Teensy's UART1, I2C0 and I2C1 ports are available through dedicated pin-headers. The UART is compatible with an FTDI 6-pin dupont connector (although, inevitably, I swapped RX and TX). The I2C headers are pin-compatible with those ubiquitous I2C OLED display modules. The remaining unused Teensy pins are broken out on a pair of pin-headers on both sides of the board.

This DSP/DAC is a companion project to my take on the HexiBase mini-subwoofer. I wanted a way to properly match that sub to the pair KEF Q150's I use as desktop speakers, and this seemed like a fun project to do myself. I'm not an electronics engineer, so this is completely based off of the reference circuits in the respective components' datasheets.

Programming the DSP

DSP functionality can be designed using PJRC's Audio System Design Tool. Initially I wanted to use Chip Audette's OpenAudio F32 Design Tool, but that library does not support the I2S2 output, which this board needs. The code generated by these tools is not a complete Arduino Sketch, but can be copy-pasted into an existing sketch. For example, below is a design of a DSP for a 2.1 speaker setup. It takes a USB stereo input signal and creates a separate subwoofer channel with just the low frequencies:

A DSP design consisting of a USB input, 2 filters, a mixer and 2 I2S outputs

This design results in the following code:

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputUSB            usb1;           //xy=167.1666717529297,283.1666564941406
AudioFilterStateVariable filter2;        //xy=403.16668701171875,341.16668701171875
AudioFilterStateVariable filter1;        //xy=407.16668701171875,218.16665649414062
AudioMixer4              mixer1;         //xy=569.1666870117188,284.1666564941406
AudioOutputI2S2          i2s2_1;         //xy=718.1666870117188,347.16668701171875
AudioOutputI2S           i2s1;           //xy=721.1666870117188,231.16665649414062
AudioConnection          patchCord1(usb1, 0, filter1, 0);
AudioConnection          patchCord2(usb1, 1, filter2, 1);
AudioConnection          patchCord3(filter2, 0, mixer1, 1);
AudioConnection          patchCord4(filter2, 2, i2s2_1, 0);
AudioConnection          patchCord5(filter1, 0, mixer1, 0);
AudioConnection          patchCord6(filter1, 2, i2s1, 0);
AudioConnection          patchCord7(mixer1, 0, i2s1, 1);
// GUItool: end automatically generated code

Programming the Teensy

The code generated by the Design Tool can be pasted into an existing Arduino sketch after downloading and installing Teensyduino. The variable declarations are enough to start the DSP process, so if all you want is audio then the only things you need to add are a call to AudioMemory, unmuting the DAC's in setup() and handling the volume set on the host in the loop() function. So given the above code, the complete sketch could be as simple as:

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputUSB            usb1;           //xy=167.1666717529297,283.1666564941406
AudioFilterStateVariable filter2;        //xy=403.16668701171875,341.16668701171875
AudioFilterStateVariable filter1;        //xy=407.16668701171875,218.16665649414062
AudioMixer4              mixer1;         //xy=569.1666870117188,284.1666564941406
AudioOutputI2S2          i2s2_1;         //xy=718.1666870117188,347.16668701171875
AudioOutputI2S           i2s1;           //xy=721.1666870117188,231.16665649414062
AudioConnection          patchCord1(usb1, 0, filter1, 0);
AudioConnection          patchCord2(usb1, 1, filter2, 1);
AudioConnection          patchCord3(filter2, 0, mixer1, 1);
AudioConnection          patchCord4(filter2, 2, i2s2_1, 0);
AudioConnection          patchCord5(filter1, 0, mixer1, 0);
AudioConnection          patchCord6(filter1, 2, i2s1, 0);
AudioConnection          patchCord7(mixer1, 0, i2s1, 1);
// GUItool: end automatically generated code

void setup() 
{
    // Allocate memory for the Audio connections
    AudioMemory(12);

    // Unmute the DAC's
    pinMode(PIND5, OUTPUT);
    digitalWrite(PIND5, HIGH);
}

void loop() 
{
    // @TODO Handle USB volume
}

If you want to add more functionality, such as controls (buttons, dials) or a display you can implement this in the same sketch.

Bit-depth and volume control (a bit of a rant, I'm afraid)

While both the processor on the Teensy and the external DACs are 32-bit capable, I belatedly discovered the Teensy Audio Library is entirely 16-bit. This is quite unfortunate, as this has implications for the final sound quality. Here's the problem: The Teensy registers as a 16-bit capable USB sound device, and outputs 16-bit data over I2S to the DACs. For maximum sound quality we want to utilize the full 16-bit range, as this results in the most accurate analog waveforms. However, the DACs have a 2.1 Vrms line-level output, which is very high. This line-level signal is amplified by a power-amp before it can drive the speakers. A power-amp has a fixed gain, for example 20, 30 or 40 dB, which corresponds to 10, 32 or 100 times the input voltage. But even a modest gain of 20 dB would turn the 2.1 Vrms signal of the DACs into a speaker-destroying, ear-splitting amplified signal, so we need to have some form of volume control. This is done by adding attenuation (the opposite of amplification) somewhere in this chain. We can do this digitally on the host PC or Teensy or analog(ly?) on the line-level signal. Attenuating the speaker-level signal would produce large amounts of heat, so that's not an option.

Digital volume control reduces the bit-depth, because in order to reduce the peak-peak voltage of the output signal we need to feed the DAC with smaller numbers (= fewer bits). If our digital processing is done using 24- or 32-bits this is not a problem since most source-material (including CD-quality) is 16-bit. A 16-bit signal can be attenuated by -48dB (for 24-bit DSP) or -96dB (for 32-bit DSP) without loosing any information/sound-quality. Unfortunately at this point we don't have that luxury with the Teensy. It receives, processes and outputs audio data fully in 16-bits, so attenuating the signal by e.g. -18 dB (1/8th gain) already reduces the bit-depth from 16 to 13.

That leaves us with analog volume control. In practice, this means running the line-level signal through a voltage divider, typically a logarithmic potentiometer with 2 or more stacked together for multi-channel volume control. Unfortunately anything you do with an anlog signal introduces some amount of noise, which gets amplified and therefore impacts sound-quality.

All of the above is partly specific to my situation. I purchased 3 cheap mono amplifiers without understanding any of this, and it turns out they are hardwired with a 36 dB gain. This would turn the 2.1 Vrms signal into a monstrous 132 Vrms speaker signal if the amps were capable of producing that. In practice, of course, they are limited by their supply voltage and start clipping the signal long before that. But in my case this means the signal needs attenuation by -16 dB just to prevent the amps from clipping. And even then the speaker-level signal is way too loud to be usable. So I'm still debating what to do with this. I might try to modify the mono-amps to a lower gain, and I'm also looking into ways to do the DSP in 32 bits. I'll probably need to do both, I suspect.

Cost

It's a pretty compact board (87x47mm, or about 3.42" x 1.85"), so getting the bare PCB fabricated by one of the large cheap Chinese PCB manufacturers is very affordable. For reference, I would have paid ~€3 for 5 boards (€0.60 per board). However, as an experiment I decided to try the assembly service they offer. With the through hole and pricier SMD (DACs and voltage regulator) components omitted this came to a total of ~€27 for 5 partially populated boards (€5.40 per board). The remaining components (excluding the pinheaders and screw terminals, which I have in stock by default) cost about :

  • Teensy 4.0: €27
  • 2x PCM5102A DAC: €3.40
  • 1x LT1962 3.3V regulator: €1.85
  • USB-C port: €1

So the total is about €40 per board for components + partial assembly. Now you can't order just 1 board, so it's more expensive than that, but if you do all soldering yourself I expect you would save at another €10 or so. Obviously this design is not on par with high-end audio gear, of course, but given that many consumer-grade USB DACs use the same DAC chip, and the DSP features are similar to something like a miniDSP I'd say this is a pretty good deal.

Enclosure and Amplifier

The board has mounting holes for installation in an enclosure, but they are not placed to fit any particular model. If you use a metal enclosure make sure is is connected to the plated mounting holes (on the Teensy/USB-C port side of the board), which connects to the protective earth of the USB-C port.

For my own build I plan to create a custom, 3D-printed enclosure and I will add a link to that design once it's done.

While it's probably superfluous to mention this for most: The output of this board is a line-level signal, so you need to add an amplifier before you can hook up speakers. Which amplifier(s) to get is up to you and beyond the scope of this document. There are MANY 1-, 2- and 4-channel amplifiers to choose from, either in module-form (for integration into your own DSP+DAC+AMP) or standalone off-the-shelf.

Donate

If you find this project useful a small donation is much appreciated (but by no means required or expected): https://ko-fi.com/jeroen94704

License

This design is ©2023 Jeroen Bouwens and is licensed under CC BY-NC-SA 4.0. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/

Releases

No releases published