Teensy 4 multichannel audio with Adafruit PCM5100 I2S DAC multichannel output

Hi everyone,

First post in here, glad to join the community.

I am currently trying a multichannel audio processing algorithm (2 channels in, N channels out) demonstrator on an embedded device.
I started to build it using an Arduino GIGA R1 Wifi, but I'm struggling to get a properly synced multichannel audio output signal using their Advanced_Analog library to drive the 3 SPI/I2S of the STM32H747xI chip.

I'm using the following audio codecs for audio input/ouput:
- One ADC -> I2S for stereo input: https://www.audiophonics.fr/fr/appa...umerique-wm8782-i2s-24bit-192khz-p-13351.html
- Three I2S -> DAC for 6ch audio output: https://www.adafruit.com/product/6251

I could not find a way so far on my Arduino code to synchronize properly the 3 I2S outputs, and did not find any library to use the SAIs, so I should write my own but it seems like a rather long path to start coding it with STM32 HAL library.

I looked a bit at the Teensy 4 board that seems to be promising to make this work because of a greater software support from the community and also to get a greater output channel count (up to 16 channels using TDM).

Did anyone experienced this kind of use cases with Teensy 4 PCB?
Any recommandations to get started using my existing adafruit I2S DACs in a first step to get at least 6 channels synced outputs?

Cheers!
 
Yes, this is easy with Teensy and the audio library. If you're not familiar with the library, this 31 page tutorial is the place to start. No need to read (or actually do) the stuff from all 31 pages. Maybe just look at the first part, then skip to the first part of section 2 to learn how to use the design tool. Once you have a general idea of how it works and how to use the design tool, skip all the other parts you're not interested in using. There are a *lot* of features.... and the library has many more added in the design tool since that tutorial was written.

To get 6 audio output from to 3 chips, put the "i2s_hex" output onto the design tool's canvas. While it's selected, the documentation will appear on the right side panel. Scroll down to "Hardware" for the pins to use. You will wire Teensy's BLCK, LRCLK and MCLK signals to all 3... so 3 wires connecting to each pin on Teensy. Then each of the 3 data pins will wire to just 1 of those DAC chips. All will be in sync, because they're all sharing the same clocks.

For 2 channel input, put the normal "i2s" input on the canvas. It also uses the same 3 clock pins, so you'll have 4 wires connected to each of those pins. Again, follow the Hardware docs to connect all your ADCs and DACs to the correct pins.

If you want even more, Teensy's main I2S port has 5 data pins total. This setup will use 4 of those 5, so you can add only 1 more stereo chip (either input or output). The design tool knows about potential hardware conflicts. To see this, just drag both "i2s_hex" input and output. Each would need 3 data pins, but Teensy has only 5 total. You'll see little yellow warning icons appear on the canvas for all the features you've tried to use which would conflict.

Teensy does have a 2nd I2S port, but unlike the 5 data pins on the 1st port, this 2nd port has only 2 for 1 stereo input and 1 stereo output. So you can get 4 more channels this way. They won't share the same clock pins, but they will use the same frequency so they will be nearly in sync, off from the other I2S port by at most 1 sample.

For more than 14 channels, you would need to use TDM. It can do much more, but support for TDM is not as common as I2S so you need to be careful which ADC and DAC chips to choose.
 
One possible hardware issue you might face is electrical noise from Teensy driving 4 wires with a fast clock signal, times 3 such signals. Best to keep the wires as reasonably short as you can. Routing a ground wire as close as possible to the clock also helps.

This is a general electronics issue with all high speed clocks, but it can be moreso with Teensy than on some slower chips. Teensy is a very fast chip with high bandwidth capability on its pins.

In an extreme case, you might need to add series resistors. For example, perhaps 4 resistors each 100 ohms connected to Teensy's pin (or close to the pins) and then each clock wire from the resistor to the ADC or DAC that receives it.
 
Thank you so much @PaulStoffregen for all those detailed advices.
I will definitely have a look to all the resources you mentioned and see if I can figure out how to make this work.

For TDM, I found this Analog Devices chip that could be perfect for multichannel output up to 16 channels:

Then I could use my ADC/I2S codec for stereo input and this ADAU1966 for TDM output.

Thanks for the link @h4yn0nnym0u5e as well.
 
I did this principle scheme to illustrate what I would like to do.

2Ch_in_6Ch_out.png


Would this kind of wiring work by using:
- "i2s" block as for stereo input
- "i2s_hex" block for 6 channel output

and insert my DSP code in the middle?
 
Ok thanks, this updated version seems about right I think then:

2Ch_in_6Ch_out_Teensy.png


The DC power of my ADC and DAC PCBs needs explicitely to be 5V, can I use USB-C to provide power to the Teensy board and get the 5V DC power from one of the Teensy pin?

What is not clear to me is how can I get the audio buffers from RX FIFO and how I feed the TX buffers, inserting my DSP code in between.
I'm using Arduino GIGA R1 Wifi with PlatformIO to generate/flash the firmware, is this something I can do by using the design tool and then edit the code to integrate my source code in a main.cpp?

Thanks again for all the support here.
 
What is not clear to me is how can I get the audio buffers from RX FIFO and how I feed the TX buffers, inserting my DSP code in between.

You have 2 options.

To process the data in your program, similar to how Arduino's library works, use the "queue" objects. Then write your program to repeatedly check whether the queue(s) delivering data have more for you to process, and/or whether those waiting for you to give them data can accept more. Like everything in the library, you'll find its documentation in the design tool right side panel.

The other way is you create your own class within the audio library. Your code must have an update() function which the library will call when it's time process another block. Your custom class doesn't need to actually be in the library folder. Just inheriting from AudioStream() and using the AudioConnection() to route it to the rest of the audio system is enough. The code can live in your program, or in its own library. Use of AudioStream() from the core library is the magic that makes things work with the audio library, even if not actually within the actual library.

Might also be worth considering both of these ways can be used together with any combination of the audio library's existing features (of course within memory and CPU limits). So if part of what you need to do is filter incoming data before your special DSP or maybe apply delays or other effects to the 6 outputs, you can lessen the amount of unique code you have to write by connecting your DSP part into a system that uses the library's existing features to do part of the work.
 
Last edited:
Thank you very much Paul.

I already tried to explore the "Create your own class" method, but it fails at compile stage using:
- Arduino IDE 2.3.6
- Teensy 1.59.0 in Boards Manager
- MacOS Sonoma 14.2.1 / MacBookPro M2 Pro

I get standard c++ types definition errors as well as missing symbols in AudioStream.h header file:
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:73:9: error: 'uint8_t' does not name a type
73 | uint8_t ref_count;
| ^~~~~~~

...


/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:144:25: error: 'cpu_cycles' was not declared in this scope
144 | cpu_cycles = 0;
| ^~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:145:25: error: 'cpu_cycles_max' was not declared in this scope
145 | cpu_cycles_max = 0;
| ^~~~~~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:146:25: error: 'numConnections' was not declared in this scope; did you mean 'AudioConnection'?
146 | numConnections = 0;
| ^~~~~~~~~~~~~~
| AudioConnection

...


/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:115:142: error: 'F_CPU_ACTUAL' was not declared in this scope
115 | #define CYCLE_COUNTER_APPROX_PERCENT(n) (((float)((uint32_t)(n) * 6400u) * (float)(AUDIO_SAMPLE_RATE_EXACT / AUDIO_BLOCK_SAMPLES)) / (float)(F_CPU_ACTUAL))
| ^~~~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:150:48: note: in expansion of macro 'CYCLE_COUNTER_APPROX_PERCENT'
150 | float processorUsageMax(void) { return CYCLE_COUNTER_APPROX_PERCENT(cpu_cycles_max); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h: In member function 'void AudioStream::processorUsageMaxReset()':
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:151:45: error: 'cpu_cycles_max' was not declared in this scope
151 | void processorUsageMaxReset(void) { cpu_cycles_max = cpu_cycles; }
| ^~~~~~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:151:62: error: 'cpu_cycles' was not declared in this scope
151 | void processorUsageMaxReset(void) { cpu_cycles_max = cpu_cycles; }
| ^~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h: In static member function 'static void AudioStream::update_all()':
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:169:57: error: 'IRQ_SOFTWARE' was not declared in this scope
169 | static void update_all(void) { NVIC_SET_PENDING(IRQ_SOFTWARE); }
| ^~~~~~~~~~~~
/Users/baptistevericel/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/AudioStream.h:169:40: error: 'NVIC_SET_PENDING' was not declared in this scope
169 | static void update_all(void) { NVIC_SET_PENDING(IRQ_SOFTWARE); }
| ^~~~~~~~~~~~~~~~

I got the same error using PlatformIO but I wanted to replicate this with Arduino IDE since I saw you do not support fully PlatformIO software issues.

Have a good week
 
You might need "#include <stdint.h>" for int8_t etc. Library files are compiled as is in C++ I think, so you have to provide all the #includes yourself.

Look at other Audio classes to see the typical setup...
 
Back
Top