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

Thread: SPDIF Input and Output

  1. #1
    Senior Member
    Join Date
    Feb 2017
    Posts
    247

    SPDIF Input and Output

    Hi all. A while back I was working on an audio-reactive LED project. Because of the physical location, the most convenient way to bring the audio to the Teensy was via an optical SPDIF connection. I started working with the TI DIR9001 mostly because it’s available on a preassembled eval board containing all required components for Optical SPDIF --> I2S conversion. You just connect power, the optical input, and the I2S signals. I posted the resulting code in this forum

    The main drawbacks of the DIR9001 are that it’s unidirectional only (it can’t do I2S --> SPDIF) and the eval board is ridiculously expensive.

    Then I ran across the WM8804 from Cirrus Logic. This device solves both of the above problems as it is bidirectional and there is a very reasonably-priced board available.

    So, I created several new classes based on the Quad I2S classes in the Audio Library. The major changes are accommodating a BCLK frequency of 64 * Fs and support for Teensy to operate in I2S Slave mode. The library code and two examples are attached. The connections supporting the examples are:

    Click image for larger version. 

Name:	I2S Connections.jpg 
Views:	326 
Size:	42.9 KB 
ID:	11027

    As when using dual Audio Adaptor Boards, you need to disconnect Teensy Pin 15 from Audio Adaptor Pin 15. Otherwise, the bypass cap on the later kills the high frequency IS0_TXD1 signal. I cut the pin, you can probably also just remove the cap.

    When a SPDIF input is present, the WM8804 must act as I2S master to synch up MCLK, BCLK, and LRCLK with this input. In this case, the I2S interfaces in the Teensy are set for Slave mode and all Audio object timing is frequency synchronous with the WM8804. The 'Quad_I2S_SlaveExample' example blends audio from 2 independent sources -- SPDIF / WM8804 and the SGTL5000's ADC on the Audio Adaptor. It then outputs the resulting signal to 3 destinations -- back to the WM8804 board for I2S --> SPDIF Out conversion, to the DAC on the SGTL5000, and to Teensy's onboard DAC. The pot sets the blending level between the two sources. The connections from the Audio Design tool look like this:

    Click image for larger version. 

Name:	Audio Tool - Teensy Slave.jpg 
Views:	231 
Size:	26.8 KB 
ID:	11028

    When there is no SPDIF input but SPDIF output is desired, then the WM8804 is set for Slave mode and Teensy becomes the I2S master. All Audio object timing is now derived from Teensy's clock. The 'Quad_I2S_MasterExample' accepts I2S audio input from the SGTL5000 and outputs it to the same 3 destinations as above. The connections from the Audio Design tool look like this:

    Click image for larger version. 

Name:	Audio Tool - Teensy Master.jpg 
Views:	171 
Size:	21.6 KB 
ID:	11029

    The only real drawback of this WM8804 board is the documentation -- there isn't any. Well, there is a short User Manual, but it's written in Chinglish and is essentially useless. The company won't release the schematic either which would be helpful in figuring out the switch settings. But after a little head scratching I worked it out. The WM8804 datasheet is pretty good.

    Anyway, I hope someone finds this useful.



    PS -- There is a short description of each new class in the respective .h files -- just under the PJRC copyright notice.
    Attached Files Attached Files

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    19,225
    Quote Originally Posted by gfvalvo View Post
    The major changes are accommodating a BCLK frequency of 64 * Fs and support for Teensy to operate in I2S Slave mode.
    The I2S slave mode object was recently improved and changed to 64 bits per frame. It's available in 1.38-beta2.

    Any chance you could give the latest beta a try? The only reason I2S slave mode was broken for so long (before 1.38-beta) was lack of usage and feedback....

  3. #3
    Senior Member
    Join Date
    Feb 2017
    Posts
    247
    I can give it a look when I get a chance. What’s the best way to try a Beta version without upsetting my “production” installation? I’m currently at Arduino 1.8.2, Teensyduino 1.36 (I know I should go to Teensyduino 1.37, just haven’t gotten around to it).

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    19,225
    Just extract the Arduino ZIP file somewhere else on your computer.

    If using Windows, you need to download the ZIP file. Don't use the Arduino windows installer.

    When you run the Teensyduino installer, of course you'll need to know where the other copy is. Pay attention to the folder location, since the Teensyduino installer may find and auto-select you first copy, especially if it's in the default location.

  5. #5
    Senior Member
    Join Date
    Feb 2017
    Posts
    247
    OK, will give it a shot.

    I just had a quick look at the source code on the github. You used a cleverer technique for 64 bits than I did. I used brute force and just set the DMA for 32 bit transfers each of Left and Right channels then threw away the unused 16 bits in the ISR.

    Anyway, looking at control_sgtl5000.cpp, I’m wondering if you’re going to need to update the AudioControlSGTL5000::enable() to set the device for 64 * Fs also?
    Code:
    write(CHIP_I2S_CTRL, 0x0030);
    Instead of
    Code:
    write(CHIP_I2S_CTRL, 0x0130);

  6. #6
    Senior Member
    Join Date
    Feb 2017
    Posts
    247
    OK Paul, by removing the Audio Adaptor and connecting the WM8804 EVB to TX_BCLK, TX_FS, I2S0_RXD0, and I2S0_TXD0 the new 64-bit I2S Slave classes worked as expected. The WM8804 was I2S bus master with timing based on the supplied Optical SPDIF signal. The T3.2 received the audio and sent it back out to the WM8804 (for I2S --> Optical SPDIF conversion) and to the Teensy's onboard DAC.

    Here's the Design Tool setup:

    Click image for larger version. 

Name:	Design Tool.jpg 
Views:	114 
Size:	22.8 KB 
ID:	11040

    Here's the sketch:
    Code:
    #include <Audio.h>
    #include "output_DacSyncLRCLK.h"
    
    AudioInputI2Sslave           i2sslave1;      //xy=623,453
    AudioMixer4                  mixer1;         //xy=826,465
    AudioOutputI2Sslave          i2sslave2;      //xy=838,349
    AudioOutputAnalogSyncLRCLK   dac1;           //xy=997,468
    AudioAnalyzeFFT1024          fft1024_1;      //xy=1004,547
    AudioConnection              patchCord1(i2sslave1, 0, i2sslave2, 0);
    AudioConnection              patchCord2(i2sslave1, 0, mixer1, 0);
    AudioConnection              patchCord3(i2sslave1, 1, i2sslave2, 1);
    AudioConnection              patchCord4(i2sslave1, 1, mixer1, 1);
    AudioConnection              patchCord5(mixer1, dac1);
    AudioConnection              patchCord6(mixer1, fft1024_1);
    
    void setup() {
      Serial.begin(115200);
      delay(1000);
      Serial.println("Starting");
      AudioMemory(15);
      AudioOutputAnalogSyncLRCLK::enableDmaRequest();
      mixer1.gain(0, 0.5);
      mixer1.gain(1, 0.5);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
    }
    
    void loop() {
    #if 1
      // print Fourier Transform data to the Arduino Serial Monitor
      if (fft1024_1.available()) {
    
        Serial.print("FFT: ");
        for (int i = 0; i < 30; i++) {
          float n = fft1024_1.read(i);
          printNumber(n);
        }
        Serial.println();
      }
    #endif
    }
    
    void printNumber(float n) {
      if (n >= 0.004) {
        Serial.print(n, 3);
        Serial.print(" ");
      } else {
        Serial.print("   -  "); // don't print "0.00"
      }
    }
    Note that I used a modified version of the AudioOutputAnalog class to run the onboard DAC. It triggers the DAC's DMA operation from LRCLK (aka FS) rather than the Teensy's clock. That way it stays synchronized with the audio from the WM8804. Here are the .h and .cpp files:
    Code:
    /* Audio Library for Teensy 3.X
       Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
    
       Development of this audio library was funded by PJRC.COM, LLC by sales of
       Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
       open source software by purchasing Teensy or other PJRC products.
    
       Permission is hereby granted, free of charge, to any person obtaining a copy
       of this software and associated documentation files (the "Software"), to deal
       in the Software without restriction, including without limitation the rights
       to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       copies of the Software, and to permit persons to whom the Software is
       furnished to do so, subject to the following conditions:
    
       The above copyright notice, development funding notice, and this permission
       notice shall be included in all copies or substantial portions of the Software.
    
       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       THE SOFTWARE.
    */
    
    // Modification of standard AudioOutputAnalog class to trigger DMA operations
    // from I2S LRCLK rather than from the programmable delay block.
    // To enable, must call enableDmaRequest after I2S constructors run.
    // Good place to do this is setup().
    
    
    #ifndef output_dac_synch_LRCLK_h_
    #define output_dac_synch_LRCLK_h_
    
    #include "Arduino.h"
    #include "AudioStream.h"
    #include "DMAChannel.h"
    
    
    class AudioOutputAnalogSyncLRCLK : public AudioStream
    {
      public:
        AudioOutputAnalogSyncLRCLK(void) : AudioStream(1, inputQueueArray) {
          begin();
        }
        virtual void update(void);
        void begin(void);
        void analogReference(int ref);
        static void enableDmaRequest(void);
    
      private:
        static audio_block_t *block_left_1st;
        static audio_block_t *block_left_2nd;
        static bool update_responsibility;
        audio_block_t *inputQueueArray[1];
    #if defined(KINETISK)
        static DMAChannel dma;
        static void isr(void);
    #elif defined(KINETISL)
        static DMAChannel dma1;
        static DMAChannel dma2;
        static void isr1(void);
        static void isr2(void);
    #endif
    };
    #endif
    Code:
    /* Audio Library for Teensy 3.X
       Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
    
       Development of this audio library was funded by PJRC.COM, LLC by sales of
       Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
       open source software by purchasing Teensy or other PJRC products.
    
       Permission is hereby granted, free of charge, to any person obtaining a copy
       of this software and associated documentation files (the "Software"), to deal
       in the Software without restriction, including without limitation the rights
       to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       copies of the Software, and to permit persons to whom the Software is
       furnished to do so, subject to the following conditions:
    
       The above copyright notice, development funding notice, and this permission
       notice shall be included in all copies or substantial portions of the Software.
    
       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       THE SOFTWARE.
    */
    
    #include "output_DacSyncLRCLK.h"
    
    #if !defined(KINETISK)
    #error Unsupported Processor
    #endif
    
    DMAMEM static uint16_t dac_buffer[AUDIO_BLOCK_SAMPLES*2];
    audio_block_t * AudioOutputAnalogSyncLRCLK::block_left_1st = NULL;
    audio_block_t * AudioOutputAnalogSyncLRCLK::block_left_2nd = NULL;
    bool AudioOutputAnalogSyncLRCLK::update_responsibility = false;
    DMAChannel AudioOutputAnalogSyncLRCLK::dma(false);
    
    void AudioOutputAnalogSyncLRCLK::begin(void)
    {
    	dma.begin(true); // Allocate the DMA channel first
    	SIM_SCGC2 |= SIM_SCGC2_DAC0;
    	DAC0_C0 = DAC_C0_DACEN;                   // 1.2V VDDA is DACREF_2
    	// slowly ramp up to DC voltage, approx 1/4 second
    	for (int16_t i=0; i<2048; i+=8) {
    		*(int16_t *)&(DAC0_DAT0L) = i;
    		delay(1);
    	}
    
      // Configure to trigger DMA using LRCLK on Pin 29
      SIM_SCGC5 |= SIM_SCGC5_PORTC;
      SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
      SIM_SCGC7 |= SIM_SCGC7_DMA;
     
    	dma.TCD->SADDR = dac_buffer;
    	dma.TCD->SOFF = 2;
    	dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
    	dma.TCD->NBYTES_MLNO = 2;
    	dma.TCD->SLAST = -sizeof(dac_buffer);
    	dma.TCD->DADDR = &DAC0_DAT0L;
    	dma.TCD->DOFF = 0;
    	dma.TCD->CITER_ELINKNO = sizeof(dac_buffer) / 2;
    	dma.TCD->DLASTSGA = 0;
    	dma.TCD->BITER_ELINKNO = sizeof(dac_buffer) / 2;
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
      dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PORTC);
    	update_responsibility = update_setup();
    	dma.enable();
    	dma.attachInterrupt(isr);
    }
    
    void AudioOutputAnalogSyncLRCLK::analogReference(int ref)
    {
    	// TODO: this should ramp gradually to the new DC level
    	if (ref == INTERNAL) {
    		DAC0_C0 &= ~DAC_C0_DACRFS; // 1.2V
    	} else {
    		DAC0_C0 |= DAC_C0_DACRFS;  // 3.3V
    	}
    }
    
    
    void AudioOutputAnalogSyncLRCLK::update(void)
    {
    	audio_block_t *block;
    	block = receiveReadOnly(0); // input 0
    	if (block) {
    		__disable_irq();
    		if (block_left_1st == NULL) {
    			block_left_1st = block;
    			__enable_irq();
    		} else if (block_left_2nd == NULL) {
    			block_left_2nd = block;
    			__enable_irq();
    		} else {
    			audio_block_t *tmp = block_left_1st;
    			block_left_1st = block_left_2nd;
    			block_left_2nd = block;
    			__enable_irq();
    			release(tmp);
    		}
    	}
    }
    
    void AudioOutputAnalogSyncLRCLK::isr(void)
    {
    	const int16_t *src, *end;
    	int16_t *dest;
    	audio_block_t *block;
    	uint32_t saddr;
    
    	saddr = (uint32_t)(dma.TCD->SADDR);
    	dma.clearInterrupt();
    	if (saddr < (uint32_t)dac_buffer + sizeof(dac_buffer) / 2) {
    		// DMA is transmitting the first half of the buffer
    		// so we must fill the second half
    		dest = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES];
    		end = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES*2];
    	} else {
    		// DMA is transmitting the second half of the buffer
    		// so we must fill the first half
    		dest = (int16_t *)dac_buffer;
    		end = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES];
    	}
    	block = AudioOutputAnalogSyncLRCLK::block_left_1st;
    	if (block) {
    		src = block->data;
    		do {
    			// TODO: this should probably dither
    			*dest++ = ((*src++) + 32767) >> 4;
    		} while (dest < end);
    		AudioStream::release(block);
    		AudioOutputAnalogSyncLRCLK::block_left_1st = AudioOutputAnalogSyncLRCLK::block_left_2nd;
    		AudioOutputAnalogSyncLRCLK::block_left_2nd = NULL;
    	} else {
    		do {
    			*dest++ = 2047;
    		} while (dest < end);
    	}
    	if (AudioOutputAnalogSyncLRCLK::update_responsibility) AudioStream::update_all();
    }
    
    void AudioOutputAnalogSyncLRCLK::enableDmaRequest() {
      // Enable PIN29 to trigger DMA
      // Call this during setup() after we're sure AudioInputWM8804Slave constructor has run
      CORE_PIN23_CONFIG |= PORT_PCR_IRQC(2); // pin 29, PTC10, I2S0_RX_FS
    }
    Finally, although the new classes worked, they didn't provide all the functionality of the example I provided. I wanted to take input from both the WM8804 and the SGTL5000 and then turn it around in the Teensy and send it back to the same devices. For that I needed the 64-bit Quad I2S Slave classes that I attached in my original post. I also attached a 64-bit Quad I2S Master class. This was Output only as you can't get input from the WM8804 unless it is the I2S Master.

Posting Permissions

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