Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 10 of 10

Thread: Teensy to stream CD quality audio USB - I2S and communicate on the I2C bus.

  1. #1
    Junior Member
    Join Date
    Dec 2020
    Posts
    10

    Teensy to stream CD quality audio USB - I2S and communicate on the I2C bus.

    Hi,

    I am very new to the microcontroller world, please go easy on me. I am hacking a car stereo to receive digital audio from an android device. I also want to communicate with the stereo's I2C interface so that I can turn on and off the I2S streaming.

    I made a proof-of-concept using a bus pirate and an ESP32 in this video:
    https://youtu.be/bnLY-UuwQsA

    I need a device that can send cd-quality audio 16bit/44.1Khz or 24bit/44.1khz audio over the I2S bus while simultaneously sniffing the I2C bus and sending a signal once a specific signal is received.

    If you saw the video - the goal is to have the I2S device begin streaming when I select AUX, and have the streaming pause when I click anything but aux. Also, I want to be able to stream the audio from an android device, so the USB audio driver would have to work with that.

    Hopefully this is a simple question
    Would the Teensy 4.0 work best for this application?
    Is the best place to start reading here?

  2. #2
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    depends...
    Quote Originally Posted by starfox5194 View Post
    Hi,
    I need a device that can send cd-quality audio 16bit/44.1Khz or 24bit/44.1khz audio over the I2S bus while simultaneously sniffing the I2C bus and sending a signal once a specific signal is received.
    I'd say this is doable.
    BUT: I2S is not a "bus". It is a master/client connection. One Master. One Client. How exactly do you plan to connect this to the radio?
    https://en.wikipedia.org/wiki/I%C2%B2S

    the goal is to have the I2S device begin streaming when I select AUX,
    But AUX is line-in?

    and have the streaming pause when I click anything but aux.
    Also, I want to be able to stream the audio from an android device,
    So the Teensy would be not just a simple audio-device, it has to control the phone, too?
    This is the point where it might get complicated. Or maybe not. I can't answer this - not sure if there is a way to control the media-player on your phone via USB.
    Maybe with a keyboard-emulation. I'd test this first, by connecting a Multimedia-Keyboard to the phone.
    Then, a simple USB Audio, to check if the phone can use it.

    so the USB audio driver would have to work with that.
    Again, not sure if Android has drivers for USB-Audio. If it has, the chances are good (for Audio).

    Would the Teensy 4.0 work best for this application?
    Any Teensy 3.x or 4.x
    I'd recommend a 4.x, yes.

    Is the best place to start reading here?
    yes.

    Edit: But probably easier would be to use AUX as it is.. as "line in" and by just connecting your phone there, without any microcontroller

  3. #3
    Junior Member
    Join Date
    Dec 2020
    Posts
    10
    Thank you for the detailed response, Frank.

    To get the best audio quality possible, I would like to keep the audio in a digital signal all the way from the source (android device) to my radio (outputs via a fiber optic signal to the amplifier).

    I took a look through the Teensy Audio Library, and it looks like the USB audio streaming is supported now, great!

    I2S is not a "bus". It is a master/client connection. One Master. One Client. How exactly do you plan to connect this to the radio?
    I think this is ok, I will be using the I2C bus to control wether or not the CD drive of the radio will be the Master or the Teensy will be the master. The I2S connection is made at the signal processor chip on the head unit.
    Signal processor Datasheet

    So the Teensy would be not just a simple audio-device, it has to control the phone, too?
    The teensy would control the pins active on the signal processor chip on the stereo that are receiving the I2S Signal, not the phone. This would be done by interfacing with the stereo via I2C (See my video where I show me sending the I2C CD source selection signal, even though AUX is currently selected).

    Although it would be cool if I could send a "pause" signal once the source is switched away on the head unit. I think this might be doable by sending a "disconnect" signal via the USB audio protocol?

    not sure if Android has drivers for USB-Audio
    This is my current biggest concern. My android does work with a USB audio DAC, so I think it should also work with the teensy, but I really don't know much about USB audio. I assume there is some kind of standard that most devices adhere to.

    But probably easier would be to use AUX as it is.. as "line in" and by just connecting your phone there, without any microcontroller
    Actually, the best thing to do would be to get an aftermarket head unit with a proper digital audio input, but I am looking to keep the vehicle's appearance as factory as possible.

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    26,572
    Here's a few technical concerns. I'll admit, I only look at this very quickly...

    Figure 1 on page 6 of the datasheet seems to show IIS_CLK and IIS_WS as outputs. So maybe it's using I2S master mode? (Master mode means which audio device controls the sample rate... not which one transmits or receives) If it's an I2S master device, you'd need to run Teensy's I2S in slave mode.

    I2S slave mode isn't very compatible with other audio I/O, but it can probably work with USB audio, if the sample rate is close enough. The USB audio uses asynchronous rate feedback to tell the USB host (your Android phone) the actual sample rate it needs.

    Whether Android supports USB audio at all, and if it does whether it works (well) with Teensy's implementation, and if both of those are true whether the actual sample rate is close enough for the asynchronous rate feedback to tell Android the required streaming speed... it's all a lot of unknowns.


    The other possible issue is the I2C control. On Teensy 4.x, we do not yet support I2C slave mode, so using 4.0 it would only be able to work if whatever you're using is an I2C device that expects to have communication initiated from an I2C controller. If it is acting as a controller and expects Teensy to be in I2C slave mode, then you would need to use Teensy 3.x which does support I2C slave mode.


    There maybe other gotchas waiting. These are just the thing I could see by a very quick look. Hopefully this at least helps you to dig into the details a bit more before deciding which way to do all this stuff.

  5. #5
    Junior Member
    Join Date
    Dec 2020
    Posts
    10
    Thank you Paul for taking a look through the datasheet and sharing your knowledge of Teensy interface support.

    So maybe it's using I2S master mode?
    I am actually interested in using the CD I2S input interface pins (29, 27, and 28 in the diagram), not the other I2S inputs (pin 31/32), therefore, the SAA7706H chip will act as an I2S slave to the teensy.

    Quote from datasheet:
    8.11 CL_GEN, audio clock recovery block
    When an external I2S-bus or SPDIF source is connected,
    the FSDAC circuitry needs an 256fs related clock. This
    clock is recovered from either the incoming WS of the
    digital serial input or the WS derived from the
    SPDIF1/SPDIF2 input. There is also a possibility to
    provide the chip with an external clock, in that case it must
    be a 256fs clock with a fixed phase relation to the source.
    I hope the teensy can generate a 256fs clock signal.

    If it is acting as a controller and expects Teensy to be in I2C slave mode, then you would need to use Teensy 3.x which does support I2C slave mode.
    I would want the teensy to act as both a slave and a master in this application, so I think the teensy 3.x is the chip I should go for.

    I ordered a 3.2 on amazon. I will be reading through your audio library documentation and working more on this as I go.

    Thanks again!
    Alex

  6. #6
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    If I remember correctly, it uses 64fs. But I think you can edit the audio-library..

  7. #7
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    26,572
    Quote Originally Posted by starfox5194 View Post
    I hope the teensy can generate a 256fs clock signal.
    Yes, that part is easy. MCLK is always LRCLK * 256.

    BCLK is LRCLK * 64 is almost all the output cases. The one exception is quad channel I2S on Teensy 3.x. It's still LRCLK * 32... and on my very low priority list to someday update to the same LRCLK * 64 rate we use with ordinary stereo and all the multiple I2S options on Teensy 4.x.

  8. #8
    Junior Member
    Join Date
    Dec 2020
    Posts
    10
    Hello,

    So I finally got my hands on a teensy 3.2. I am having some difficulty with the I2S implementation.

    After using a logic analyzer, I found that the radio has a LRCLK and a BCLK that are always active where I am trying to send an audio signal. I can turn off the LRCLK by writing to the I2C bus, but I haven't figured out how to turn off the BCLK. This was determined using a cheap 24mhz 8 channel logic analyzer.

    So, I put my prototype ESP32 board into I2S Slave mode by modifying one of the examples to send sine waves in slave mode. Code for ESP32 looks like this:

    Code:
    /* I2S Example
    
        This example code will output 100Hz sine wave and triangle wave to 2-channel of I2S driver
        Every 5 seconds, it will change bits_per_sample [16, 24, 32] for i2s data
    
        This example code is in the Public Domain (or CC0 licensed, at your option.)
    
        Unless required by applicable law or agreed to in writing, this
        software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
        CONDITIONS OF ANY KIND, either express or implied.
    */
    #include <stdio.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "driver/i2s.h"
    #include "esp_system.h"
    #include <math.h>
    
    
    #define SAMPLE_RATE     (36000)
    #define I2S_NUM         (0)
    #define WAVE_FREQ_HZ    (100)
    #define PI              (3.14159265)
    #define I2S_BCK_IO      (GPIO_NUM_26)
    #define I2S_WS_IO       (GPIO_NUM_25)
    #define I2S_DO_IO       (GPIO_NUM_22)
    #define I2S_DI_IO       (-1)
    
    #define SAMPLE_PER_CYCLE (SAMPLE_RATE/WAVE_FREQ_HZ)
    
    static void setup_triangle_sine_waves(int bits)
    {
        int *samples_data = malloc(((bits+8)/16)*SAMPLE_PER_CYCLE*4);
        unsigned int i, sample_val;
        double sin_float, triangle_float, triangle_step = (double) pow(2, bits) / SAMPLE_PER_CYCLE;
        size_t i2s_bytes_write = 0;
    
        printf("\r\nTest bits=%d free mem=%d, written data=%d\n", bits, esp_get_free_heap_size(), ((bits+8)/16)*SAMPLE_PER_CYCLE*4);
    
        triangle_float = -(pow(2, bits)/2 - 1);
    
        for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
            sin_float = sin(i * PI / 180.0);
            if(sin_float >= 0)
                triangle_float += triangle_step;
            else
                triangle_float -= triangle_step;
    
            sin_float *= (pow(2, bits)/2 - 1);
    
            if (bits == 16) {
                sample_val = 0;
                sample_val += (short)triangle_float;
                sample_val = sample_val << 16;
                sample_val += (short) sin_float;
                samples_data[i] = sample_val;
            } else if (bits == 24) { //1-bytes unused
                samples_data[i*2] = ((int) triangle_float) << 8;
                samples_data[i*2 + 1] = ((int) sin_float) << 8;
            } else {
                samples_data[i*2] = ((int) triangle_float);
                samples_data[i*2 + 1] = ((int) sin_float);
            }
    
        }
    
        i2s_set_clk(I2S_NUM, SAMPLE_RATE, bits, 2);
        //Using push
        // for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
        //     if (bits == 16)
        //         i2s_push_sample(0, &samples_data[i], 100);
        //     else
        //         i2s_push_sample(0, &samples_data[i*2], 100);
        // }
        // or write
        i2s_write(I2S_NUM, samples_data, ((bits+8)/16)*SAMPLE_PER_CYCLE*4, &i2s_bytes_write, 100);
    
        free(samples_data);
    }
    void app_main(void)
    {
        //for 36Khz sample rates, we create 100Hz sine wave, every cycle need 36000/100 = 360 samples (4-bytes or 8-bytes each sample)
        //depend on bits_per_sample
        //using 6 buffers, we need 60-samples per buffer
        //if 2-channels, 16-bit each channel, total buffer is 360*4 = 1440 bytes
        //if 2-channels, 24/32-bit each channel, total buffer is 360*8 = 2880 bytes
        i2s_config_t i2s_config = {
            .mode = I2S_MODE_SLAVE | I2S_MODE_TX,                                  // Only TX
            .sample_rate = SAMPLE_RATE,
            .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 = 60,
            .use_apll = true,
            .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1                                //Interrupt level 1
        };
        i2s_pin_config_t pin_config = {
            .bck_io_num = I2S_BCK_IO,
            .ws_io_num = I2S_WS_IO,
            .data_out_num = I2S_DO_IO,
            .data_in_num = I2S_DI_IO                                               //Not used
        };
        i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
        i2s_set_pin(I2S_NUM, &pin_config);
    
        int test_bits = 16;
        while (1) {
            setup_triangle_sine_waves(test_bits);
            vTaskDelay(5000/portTICK_RATE_MS);
            test_bits += 8;
            if(test_bits > 32)
                test_bits = 16;
    
        }
    
    }
    This code generates sine waves and they sound very good in slave mode. In master mode, it sounds just like a bunch of static.

    When I try to put the teensy 3.2 in I2S Slave mode, and send audio from my PC via USB, it sounds like there is a lot of static - just like when the ESP32 is in I2S master mode. I verified with the logic analyzer that the BCLK and LRCLK are not generating a signal on the teensy. I am not sure if this is an issue with the configuration of the I2S data on the teensy or an issue with the driver, or if there is more testing that needs to be done on my end.

    This is the code I used to send audio over usb on the teensy 3.2 in I2S Slave mode.

    Code:
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    
    AudioInputUSB            usb1;           //xy=200,69  (must set Tools > USB Type to Audio)
    AudioOutputI2Sslave      i2sslave1;      //xy=443,232
    AudioConnection          patchCord1(usb1, 0, i2sslave1, 0);
    AudioConnection          patchCord2(usb1, 1, i2sslave1, 1);
    
    
    
    
    void setup() {
      AudioMemory(80);
    }
    
    void loop() {
    
    }
    I am currently digging through this and troubleshooting, but if anyone has any suggestions for me I would really appreciate them!

    1) I am going to mess with the frequency, sample rates, etc of the ESP32 to see if it sounds good at all types of different settings. Maybe just this one particular sine wave sounds good.
    2) I am gong to double check how the teensy sends data in I2S slave mode with the logic analyzer. Maybe it is sending data in master mode despite the code specifying slave?
    3) I do not know how to modify the I2S settings for the teensy, I am thinking they are in Audio.h. I am not great at C, so I am learning as I go. My plan is to mess with Audio.h, rename it, and load it up separately to see if it helps. This sounds like a pain though. Is there a way to just re-define the configuration in the main script?

    Thanks for looking this over, and I appreciate your help!

    Alex

  9. #9
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    I see the ESP-Part is easy configuarable. It is much easier to modify the ESP32-Code than to dig into the Teensy I2S code.
    "Slave" is a good choice for the ESP.

  10. #10
    Junior Member
    Join Date
    Dec 2020
    Posts
    10
    Thanks Fank. There are still some issues I need to get rid of on my end. I'm going to keep messing with this I2C bus. Something isn't right, even with the ESP32 in slave mode.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •