Few months ago I set myself the challenge of designing a basic FLAC player add-on kit for obsolete Hi-Fi units, something minimally intrusive that would transform them into unassuming remotely controlled audio archive players. Although this project was targeting old CD-players, I took its first integration to another level and chose an 80s tuner as its host, the slim Technics ST-S505(L).
This was a personal choice because the alternative in my possession, an SL-P200, holds no technical or esthetic value. There was another, more practical, feature of this tuner that made it a viable candidate, as I'll explain further down.
On the software side, one of the challenges was integrating a real-time FLAC decoder with the ESP32, using IDF. The embedded libFLAC decoder build, in spite of its documentation, still requires all sorts of stuff that yields a chunky binary, even with the right optimization flags, so much so that needs program flash re-partitioning. Also, making every function involved in decoding run from IRAM was quite an effort that led to another RAM allocation compromise too early in the prototyping stage. I'm sure that a better configuration exists but searching for it wasn't top priority. A second option was the esp-adf library with a whole new C API, its learning curve, and nothing to learn from.
As I wasn't keen on integrating other libraries, I went for my FLAC decoder. Although irrelevant on LX6 cores, a previous test on an x64 machine showed it to be twice as slow compared to libFLAC, so I was a bit concerned about its performance. It was easier to integrate and even after linking all code to IRAM, the decoder required very little program memory. I also needed an application, other than a simple test app, to validate my decoder implementation.
The SPI SD card integration was another necessary step as it allowed reading FLAC files off an SD card. In this aspect IDF is sufficiently flexible in terms of tooling: one can mount a FAT partition using the FATFS library and register it with a virtual filesystem (VFS), prefixed by a base path. After configuring the module for the maximum SPI clock (20 MHz) that ESP32 can handle, I was able to decode FLAC tracks in under their playback duration. That was encouraging as it meant real-time playback was possible and that my decoder wasn't all that bad.
Audio CD tracks use a sampling rate of 44.1 kHz and my player, for now, supports that and anything below it. Also, samples larger than 16 bit will be right-shifted to fit PCM56's register. With my perception capabilities, these specs are sufficient for a very good, pure and simple playback experience.
PCM56 signal timing chart for one sampleTo insure a quasi-accurate playback, a lightweight timer-triggered ISR function fetches left-right samples off a buffer and bit-bangs them serially to both DAC chips. It was quite exciting at first to observe that the ISR can coexist with the SD reader and FLAC decoder. However, there were occasional hiccups for higher polynomial order FLAC frames. With everything else already stripped to minimum, I had to increase ESP32's clock to its maximum 240 MHz in order to insure real-time playback.
In spite of a steady overall playback sample frequency, the instant timing is not. This random jitter is caused by the fight for run-time resources with the decoder and SD card reader. Moving the timer and ISR on the other core solves this problem but requires a carefully chosen memory ordering for the inter-core shared objects. However, a trade-off had to be made later-on with the WiFi and web server that eliminated that option. So, the entire playback stack was confined to one core, other than the networking one. In spite of being a known cause of distortion, I'm inclined to say the jittery version sounds slightly more joyful for a reason that eludes me, as if dither has been applied to the signal.
was split in two modules: DAC and logic. This is helpful on the long run as either of the two can be improved independently. The immediate benefit in this particular implementation is that of better space use within the host's case.
The first module consists of the two PCM56 DAC chips, one UPC4570 op-amp with its voltage dividers and signal filtering capacitors, lots of decoupling capacitors, two relays for switching power and output signals, their drivers and a SMPS for the logic board. The last one was a compromise to fit space constraints.
Decoupling got quite some attention. I chose to use pairs of 100nF + 100uF X7R MLCC in parallel for each analog power pin of each chip to insure a stable current supply. PCM56's digital power pins, being more tolerant, got just one regular 1uF tantalum per rail and per chip (as specified). Moreover, each digital/analog power rail is fed through a 4R7 resistor from the corresponding, common regulated one. Their purpose is to have a tiny drop at DC and dissipate more at high-frequency transients, further reducing noise propagated to the analog power rail. Ferrite beads would have been inappropriate given the noise band (under 10 MHz - caused by DAC clock).
On the DAC output some effort needed to be made to lower the sampling rate noise and its harmonics. I went for an active 2nd order low-pass multiple feedback architecture and, because I wanted to flip the phase of the PCM56 voltage output, I chose an inverting circuit. Call me names, this was a fixation of mine. A future exploration path would be the use of the PCM56's current output with an external I/V and non-inverting active filtering circuitry.
PCM56 offers a MSB error adjustment method, which is very important for achieving lowest distortion levels. Too good of an offer not to be accepted! As such, the DAC module PCB includes traces and footprints for said circuitry. As I found perceptually difficult to fine-tune K-rated PCM56 chips, my implementation doesn't include the multi-turn trim-pots.
PCM56 stereo DAC board - SMD face; GNDA and GNDD connected in one place; resistor isolated digital supply rails.Both DAC power rails are obtained from one AC transformer secondary, that of the host. This is perfectly legal, the tuner does it too (with a different ground potential) but requires bigger filtering as each rails is fed by one half cycle. The module draws a total of 80mA, which is three times less than the tuner with smaller capacitors.
The PCB for this module was made on a single-sided board with jumpers, much like the tuner. Since the gap below the board allowed it, most of the auxiliary parts are SMD and placed on the back. Hashed ground areas fill the remaining board surface, excepting below the AC/DC convertor. Hashing angle differ for visual distinguishing GNDA and GNDD, although direct ground lines exist for a minimum impedance return path.
Few corrections have been made to the PCB as I omitted the fact that relays were polarized and to add extra dissipation resistors before the regulators.
The logic board, in this particular case needed to be placed vertically in a tight corner, had to contain the ESP32 with at least one decoupling capacitor, the SD card slot with its pull-up resistors and another decoupling capacitor, the serial communication connector with adaptations for supporting DTR/RTS and two buttons for Reset/Boot selection. I could've had either the adaptations or the buttons but it's always good to have both.
Logic board: ESP32, micro-SD card socket, serial port with reset + boot capabilities.This PCB was trickier to manufacture. Every time I'm dealing with a serious MCU, I choose a dual-layer board with copper pour on the bottom for digital GND - which had to be scarred by several small traces with no impact on current loops. As one can imagine, top/bottom mask positioning was of key importance such that the small vias and solder-mask pads align reasonably well. Because it had a rather high local part density, I had to use 0603 SMD resistors, the smallest I ever hand-soldered. Some parts (connectors and momentary switches) are THT because I didn't have other types in stock.
Manufactured logic board: build quality limited by the employed DIY techniques.Thorough consideration was given to power and signal grounding, to reduce interference between analog and digital supplies as well as current loops for high frequency signals. Heat dissipation in the DAC power supply circuit was distributed along the line with several resistors such that the 78L05 and 79L05 regulators don't surpass the rated power.
DAC and logic boards, alignedThe PCB manufacturing never gets any better when slight errors pile-up at every processing step. Toner transfer works great for narrow traces (the signal ones on both boards have 0.4mm) but gets crappy for copper pours. That's the main reason I used hashed patters for filled areas. In spite of poor execution techniques the outcome is always functional: traces conduct, solder mask isolates.
an ST-S505, was chosen for the fact that it didn't had a grid voltage filtering board, unlike the ST-S707. It should've, but the constructor chose not to put one. This left the space for a board of maximum 15 x 5 cm, which proved borderline sufficient for the DAC board. The case is similar for both models and have board clamps punched-out of the bottom piece to attach the optional grid-line filtering board. Although they were a limiting trace routing factor, made a perfectly snug and screw-less fixation mechanism for my board.
ST-S505 bottom case piece: punched-out PCB clamps creating a micro-SD card sized openingOne of the punched-out profile creates a very useful 18 x 20 mm opening on the side, ideal for positioning vertically a micro-SD card slot. Even with the lid on, the card can easily be accessed.
ST-S505 case hosting the add-on player boards instead of the optional grid filtering ferrite beadsSome precautions were taken with the DAC board, which contains an AC/DC convertor. 300V rated wires have been used to feed it, I have spaced connector pins and traces to twice the 2.54 mm pitch, I applied plastic glue over any exposed high-potential contact, placed a thick plastic insulation sheet under the boards and stuck a 230V warning sticker on it. The AC/DC convertor has its own fuse so the dedicated PCB footprint was bypassed. Oh, and by the way...
Warning:
This project involves high voltage! Touching a live wire is fatal!
Make sure you know what you're doing before operating with high voltage circuits!
Both tuner and player circuits share the same ground potential. This is an important electrical characteristic. To achieve this, both pins of the transformer secondary need to be simultaneously switched between host and player, otherwise there will always remain a path for a rectified DC current between the tuner and player circuits with undefined (likely destructive) results. A DPDT relay solved that problem. For system safety, both circuits will share the low voltage 500 mA fuse from the tuner board. Thus, one PCB jumper (J) was replaced with wires (A and B) going to/from the relay connector on the DAC board and two others (C and D) to/from the fuse and its holder.
ST-S505 power transformer secondary; J removed; for tuner operation: A and B are connected, also C and DSwitching the output signal between tuner and player is done with another DPDT small signal relay controlled by the logic board. Connecting this with minimum interference on the host's PCB was trickier. Both left(L)/right(R) AF signal pins from the back panel connectors were disconnected from the board and re-routed via the relay. Each tap wire is twisted with a signal ground (GND) wire, creating the essential ground interconnection.
ST-S505 AF output; RCA connector pins de-soldered and connected to output relayTuner's output signal wires terminate with a female 2.54 mm pitch, 3-pin connector and the system's (relay's) output wires use a similar 3-pin male connector. Likewise, the AC voltage coming from the transformer secondary enters the board via a female 2.54 mm pitch, 2-pin connector while the tuner's AC input leaves the power relay with a male connector. This is especially useful when removing the add-on boards: all corresponding connectors can be mated together, yielding a regular, unmodified tuner. Being confident this will remain a permanent mod, I hot-glued the connectors side-by-side. This is a lot more practical as it eliminates any source of confusion about what goes where.
WiFi signal was routed outside the host case from ESP32's U.FL. plug with a 30 cm coaxial cable terminating in a 6 dBi omnidirectional antenna. Any other possibly radiating WiFi path is enclosed in the shielded WROVER package and, at its 2.4 GHz, is way above the receiver's band.
The MCU's 240 MHz operational clock is 2x above the reception capabilities of the host while the 40 MHz crystal is below it and, again, all is shielded inside the WROVER package. The SD card's SPI 20 Mhz clock is below radio usable range. The DAC clock, data and latch-enable signals are below it still and the switch-mode AC/DC supply, operating under 100 kHz, has no impact on it. The relay can generate transients when they disengage but the fly-back diode reacts quickly and besides, at those transition states the tuner is either stopped or stopping.
ST-S505 containing the ESP32 FLAC player; complete re-assembly with connections to the hostThe only remaining interference could come from the low-impedance audio output wires crossing the case to the player and back to the back panel. However, the board under the wires hosts the tuner's power supply and MPX stereo decoder, both of which cannot be affected by a GND twisted-pair driving a high impedance load (amplifier input). At 60 Hz, the transformer could radiate some but not enough energy to influence a high-impedance audio output, 10-15 cm away.
ST-S505 front-end located at the opposite corner of the PCBThe most sensitive piece of circuitry in the tuner, the front-end, is located in the opposite corner of the board, protected with a shielded case.
None of the signals generated by the player can possibly influence the host or vice-versa.
Few hundred listening hours later and I'm still impressed. The sound is pure - getting goose bumps to female voices. Unplugged instrumental performances are detailed and spacious. K-rated PCM56 have their merits, yes, but a decent PCB and better decoupling plays an important role. I like everything about it and I'm yet to find perceivable flaws to the sound.
At low levels the sound may be dull, much like it is on my PC's audio card but a whole new dimension opens by cranking-up the volume. That's explainable: if one wants the 96dB dynamic range of the 16bit DAC, one needs to bring the speaker's output around that level. My Classix II speakers have Dayton DC-160 woofers, rated 86dB @1W/1m - not exactly sensitive - so I have to put 3-4 watts into them to experience the DAC capabilities. I'm also suspecting my Technics SU-Z65 amp to be less detailed at low levels. In spite of its New Class A label - which was a fancy marketing name for class B with a bias improvement - the S/N is not at its best and could benefit from an ICQ tuning.
Usability-wise, I can remotely browse tracks from my phone, play/stop them and switch between tuner and player - great for skipping commercials on radio! A nice feature that I've been using a lot is the software volume control - heresy in audiophile terms. At this point I'm perfectly happy with this set of features and, as a bonus, my FLAC decoder proves stable.
At some point I'd like to experiment with a 2x oversampling - which would mean doubling the timer ISR calls in the detriment of decoding. The single core playback stack is working at capacity already so that would be an interesting experiment. A cross-core distribution of tasks would be an option but WiFi and networking runs high-priority tasks which starve of execution time the others. I'd also like to try other MCUs (some STMs?) other DAC chips, bigger samples and rates, more channels... scope creep looms at every corner. Anyway, such projects are mesmerizing, subjective and addictive to some extent. There's room for improvements at all levels and the perfectionist could easily spend years in a quest for, well, perfection. Personally, I intend to freeze this in a couple of weeks as I've reached my goals, use it as it is and gather feedback. Any new intel could be conveyed into a new future integration, most likely another (Technics?) model.
Check-out esp32-audio-player repo on GitHub for hardware/firmware details.