Teensy 4.0 Bluetooth A2DP Wireless Audio Stream to I2S

Status
Not open for further replies.

JayShoe

Well-known member
Hello,

My project requires a Bluetooth A2DP Sink that is the fed to the Teensy 4 via I2S. I'm leaning towards an ESP32 chip connected to the Teensy for both control over its Bluetooth pairing functions, as well as input of it's bluetooth audio.

- There is sample code in the ESP32 API.
- There is sample code in the ESP32 Mp3 Decoder which also shows some hardware pin-outs for connecting the ESP32 to I2S.

Are there any projects that are doing this that I should look at? Since the new Teensy 4 has some additional I2S data lines, I'm looking to add Bluetooth Audio to my project and send the data to the Teensy via I2S.

Jay
 
Hello,

Over a year later and I'm still at it! The ESP32 implementation works ok, but I didn't like the workflow. I searched on a way to build the project with Arduino IDE instead, and found that a user pschatzmann on github made this ESP32-A2DP project, and it compiles fine!

OK, so fire up a sketch and hook up I2S to my project like this.

esp32_a2dp_test2.jpg

As posted on the github project page, and similarly to the ESP32 I2S library, you can define the I2S stream. Here is my code:

Code:
  /*
  Streaming Music from Bluetooth Phil Schatzmann
  
  Copyright (C) 2020 Phil Schatzmann
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//default I2S pins: – bck_io_num = 26, – ws_io_num = 25, – data_out_num = 22,


#include "BluetoothA2DPSink.h"

BluetoothA2DPSink a2dp_sink;

void setup() {
  
static const i2s_config_t i2s_config = {
  //
.mode = (i2s_mode_t) (I2S_MODE_SLAVE | I2S_MODE_TX ),
.sample_rate = 44100, // corrected by info from bluetooth
.bits_per_sample = (i2s_bits_per_sample_t) 32, /* the DAC module will only take the 8bits from MSB */

// I2S_CHANNEL_FMT_RIGHT_LEFT, I2S_CHANNEL_FMT_ALL_RIGHT, I2S_CHANNEL_FMT_ALL_LEFT, I2S_CHANNEL_FMT_ONLY_RIGHT, I2S_CHANNEL_FMT_ONLY_LEFT
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 

.communication_format = (i2s_comm_format_t) (0X01), //I2S_COMM_FORMAT_STAND_I2S 
//.communication_format = (i2s_comm_format_t) (0X03), //I2S_COMM_FORMAT_STAND_MSB 
//.communication_format = (i2s_comm_format_t) (0x02), //I2S_COMM_FORMAT_I2S |I2S_COMM_FORMAT_I2S_LSB 
//.communication_format = (i2s_comm_format_t) (0x04), //I2S_COMM_FORMAT_PCM

.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 16,
.dma_buf_len = 64,
.use_apll = false
};

static const i2s_pin_config_t pin_config = {
    .bck_io_num = 26,
    .ws_io_num = 25,
    .data_out_num = 21,
    .data_in_num = I2S_PIN_NO_CHANGE
};

//i2s_set_pin(i2s_num, pin_config);
a2dp_sink.set_i2s_config(i2s_config);
a2dp_sink.start("MyMusic2");

}


void loop() {
}

For the Teensy config, I've tried it with an I2S/I2S2 configuration as well as a TDM/I2S2 configuration and I get the same results. I get sound from the bluetooth sink, but it sounds high pitched (chipmunk). When I set the bitrate to 16 bit audio I get loud noise, when I set it to 32 bit audio it's high pitched but it is audible.

If you have any interest in getting this working it's a pretty simple setup. Run a passthrough USB sketch on teensy, confirm sound, then add your I2S2 object (and a pair of mixers) to the project and hook up the 3 I2S lines (.bck_io_num = 26, .ws_io_num = 25 (lrclk), .data_out_num = 21). Here is some sample code for the Teensy...


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


// GUItool: begin automatically generated code
AudioInputI2S2           i2s2_1;         //xy=413.88890075683594,363.88888931274414
AudioInputUSB            usb1;           //xy=420.8888854980469,282.99999886751175
AudioMixer4              mixer1;         //xy=623.8888854980469,296.8888854980469
AudioMixer4              mixer2;         //xy=627.8888320922852,383.8888854980469
AudioOutputI2S           i2s1;           //xy=797.8889083862305,277.99999237060547
AudioConnection          patchCord1(i2s2_1, 0, mixer2, 0);
AudioConnection          patchCord2(i2s2_1, 1, mixer1, 1);
AudioConnection          patchCord3(usb1, 0, mixer1, 0);
AudioConnection          patchCord4(usb1, 1, mixer2, 1);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=565.8888702392578,518.0000267028809
// GUItool: end automatically generated code


void setup() {                
  AudioMemory(12);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.6);
}

void loop() {
  // read the PC's volume setting
  float vol = usb1.volume();

  // scale to a nice range (not too loud)
  // and adjust the audio shield output volume
  if (vol > 0) {
    // scale 0 = 1.0 range to:
    //  0.3 = almost silent
    //  0.8 = really loud
    vol = 0.3 + vol * 0.5;
  }

  // use the scaled volume setting.  Delete this for fixed volume.
  sgtl5000_1.volume(vol);

  delay(100);
}

// Teensyduino 1.35 & earlier had a problem with USB audio on Macintosh
// computers.  For more info and a workaround:
// https://forum.pjrc.com/threads/34855-Distorted-audio-when-using-USB-input-on-Teensy-3-1?p=110392&viewfull=1#post110392


Does anyone have any advice on how to further debug this? From experience debugging my other modules I'm certain it is a data format/timing issue...


Thanks for your input!
Jay
 
Hi,

I also use an ESP32 as A2DP sink and I also connected it to a Teensy 4.0 via I2S. My setup is working without any problems. So we could search for differences in order to find out what's going on. The only problem is that I am not using the original teensy audio library. But you could compare the I2S- configurations of my Teensy and my ESP32. At my setup the ESP32 is the I2S master and the Teensy the slave. But I would ignore this difference for now.
The code, that runs on my ESP32 is based on this a2dp sink example:
https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/classic_bt/a2dp_sink
The I2S configruation that I use is:
Code:
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,                                  // Only TX
        .sample_rate = 44100,
        .bits_per_sample = 16,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                           //2-channels
        .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
        .dma_buf_count = 6,
        .dma_buf_len = 128, //60
        .use_apll = 1,
        .intr_alloc_flags = 0,                                                  //Default interrupt priority
        .tx_desc_auto_clear = true                                              //Auto clear tx descriptor on underflow
    };

The I2S configuration at the Teensy is:
Code:
	uint8_t noBitsM1=noBits-1;  //noBits=16 
		
	I2S1_TCSR &=~I2S_TCSR_TE;
	I2S1_RCSR &=~I2S_RCSR_RE;

	CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
	CORE_PIN21_CONFIG = 3;  //1:RX_BCLK
	CORE_PIN20_CONFIG = 3;  //1:RX_SYNC
	IOMUXC_SAI1_RX_BCLK_SELECT_INPUT = 1; // 1=GPIO_AD_B1_11_ALT3, page 868
	IOMUXC_SAI1_RX_SYNC_SELECT_INPUT = 1; // 1=GPIO_AD_B1_10_ALT3, page 872

	// configure transmitter
	I2S1_TMR = 0;
	I2S1_TCR1 = I2S_TCR1_RFW(1);  // watermark at half fifo size
	I2S1_TCR2 = I2S_TCR2_SYNC(1)	//1...transmitter is running synchronously with receiver, 0.. running asynchronously
				| I2S_TCR2_BCP;		//Bit clock polarity: active low Todo: check
	I2S1_TCR3 = 0;	// disable all transmit channels for now, I2S_TCR3_TCE;		//Transmit channel enable
	I2S1_TCR4 = I2S_TCR4_FRSZ(1)	//two words per frame
				| I2S_TCR4_SYWD(noBitsM1)	//sync width:noBitsM1 -> (noBitsM1+1)bit = 1 word
				| I2S_TCR4_MF		//most significant bit first
				| I2S_TCR4_FSE		//Frame sync asserts one bit before the first bit of the frame
				| I2S_TCR4_FSP;		//frame sync polarity: frame sync is active low
	I2S1_TCR5 = I2S_TCR5_WNW(noBitsM1)	// word N width
				| I2S_TCR5_W0W(noBitsM1)	// word 0 width
				| I2S_TCR5_FBT(31);	//first bit shifted

	// configure receiver
	I2S1_RMR = 0;
	I2S1_RCR1 = I2S_RCR1_RFW(1);
	I2S1_RCR2 = I2S_RCR2_SYNC(0)	//1...receiver is running synchronously with transmitter, 0.. running asynchronously
				| I2S_TCR2_BCP;	//Bit Clock is active low with drive outputs on falling edge and sample inputs on rising edge
	uint32_t enableChannel=I2S_RCR3_RCE;
	I2S1_RCR3=0;
	for (uint8_t i =0; i < MAX_NO_I2S1CHANNELS/2; i++){
		I2S1_RCR3 |= enableChannel;
		enableChannel=enableChannel<<1;
	}
	I2S1_RCR4 = I2S_RCR4_FRSZ(1)
				| I2S_RCR4_SYWD(noBitsM1)
				| I2S_RCR4_MF
				| I2S_RCR4_FSE
				| I2S_RCR4_FSP;
	I2S1_RCR5 = I2S_RCR5_WNW(noBitsM1)
				| I2S_RCR5_W0W(noBitsM1)
				| I2S_RCR5_FBT(31);
I hope that helps at debugging.

Alex
 
Hi Alex,

Thanks, that definitely helps. Does anyone know if I2S2 (in2, out2) can run in slave mode while the I2S1 (in1, out1a, ...) runs in master mode? The audio design tool only has an object for I2Sslave_1. I suspect that there is some reason why running this setup with the Teensy as the Master is causing it not to jive.

Jay
 
You wrote that configured the ESP32 to output 16bit data. Have you also changed the I2S input of the Teensy to 16bit? I also tried to send 32bit samples from the ESP32 to the Teensy, but there was a problem at the ESP32 (can't remember, it's already some time ago). In the end I gave up and sticked to 16bit data and changed the I2S configuration of the Teensy.
 
That's right. The teensy audio library runs at 16bit/44.1kHz. But the I2S inputs are configured to receive 32bit data. You would need to write your own AudioInputI2S2 to change that. Basically you can copy the original AudioInputI2S2 + add 'AudioOutputI2S2::config_i2s(void)' from AudioOutputI2S2 to your class. In config_i2s you would need to replace 32 with 16 at I2S_RCR4_SYWD((32-1)), I2S_RCR5_WNW((32-1)) and I2S_RCR5_W0W((32-1)) (not at I2S_RCR5_FBT((32-1)). Then it might work. This configuration at least works at my setup.
 
Status
Not open for further replies.
Back
Top