Projects Research & Design Hub

A Digital Amplifier for TVs

Written by Brian Millier

Using Teensy 4.1

Using the same store-bought speaker system for both TV audio and playing music means dealing with unwelcome trade-offs. With that in mind, in this project article Brian taps a Teensy 4.1 to build a home-brew digital amplifier for his TV that’s optimized for both TV watching and playing music.

  • How to use Teensy 4.1 to build a home-brew digital amplifier optimized for both TV watching and playing music.

  • How TV digital audio formats work

  • How to make use of the Teensy 4.1 module

  • How to make use of the Teensy Audio System Design Tool

  • How to craft your audio signal flow

  • How to use a  6-band graphic EQ

  • How to develop the circuitry

  • Teensy 4.1 board from PJRC

  • Teensy Audio library from PJRC

  • Everlight’s PLR135/T TOSLINK receiver module

  • Audio System Design Tool from PJRC

  • Princeton Technology’s PT8211 Stereo DAC

  • NXP Semiconductors’ MIMXRT1062 MCU

  • TSOP38238 IR receiver module from Digi-Key

  • Vishay TSOP38238 remote-control receiver

  • OKI-78SR-5/1.5 from Murata Power Solutions

  • Maxim Integrated’s MAX98357 I2S amplifier IC

  • ‎Walfront’s Bluetooth Amplifier Board

Modern, flat-screen TVs have slim bezels and are very thin in depth. As a result, most of them have little space for a decent loudspeaker, so they don’t provide good sound quality unless you augment them with something external. Today that generally means adding a soundbar of some sort, whereas when I purchased my first flat screen TV, a complete “Home Theater” solution was what was being promoted. The marketing influenced me; I purchased a high-quality home theater receiver and five speakers to match the receiver’s 5.1 sound capability.

In the early days of home theater setups, any enhanced audio content on most of the available TV programs was minimal—non-existent if you happened to like the older shows. Even as enhanced “surround” sound became available on the newer programs, what my wife and I found was:

  • 1) The various “sound effects” would only occur occasionally, and they were much too loud.
  • 2) Many programs, such as police shows, took place on city streets, and the traffic sounds often competed with the dialog to the extent that it was hard to understand what was being said.
  • 3) Certain “sound effects” were very low in frequency (they came from the subwoofer) and bothered our two dogs.

The net effect of this was that we stopped turning on the home theater receiver and went back to listening to the TV through its small built-in speaker, which allowed us to understand dialog better. When streaming boxes became popular, I was able to use my TV system to listen to music that I had stored on my NAS drive. Having a good amplifier and speakers was necessary for that purpose.

While I could have continued to use my Yamaha Home Theater receiver, I became interested in building a home-brew digital amplifier for my TV that would serve two different purposes:

  • 1) For regular TV watching, it would send the audio signal through a filter, which was tailored to just pass the frequencies that were contained in normal speech.
  • 2) For listening to music, it would send the audio signal through a 6-band graphic EQ that I could set to suit my preference. I’m in my late 60s but my hearing is still quite good. However, with age, everyone has hearing loss in the upper frequency range found in music.

I have a small music recording studio, which I use as a hobby, and am quite familiar with the various types of digital filters used in the sound recording industry. Since I wanted to do all the TV’s audio signal processing in the digital domain, I thought it made sense to use the TV’s digital audio output, rather than taking the analog “line-out” signals and converting those analog signals into digital format using an analog-to-digital converter (ADC) or audio Codec chip.


I have built many digital audio projects over the last few years, using different modules in the Teensy line. In every case, they used the Teensy’s I2S digital audio interface, which is well-supported by the Teensy Audio library. The I2S interface is a standard 3- or 4-wire audio interface that can be found on virtually all audio Codecs, as well as discrete audio ADCs and DACs. I’ll discuss the Teensy Audio library later, but for now I’ll just mention that it is very easy to use and contains many audio signal-handling and DSP routines.


Advertise Here

All that said, TVs do not use the I2S standard for their digital audio interface. Instead, they use the S/PDIF interface, which comes in two different formats:

Optical: This is what is used by the TOSLINK (Toshiba Link) interface. It transfers the digital signal by means of a single fiber-optic cable.
Coaxial: This uses a coaxial cable to transfer the digital signal, and is commonly terminated using old-fashioned RCA plugs and sockets.

S/PDIF stands for “Sony/Philips Digital Interface.” It has been around for 20 years or so, and is also referred to as IEC958 or IEC60958, after the International Electrotechnical Commission standards to which it adheres. Unlike the I2S interface, which contains separate Data, Clock and Left/Right channel-select lines, the S/PDIF signal is a single digital signal containing data for two or more channels, channel status and clocking information—all in one signal.

This allows for a simple coaxial cable between the TV and the amplifier/receiver, or a single optical fiber in the case of the TOSLINK. However, this is offset by more complexity in the S/PDIF receiver circuitry. The S/PDIF receiver circuit must extract a clock signal from the incoming digital data stream. This is facilitated in S/PDIF by the use of a biphase encoding technique for the data stream. Specifically, it uses the biphase mark code (BMC) format, which has one digital transition per data bit (when bit value=1) or two digital transitions per data bit (when bit value =0). Figure 1 shows the BMC encoding scheme.

FIGURE 1 – S/PDIF signals are encoded using the biphase mark code (BMC) scheme. This allows for relatively easy clock regeneration from the data.

With this type of encoding—regardless of the content of the data stream—there will always be at least one data transition per data-bit. When the data-bit equals 1, there are two transitions, and the second transition occurs in the middle of the data-bit cell time. It is relatively easy for the S/PDIF receiver to regenerate a clock signal from the data stream. It can just ignore the transitions that occur in the middle of the bit cell when the data bit equals 1, and synchronize a clock based on the transitions that occur at the beginning and end of each data-bit cell.

Even though the S/PDIF clock regeneration scheme is not too complex, in most cases it still requires that your microcontroller (MCU) contains a dedicated S/PDIF function block. While all Teensy modules containing 32-bit Arm cores contain one or more I2S interfaces, a dedicated S/PDIF module only became available on the newest Teensy modules—the Teensy4.0 and Teensy4.1. I’ll mention that some power-users of the Teensy family have managed to implement an S/PDIF port using the Teensy’s I2S port, using a software-assisted implementation of the S/PDIF clock regeneration circuitry, which is missing in the I2S module. I have no experience with this. I have only used the Teensy Audio library’s S/ PDIF functions that use the MCU’s dedicated S/PDIF module.


Given the considerations discussed so far, I decided to use a Teensy 4.1 module for this project. This Teensy module contains the hardware S/PDIF interface, and the Teensy Audio library contains functions for both S/PDIF input and output. There are other complications, however. As explained above, the Teensy S/PDIF receiver block must regenerate a clock signal, which it is capable of doing. However, the Teensy Audio library contains many functional blocks that also require a clock signal to allow them to transfer audio data from one block to another in a synchronized fashion. In general, this clock is set to produce a sample rate of 44,100Hz, which is the rate used by music CDs. This clock signal is generated internally by the Teensy’s MCU, itself, using a sophisticated, phase-locked loop (PLL)-based clock generator that is part of the I2S module (though it also can be used for other purposes).

So, you can see the problem—the S/PDIF module must regenerate a clock signal based on the incoming S/PDIF data stream, and the rest of the Teensy Audio library routines use a fixed clock signal that is generated by the MCU, itself (using the PLL-based I2S clock generator). This would lead to synchronization problems, even if the S/PDIF data stream were using a 44,100Hz sample rate (S/PDIF data streams could just as easily use the 48,000Hz standard). That is, even if both used nominal 44,100Hz clocks, they would not be exactly equal, and the synchronization would slowly drift off, causing drop-outs, pops and so forth.

Initially, you could only use the Teensy Audio library’s S/PDIF input function alone or with a matching S/PDIF output function. That worked because the S/PDIF output function uses the regenerated clock provided by the S/ PDIF receiver module. That wouldn’t work for my project, but soon afterward Alex Walch wrote the spdif_async input library, which is now included in the Teensy Audio library.


Advertise Here

This is an excellent piece of coding that captures the S/PDIF input data stream (using a regenerated clock), and then re-samples that data stream to the exact 44,100Hz rate used by the rest of the Teensy Audio library. This was the final piece in the puzzle needed to start designing my project.

On my TV, the S/PDIF digital audio output signal was an optical one, using the TOSLINK physical interface. Figure 2 is a photo of the Everlight PLR135/T TOSLINK receiver module that I used for the project. The PLR135/T TOSLINK is available from Digi-Key. The top section with the hole is a dust-cap that you take off before inserting the TOSLINK fiber-optic cable. The nice thing about using TOSLINK optical modules is that they provide a 3.3V logic level output signal that can be connected directly to the MCU’s S/PDIF input pin. In contrast, the coaxial (RCA) S/ PDIF signal is a 0.5V peak-to-peak signal terminated by 75Ω, which needs some signal-conditioning circuitry to allow it to be sent to the MCU’s S/PDIF input.

FIGURE 2 – The Everlight PLR135/T TOSLINK optical receiver module used in this project

The Teensy Audio library provides about 90 audio routines in the following general categories:

Input: I2S, SPDIF, ADC, Codecs, USB and others
Output: I2S, SPDIF, DAC, Codec, PWM, ADAT, USB and others
Mixer: 4-channel Mixer, Amplifier
Play: Play audio files from SD card, flash, RAM and so on
Record: Sends audio data stream to Arduino program for custom processing
Synth: Various music synthesizer modules
Effect: Various musical effects including reverb, chorus and delay
Filter: Digital filters such as Bi-quad, FIR and Chamberlin
Analyze: FFT, Peak/RMS measurement, frequency counter
Control: Configuration for various Codec chips

Most of the 90 routines are compatible with each other; that is, they can be interconnected and used together. Some exceptions to this are noted in the documentation for each routine.

It’s important to realize that handling a digital audio data stream at the default 44,100Hz sample rate requires the use of a timed interrupt service routine (ISR) at the bare minimum, and in practice requires the use of the DMA capability of the MCU. In the case of the Teensy Audio library, both methods are used. Once the proper timed ISRs and DMA routines are properly configured, they move the audio data stream from an input source to as many processing routines as are needed, and then out to the user-selected output device. The user program doesn’t need to be involved in any of this, apart from setting up the ISR/DMA framework at startup. The code involved in the audio library’s ISR/DMA initial configuration is quite complex, but is entirely transparent to the developer writing a custom program. Once the following statement is found at the start of your program, this framework will be added by the compiler/linker automatically:

#include <audio.h>

While not used in this project, if you wish to have USB audio input or output, additionally you must specify this by selecting the Audio option in the Arduino Tools > USB Type menu.

While the above sets up the ISR/DMA audio framework, it does not include the library routines for any of the 90 functions noted above, which you want to use for your project, nor does it define how they are interconnected. While you could add the necessary code to the start of your program manually, it is much easier to use the Audio System Design Tool provided by PJRC, the company that designed and sells the Teensy modules. This graphical design tool allows you to drag/drop the required functions to a work screen, and interconnect them with the equivalent of “wires” (as if they were hardware modules being wired together).

The Audio System Design Tool is web-based [1]. Figure 3 is a screen capture of this tool, showing the three sections that you work with. At the left is the list of the 90 routines mentioned earlier. Once selected, the Info screen on the right will explain its function, the parameters needed to configure it, and will provide a link to some example programs for that routine.

FIGURE 3 – The Teensy Audio System Design Tool screen, showing the blocks used in this project. The right pane shows help info for the block selected.

You merely drag a function to the center workspace to use it. Once you do so, it will be labeled like the function, itself, but with a “1” or “2” suffix added to differentiate it, if more than one of any given function is present. To interconnect the various functions, you click on the rounded rectangles (inputs on left, outputs on right) and drag a “wire” to another module. Outputs can have several wires connected to them, if necessary, but inputs can only have one wire attached. Once you’re finished, you click on the red Export button at the top, and the follow directions to have the necessary code added to the PC’s clipboard. From there you just paste it at the top of your Arduino sketch.

This automatically generated code consists of three sections:

  • 1) A #include section which specifies all the .h files needed from the audio library
  • 2) A section where the various classes are instantiated, corresponding to the audio functions that you added to the Design Tool workspace
  • 3) The AudioConnection section, which contains the macros needed to generate the code that basically “wires” the different functions together in the configuration you defined in the Design Tool workspace window

Figure 4 shows a screen capture of the workspace that I configured for this project, with some explanatory labeling that I added later. Let’s look at the audio signal flow. The S/PDIF signal, coming from the TV’s Optical Digital Output, is fed into the Teensy 4.1’s SPDIF input pin. That signal is handled by the spdif_async1 library function. As mentioned earlier, of the two SPDIF input libraries, only the spdif_async library is compatible with the other audio library functions, due to its re-sampling capability. The stereo data stream output from the spdif_async1 function (the two output rectangles) is split into two paths:

FIGURE 4 – The signal path and audio blocks used in this project, with the blocks labeled with their function
  • 1) The left/right data streams are sent to amp1/amp2, which act as the user volume control. Next, they go to fir1/fir2, which are each configured as a 6-band EQ. Finally, they are sent to a Princeton Technology’s PT8211 Stereo DAC, which converts them to a stereo analog signal.
  • 2) The L/R data streams are combined in mixer1, the gain of which is also set by the user’s volume control. Next it goes to a four-section Bi-quad low-pass filter, set to only pass frequencies below 150Hz. Finally, it is sent to another PT8211 DAC (connected to the Teensy’s secondary I2S port), which produces the analog signal for the subwoofer.

Earlier, I mentioned that when you export your workspace into your Arduino sketch, all the necessary code will be generated and added to your sketch to configure the audio chain properly. Most of the time this is true, and no “tweaking” of this block of initialization code is needed. But that would be too easy, right?

Whenever you use the spdif_async input block, it needs to have some clock signal available. The clock used by the rest of the audio library routines is not suitable. They mention in the info section of the Audio System Design Tool that some other input or output block must be added to the workspace to provide this clock. However, I found it to be more complicated than that. I needed the PT8211 output function for the project, so I figured that it would be satisfactory.


Advertise Here

This didn’t work, however. My program died as soon as the audio routines had been initialized and started executing. After checking the Teensy forum, and trying different things, I found that the spdif3 routine (S/PDIF output) had to be added to the list of routines, even though I was not using S/PDIF output. Also, I found that the order in which it was placed in the list was important—it had to precede the spdif_async routine.

Note that in Figure 4 the spdif3 function does not appear. Since it is only necessary for clocking purpose, and not a part of the overall signal flow, I just added the spdif3 line to my sketch manually. This makes Figure 4 clearer in terms of actual functionality.


Earlier, I mentioned that I had implemented a 7-band graphic EQ. Actually, I configured a 6-band graphic EQ for the TV’s regular stereo channels, and added a separate, mono channel for the sub-woofer. It is low-pass filtered to include only frequencies below 150Hz.

In my previous article, “Fancy Filtering with the Teensy 3.6” (Circuit Cellar 346, May 2019), I explained that I wrote an audio library FIR function that used FFT/iFFT routines to perform filtering and guitar cabinet simulation in the frequency domain. Due to the built-in floating-point math capability of the MCUs used in the Teensy 3.x and 4.x families, I was able to implement floating-point FIR filters with many more taps than the standard FIR filter routine that is included in the Teensy Audio library. I was planning on using my FFT/iFFT FIR routine for the current project.

Although I have tested and used this routine with other routines in the Teensy Audio library, I found that as soon as I tried to use it with the spdif_async routine, my sketch would crash—somewhat like it did before I realized that I had to add the “dummy” spdif3 routine, previously mentioned. I had developed some expertise in writing Teensy Audio library routines back when I wrote the FFT/iFFT FIR routines 18 months ago, but I wasn’t able to determine what conflicts there were between my routine and Alex Walch’s spdif_async routine.

It looked like I would have to use the 16-bit, fixed-point FIR routine that is included in the Teensy Audio library. It was, however, limited in length to only 200 taps. This limitation was made when the library was written and was being used with the much less powerful Teensy 3.x MCUs. The Teensy 4.1 MCU is so much more powerful, and contains so much more SRAM that this 200-tap limit no longer makes sense. I was able to modify the existing FIR routine code to allow for up to 512 taps, which is what I use in this project’s Arduino sketch. The software that I provide on the Circuit Cellar article code and files webpage includes the modified filter_fir.cpp and filter_fir.h files. These must replace the like-named files in the following folder on your PC:


Note that you will have to tailor this path somewhat, if you are using a different version of the Arduino IDE than the version 1.8.9 that I am currently using.

The last issue that I had to address for the 6-band graphic EQ concerned the calculations of the 512 filter coefficients. It is relatively easy to go on the Internet and find online calculators that produce an array of coefficients for a user-entered low-pass, high-pass or band-pass filter specification. It’s harder to find such a calculator for a multi-band graphic EQ. Another problem is that such online calculators don’t lend themselves to a project where you want to be able the adjust the filter gains for each band dynamically, in real time, while you are listening to the amplifier.

When I was developing the FFT/iFFT FIR routines described in my Circuit Cellar article cited earlier, I learned that you could, in theory, take the coefficients generated from a single band-pass filter of frequency x, and add them to the results of a band-pass filter of frequency y. This would provide a filter with the combined characteristics of both band-pass filters. Theoretically, you could also assemble a 6-band EQ filter in this manner. When I tried to do this with my FFT/iFFT FIR filter, it didn’t work well at all, despite the fact that my filter routines were done in floating-point, the accuracy of which would ensure better results than 32-bit fixed-point routines used in the standard Teensy Audio library routines.


I don’t profess to be an expert in DSP filtering methods. What I was missing here was that each of the filter bands had to have its coefficients run through a window function before they were combined. I became aware of this when I found another Teensy Audio library that was written using floating-point math for many different audio routines (but not currently compatible with the standard Teensy Audio library). That library contained a graphic EQ routine using an FIR filter. Although I could not use that library’s FIR real-time filtering function with the other Teensy Audio library routines that I was using (shown in Figure 4), I was able to use the class member of that graphic EQ library that calculated the 512 filter coefficients from the frequency/gain parameters that the user’s program fed to it.

In this project’s sketch, the 6 EQ band frequencies are defined in the following array:

float fBand[] = { 200,500,1250,3000,7000,22058 };

where Band1 covers from DC to 200Hz, Band2 from 200Hz to 500Hz and so on.

The gain (in dB) for each EQ band is defined in the following array:

float dbBand[] = { -10,-10,-10,-10,-10,-10}; // flat response for now

Five of the six values shown above can be varied by adjusting the values of five 10kΩ pots mounted on the front panel of the receiver. I leave the 7,000Hz to 22,058Hz band fixed, because I cannot distinguish much difference in these really high audio frequencies at my age. The sixth pot adjusts the level of the sub-woofer channel.

Instead of having these pots active continuously for on-the-fly adjustment of the EQ, I set things up with a push button, which activates the reading of the six pots, and a Save button, which stores those readings. In this way, I can switch between several different EQ “Profiles,” using the same IR remote that I use to control the other adjustable digital amplifier features.

The AudioFilterEqualizer_F32 audio library that I referred to earlier is available on GitHub [2]. It was written by Chip Audette in conjunction with Bob Larkin. Note that this GitHub site includes examples of the various floating-point audio routines. When I tried the FIR_Equalizer example, it wouldn’t compile. In a PJRC Teensy Forum thread, I described to Chip Audette the small change I had to make, to allow the example to compile [4].

I’ll admit that I was somewhat lazy in respect to how I used this FIR coefficient calculation routine. Basically, I downloaded the whole floating-point audio library into my Arduino library folder. Then I included it into my sketch as follows:

#include "OpenAudio_ArduinoLibrary.h"

I then had to instantiate the AudioFilterEqualizer_F32 class as equalize1, and used the following line to calculate the filter coefficients:

uint16_t eq = equalize1.equalizerNew(nBands, &fBand[0], &dbBand[0], 512, &equalizeCoeffs[0], 65);

This routine generates floating-point coefficients. I had to convert these into 16-bit integers that are expected by the Teensy Audio library FIR routine, since it does all its processing using integers.

I looked at the equalizerNew() routine, itself, to see if I could replicate it as a function contained within my sketch (rather than loading in the whole OpenAudio library). However, it used several underlying dependencies, and I decided it wasn’t worth the trouble of trying to extract that function and integrate it directly into my sketch.


In the previous two sections, I discussed the important details of the audio signal processing chain. The rest of the software was not so involved, but there are a few things I’d like to mention. I wanted a display that would allow for easy setup of the graphic EQ parameters, and also indicate the current volume setting and Bluetooth status.

I wanted a large font that could be read from normal TV viewing distance. I could have used a common alphanumeric LCD display (HD44780-based), but these are generally hard to read at a distance. I settled on an inexpensive 2.8” TFT color touchscreen display that is sold by PJRC, the company that sells the Teensy modules.

These displays feature the common ILI9341 controller chip. Adafruit first provided an Arduino driver for these displays several years ago. However, if you are using a Teensy MCU, an optimized version of this driver that uses the enhanced hardware is contained in the Teensy 32-bit Arm MCU- the ILI9341_t3 library. This library is automatically included when you download the Teensyduino plug-in for the Arduino IDE.

I wanted to control the digital amplifier with an IR remote control. To make it easier for my wife to use, I wanted to use the remote control that came with the PVR (personal video recorder) supplied by our cable provider. That remote can be programmed to activate a home theater receiver when you press the AUX device button. I had previously set up the remote to control a Yamaha home theater receiver.

Over the years I have published a few articles in Circuit Cellar on the subject of “smart” IR remote controllers that I had designed. An example is my article “Build an iPad-Based IR Remote (Part 1)” from May 2016 (Circuit Cellar 310) [3]. They were built around either Microchip (formerly Atmel) AVR or Espressif’s EESP32 MCUs. I had written code to make these devices “learning” remotes—they could sample the IR codes coming from any remote, and replicate them. Being a universal “learning” remote, the code for them was fairly complex, and tailored for either the AVR or ESP32 MCU hardware.

Since I was using a Teensy MCU in this case, I decided to see if I could use the standard Arduino IR library that came with the Teensyduino plug-in. Within the Arduino IDE, once you have loaded the Teensy 4.1 as your target MCU, if you click on the “Examples” tab you can scroll down to the section “Examples for the Teensy 4.1.” Within that section you can find a listing for IRremote. I loaded the IRrecvDemo example into this project’s Teensy 4.1. You must specify the pin to which you have connected the IR receiver module, near the top of the example.

When I ran this example program with my IR remote, it displayed the hex codes associated with the various buttons that I needed to use, as I pressed them. Near the beginning of my sketch I define the hex values of all the required buttons. I call the example’s irrecv.decode function in the loop section of my program, to monitor the IR codes that are arriving.

Note that this demo program works for several different IR remote protocols, but not all of them. However, you can always select different receivers from among those in the list that your remote control instruction sheet says it will support, until you find one that works with the IRrecvDemo example program.

Earlier, I mentioned that I wanted to be able to store several different EQ profiles. In practice I have only set up three profiles:

  • 1) Completely flat frequency response (power-up default)
  • 2) A “speech” profile, where all the bass and very high audio frequencies are filtered out, and the 500Hz to 3,500Hz normal speech ranges are boosted. This shows up as “TV Viewing” on the TFT screen, when selected.
  • 3) A “music” profile. Here I boost the low, subwoofer bass (I grew up in the late ’60s and liked rock music), and tweak the 3kHz to 7kHz range to compensate for my “old-man” ears.

To allow these profiles to survive power-off cycles, I use the Arduino EEPROM class. It’s very easy to use. You can just use the put function to save a profile’s complete EQ array structure in one operation. The get function will return that whole array in one operation. All you need in addition to the structure name is the address in EEPROM where that profile will be located, and the size of the profile’s structure.


I used the latest Teensy MCU module, the Teensy 4.1, for this project (Figure 5). Like the earlier Teensy 4.0 module, the MCU used on this module is NXP Semiconductors’ (formerly Freescale) MIMXRT1062 32-bit Arm MCU. However, the Teensy 4.1 module has a 48-pin header configuration, rather than the 28-pin header configuration found on the Teensy 4.0. This project involved a fair number of peripheral devices, so the greater complement of GPIO lines available on the Teensy 4.1 was needed.

FIGURE 5 – The Teensy 4.1 module used in this project. I chose it over the smaller Teensy 4.0 mainly because of its greater number of GPIO lines, but did not need the on-board microSD card socket.

The MIMXRT1062 MCU has plenty of resources for this project. The chip is clocked at 600MHz, making it the fastest Arm Cortex-M7 family MCU currently available. It contains 1,024KB of internal RAM and 2,048KB of flash memory, of which 64KB is reserved for EEPROM simulation (including wear-leveling) and bootstrap recovery space. Crucial for this project is the inclusion of a full hardware-based S/PDIF Digital Audio port, and two I2S ports—one for each of the two PT8211 stereo DACs used in the project. I used six EQ pots in the project, and the MIMXRT1062 MCU contains 14 separate ADC pins, so I didn’t have to use an additional analog multiplexer chip.

The Teensy 4.1 module comes with a built-in SD card socket, wired to the MCU’s dedicated high-speed SDIO port; however, this feature wasn’t needed in this project. The Teensy 4.1 is programmed via its built-in USB port using the HID profile. The MIMXRT1062 MCU’s USB port can also be programmed to support Serial, MIDI, Audio, Keyboard and Mouse profiles, in many combinations. These are all supported by the Teensyduino plug-in. I used the USB Serial profile, which I use only for debugging using Serial.println() during development.

FIGURE 6 – Schematic diagram of the project. Not shown here is the Class-D power amplifier/Bluetooth receiver board. (CLICK TO ENLARGE)

Please refer to Figure 6 for the schematic diagram of the unit. The 2.8” PJRC TFT display is connected to the Teensy 4.1’s Primary SPI port, so only the *CS and D/*C lines on GPIO 10,9 had to be specified when instantiating the ILI9341_t3 class:

// instantiate the TFT class
#define TFT_DC 9
#define TFT_CS 10
// Use hardware SPI and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

The optical S/PDIF signal from the TV is fed to an Everlight Electronics PLR135/T TOSLINK receiver module, which feeds the SPDIF input pin on the MIMXRT1062 MCU directly.


For the remote-control receiver, I used a Vishay TSOP38238. Despite its name, it is not in a TSOP package, but rather, a through-hole SIP package. It is available from Digi-Key. This module is designed for IR signals using a 38kHz carrier frequency, which is the most common IR remote carrier frequency in use. From past experience with the remote control from a satellite receiver/PVR, I know that occasionally you may run into 56kHz remotes, and they would not work with this particular receiver. A TSOP4856 would work in this case.

I used a rotary encoder for the front-panel volume control. Since volume is generally controlled by the remote-control, I wanted the front panel volume control to act in a relative, rather than an absolute mode. That is, the remote control has volume-up and volume-down buttons, and the encoder also just increments or decrements the current volume value, depending upon which direction you move it. The five pots for setting the EQ gains, as well as one for the sub-woofer gain, are all connected to the 3.3V supply and feed analog inputs A8-A13 on the Teensy 4.1 module.

The whole circuit, except the power amplifier, is powered by 5V, provided by an OKI-78SR-5/1.5 from Murata Power Solutions. This is a tiny switching regulator meant as a drop-in replacement for the common 7805 analog regulator. Because I was powering the regulator from the much-higher voltage needed for the power amplifier module, it was important to use a switcher like this to minimize wasteful power dissipation.

The PT8211 DAC, for the Left, Right channels, is connected to the Primary I2S port. The DAC is powered by the 3.3V power rail, and its output will idle at 1.65V. Therefore, it is coupled by C3, C4 capacitors to convert its output into a ground-referenced AC signal. The PT8211 puts out a 2.0VPP signal, which is more than double the amount of signal needed to drive the AUX input of the power amplifier module I used. Volume control is performed in software by adjusting the gain of amp1amp2 (as shown in Figure 4).

I used a 2:1 resistive voltage divider (R5, R6 and R7, R8) to attenuate the DAC’s output down to the full-scale input range of the power amplifier, rather than depending on the software to attenuate it. The latter would have sacrificed 1-bit of resolution. The datasheet for the PT8211 recommends the use of a filter on its outputs to minimize clock noise, which I’ve implemented in earlier projects. However, I am now aware that the PT8211 software driver in the Teensy Audio library uses 4x oversampling (196kHz), so this filter is not necessary. A second PT8211, fed from the Teensy’s second I2S port, is used in a similar circuit to provide the subwoofer signal.


I’ve been an audiophile and amateur musician since my teenage years, and during that long period of time, I’ve built a lot of home-brew audio amplifiers, including many using vacuum tubes early on. When I was younger, and in a band, the more powerful the amplifier the better. Even when buying stereo receivers for my home, most of the modern ones that are of decent quality sport greater than 100W per channel (peak, not RMS), and they are often four channels. However, modern loudspeakers use much more powerful magnets, made possible by the improved materials available now.

The net result is that I have found, from taking measurements with my digital meter and oscilloscope, that 2W to 4W RMS per channel is all that is really required for normal TV viewing—even in my 160ft2 recreation room. When I’m listening to music, I admit I still like to turn it up louder, so more power is needed in that case.

I like Maxim Integrated’s MAX98357 I2S amplifier IC, because it eliminates the need for a DAC and can put out about 2.5W RMS of power. Early in the design process, I connected two of these (on Adafruit breakout boards) directly to the Teensy 4.1 I2S port. They were just powerful enough for normal TV watching, but not powerful enough when I wanted to crank up the volume for music.

I found that numerous power amplifier modules are available from Amazon, Banggood and other online retailers. Many use STMicroelectronics’ (ST’s) TDA7492P Class-D power amplifier IC that can provide 25W RMS on two channels, using only a single 20V power supply. It uses the Bridge-Tied Load configuration to achieve this. That 25W figure incurs 10% total harmonic distortion (THD), which is too high, but at 10W per channel, this drops to 0.015%, which is acceptable. These modules work perfectly fine (at a slightly lower power output than 25W) when powered by laptop power packs. I have many such laptop power packs sitting idle in my parts bins. I chose an older 15V/4A one for this project, since I didn’t need the extra output power that I could have achieved had I used the more common 19V laptop power packs that I also had on hand.


The amplifier module that I chose came from Amazon and included an on-board Bluetooth receiver, which I found attractive, since I sometimes like to listen to my MP3 music collection on both my iPhone and iPad via Bluetooth. I noticed from user-reviews that certain amplifier modules available merely connected their Auxiliary input signals in parallel with the output of the on-board Bluetooth receiver IC. This is unsatisfactory when listening to the AUX input, because it results in low signal levels and distortion. I chose an amplifier model that disables the Bluetooth IC’s output stage when the AUX cable is plugged in.

Like many unbranded modules available from Amazon and other online retailers, this one did not come with any instructions. At first, I wasn’t sure how the module detected when the 3.5mm Auxiliary cable was plugged in. I discovered that the 3.5mm Auxiliary input socket was a TRRS type. That is, it had contacts for a tip contact, two ring contacts and the sleeve contact. The mating plug is shown in Figure 7. If you plug a common 3.5mm TRS plug into this socket, the plug’s second ring contact is absent, and that area is instead connected to the sleeve connection. Since the sleeve connection is the common or ground connection, the socket’s second ring contact will be grounded as soon as the TRS plug is inserted. This second ring contact in the TRRS socket is connected to the Bluetooth IC and serves to disable the Bluetooth signal output whenever the TRS plug is inserted.

FIGURE 7 – The auxiliary input on the Class-D power amplifier board uses a 3.5mm TRRS socket. This shot of the corresponding TRRS plug shows the tip, two ring and sleeve sections.

Since the amplifier module was mounted inside an enclosure, I wasn’t planning on plugging/unplugging a TRS plug from the Teensy board whenever I wanted to switch between Auxiliary input and Bluetooth. Therefore, I used transistor Q2, driven by Teensy’s GPIO8, to ground the second ring contact of the TRRS socket whenever audio signals from the TV were being processed via the Teensy’s S/PDIF port. Rather than use a 3.5mm TRS plug, I wired the signal and AUX enable wires directly to the AUX input socket, as shown in Figure 8.

FIGURE 8 – The Class-D power amplifier/Bluetooth receiver board. The signal and switching connections are highlighted at the top-right.

Apart from the speakers, the only other connections to the amplifier module are the power connections to my 15V/4A laptop power pack. By using a laptop power-pack, I lost the ability to switch the AC grid power on/off directly. I considered a few options regarding low-power consumption for when the unit was not being used. I placed a mechanical switch on the 15V output of the power-pack, and with that turned off, the 120VAC power consumption was 17mA (2W). If you were to purchase a modern power pack with similar output voltage and current ratings, this power consumption would drop to about 150mW, due to newer, more strict regulations.

If I decide to leave the power switch turned on, the Class-D amplifier takes negligible current with no audio signal present, but the Bluetooth IC always must be powered up to be ready to receive a pairing request. The Teensy 4.1 is also powered up, since it is processing the IR signals from the remote. The power-on/power-off signals from the remote control currently only turn the TFT screen backlight on and off. In this powered-up but idle state, the project consumes about 7W, varying a bit depending on whether the TFT backlight is on or off. Power consumption when the project is producing sound varies with the power supplied to the speaker, but the TDA7492P amplifier is about 90% efficient.

Figure 9 shows the circuitry mounted in a small wooden cabinet I built. The S/PDIF signal goes to the PLR135/T module, which you can see on the left rear of the main circuit board—glued to the board for mechanical strength. The main Left/Right outputs are the two green sockets on the power amplifier board. The subwoofer output is the RCA socket at the far-left rear. Figure 10 shows the front panel. The left-most black knob is the subwoofer level, and the five knobs to the right of it are the EQ level pots. The sixth band (highest) of the EQ is fixed in level. The silver knob is the volume control.

FIGURE 9 – The circuitry mounted in a small wooden case that I made
FIGURE 10 – The front panel of the finished project. The subwoofer level is set by the black knob on the far left. The other five knobs control the adjustable bands of the 6-band EQ, and the sixth band is fixed in level. The power amplifier module can be placed in Bluetooth mode by either the IR remote, or the Bluetooth switch on the front panel.

I was only able to implement this project with the help of the dedicated S/PDIF library tailored to the Teensy Audio library. While I found the interoperability of the many Teensy Audio library objects to be generally excellent, I ran into some issues with the combination of modules that I needed for this project. It took some experimenting to “fine-tune” things until everything played well together.

I’m considering adding a small board containing a low-power AVR MCU. This would sample the IR receiver module’s signal and switch the 15V power to everything else. It would need its own 5V regulator, supplied by the 15V power pack. This would allow for very low idle power consumption. 


[1] Teensy Audio System Design Tool:
[2] GitHub link for floating-point audio routines
[3] “Build an iPad-Based IR Remote (Part 1)” from May 2016 (Circuit Cellar 310)
[4] Link to a PJRC Teensy Forum thread about small problem in FIR equalizer example:

Teensy 4.1 Module:

TFT Touchscreen display:

Audio Power Amplifier with Bluetooth receiver:

PT8211 16-bit DAC (USA source):

PLR135/T TOSLINK optical receiver:

TSOP38238 IR receiver module:

Adafruit |
Digi-Key |
Everlight Electronics |
Maxim Integrated |
Murata Power Solutions |
NXP Semiconductors |
Renesas Electronics |
STMicroelectronics |
Vishay Semiconductors |


Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.

Note: We’ve made the May 2020 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

+ posts

Brian Millier runs Computer Interface Consultants. He was an instrumentation engineer in the Department of Chemistry at Dalhousie University (Halifax, NS, Canada) for 29 years.

Sponsor this Article

Supporting Companies

Upcoming Events

Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2021 KCK Media Corp.

A Digital Amplifier for TVs

by Brian Millier time to read: 30 min