Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 3 1 2 3 LastLast
Results 1 to 25 of 69

Thread: Teensy LC, audio library support?

Hybrid View

  1. #1
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248

    Teensy LC, audio library support?

    Quote Originally Posted by PaulStoffregen View Post
    But Teensy LC does have the I2S peripheral, and the audio library does have some limited support for Teensy LC. Even fairly simple audio systems quickly use up LC's limited CPU and memory.
    Would it be possible to talk to the PT8211 DAC over I2S with a Teensy LC?
    This small program compiles without error for Teensy LC but no sine output out of the PT8211 kit. This code does work on a Teensy 3.2.
    Code:
    // PT8211 bd    Teensy
    // VCC          3V3
    // GND          GND
    // WS           23
    // DIN          22
    // BCK          9
    // --           11 (MCLK)
    
    #include <Audio.h>
    
    uint32_t Freq = 440;
    
    AudioSynthWaveformSine   sine1;
    AudioOutputPT8211        pt8211_1;
    AudioConnection          patchCord1(sine1, 0, pt8211_1, 0);
    AudioConnection          patchCord2(sine1, 0, pt8211_1, 1);
    
    String incoming;
    
    void setup() {
      Serial.begin(115200);
      while (!Serial);                        // wait for serial monitor to be active
      Serial.setTimeout(50);                  // set timeout in millisecs for readString()
    
      pinMode(LED_BUILTIN, OUTPUT);
      digitalWrite(LED_BUILTIN, HIGH);
    
      AudioMemory(12);
      sine1.frequency(Freq);
      sine1.amplitude(1.0);
    }
    
    void loop() {
      while (Serial.available()) {
        incoming = Serial.readString();         // read string from serial monitor until timeout
        Serial.print(incoming);                 // return input string to serial monitor
        int32_t value = incoming.toInt();       // convert string into number
        Serial.println(value);                  // return value to serial monitor
        sine1.frequency(value);                 // update frequency
      }
    }
    Looking into the MKL26Z64 datasheet, the I2S pins are available on the 48pin package:

    Click image for larger version. 

Name:	TLC_I2Cpins.PNG 
Views:	98 
Size:	98.0 KB 
ID:	23106

    From the Teensy LC schematic:
    pin 33 connects to Teensy pin 15/A1
    pin 34 connects to Teensy pin 22/A8
    pin 35 connects to Teensy pin 23/A9
    pin 36 connects to Teensy pin 9
    pin 39 connects to Teensy pin 11


    So electrically it seems possible to output I2S on a Teensy LC.
    In which audio library files should I now have a look?

    [Arduino 1.8.13, Teensyduino 1.53, Windows 10 Pro]

    Thanks,
    Paul

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    28,468
    Quote Originally Posted by PaulS View Post
    Would it be possible to talk to the PT8211 DAC over I2S with a Teensy LC?
    It should be possible. The oversampling code might need to be disabled.


    This small program compiles without error for Teensy LC but no sine output out of the PT8211 kit.
    Yup. The audio library as ifdefs to put in a do-nothing version of each unsupported output, so the library at least compiles.

    To make it actually work, that do-nothing code needs to be replaced with do-something code....


    pin 34 connects to Teensy pin 22/A8
    pin 35 connects to Teensy pin 23/A9
    pin 36 connects to Teensy pin 9
    pin 39 connects to Teensy pin 11
    These are the same pins Teensy 3.2, 3.5, 3.6 use. When when/if this ever works, the audio shield and pt8211 kit for Teensy 3.x should "just work" when soldered to Teensy LC.



    In which audio library files should I now have a look?
    Your first place to look is output_dac.cpp. As far as I know, it's currently the only I/O code actually working on Teensy LC.

    I'm not 100% happy with the way DMA is used. But it was contributed code and generally seems to work (certainly a lot better than nothing at all), so I merged it. The main problem is Teensy LC's weaker DMA engine doesn't have an interrupt at half-used. So the only interrupt happens when the DMA controller completes the last transfer. If the interrupt isn't serviced within about 22 us (1 audio sample time), the next DMA transfer isn't set up and the output skips a sample.

    On Teensy 3.x and 4.x, the DMA controller gives an interrupt at complete and at half-used. The interrupt copies new data into the half of the buffer the DMA controller just finished using. If that interrupt isn't serviced quickly, it's not a problem. The DMA controller keeps streaming audio from the other half of the buffer. As long as the interrupt gets serviced within about 1450 us, it can get the data ready in the other half before it's needed.

    The Teensy LC code needlessly retains much of the original structure, using 2 buffers instead of 2 halves of a larger buffer.

    The way this should be done on Teensy LC involves using 2 DMA channels, with the channel linking feature, which is controlled by the DCR register of each channel, documented starting on page 378 in the ref manual. Each channel would set the LINKCC bits to 11, and the LCH1 bits to the number of the other channel, as as one channel competes it causes the other to activate. The idea is the same as using the half channel approach on the bigger boards, each each half would be its own DMA channel, using one of the buffers while the other waits to be refilled. Each channel would trigger its own interrupt, which would not need to check the destination address. Each interrupt would refill the buffer for the other channel.

    Whether anyone wants to go to all this trouble, I don't know. But I'm taking a little time to write this lengthy explanation in hopes you or maybe Frank would like to dive into the DMA settings and convert the DAC code to using 2 linked channels. Getting 2 channels working, so the DMA interrupt can tolerate more than ~22 us of latency would give a much more solid foundation on Teensy LC.

    Of course, if you just want to try getting PT8211 running with the existing 1 DMA channel approach, I can understand that. But please do keep the interrupt latency issue in mind.

  3. #3
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    Hi Frank, no need to spend your precious time on this adventure. As said above, I was just curious.

    Gruesse,
    Paul

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    I've never done much with the LC and don't even know how DMA works on this chip..
    But I can try to make at least I2S and the slightly different PT8211 work. If I remember correctly, from years ago, there is only one single bit different. And yes, the oversampling of course. If that works I can try to do more, or use two DMA channels, but the question is, if it worth the time.. having not enough RAM is a problem

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    28,468
    Quote Originally Posted by Frank B View Post
    I've never done much with the LC and don't even know how DMA works on this chip..
    The good news is the DMA controller is much simpler, with only 4 registers instead of the 8 we normally use in the TCD. There is no "minor loop", just 1 transfer for each trigger event. Fortunately we're configuring the minor loop to do just a single transfer on most of the audio library usage.


    but the question is, if it worth the time.. having not enough RAM is a problem
    I thought this too, but the DAC output does work pretty well.

    If you look into this, I would recommend cutting the buffer sizes in half. Then use each DMA channel to consume half of the 2 pending audio blocks. Since it will become 2 separate interrupt functions, only the 2nd one needs to grab the next pending block from the update() function. The first interrupt will always consume the other half of whatever block was available when the 2nd interrupt triggered.

    Maybe abandon queuing a second block. It really isn't necessary. Someday I want to go through all the audio output objects and remove the 2nd block pointer.

    With those changes, the memory usage for stereo output should be 2 buffers each holding 64 stereo pairs, plus 2 audio blocks filled with data from the rest of the audio design. I believe that adds up to 1024 bytes, plus tiny overhead on the 2 audio blocks. Or if someone routes the same audio to both channels, then only 768 bytes for buffers to make the output work.

  6. #6
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    Hi Paul, thanks for your extensive answer!
    I'm afraid the DMA stuff is a bit beyond my league.
    To be honest, I don't need this to work - I was just curious whether I could built a simple sinegenerator with a Teensy LC when reading about "limited audio support on Teensy LC".

    Regards,
    Paul
    Last edited by PaulS; 01-05-2021 at 08:18 PM.

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    I have no other "TODO" anyway.. so it is a good Idea.

  8. #8
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    28,468
    Maybe each interrupt will need to reprogram the other channel's settings to get it ready again? At least it's only 4 registers to write.

  9. #9
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    I'll dig into this. LOL, the ony unsoldered LC I found in my drawer is the original purple Beta board. Cool. It will do it - the right task for a beta

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735

    Post

    @Paul,

    i'm afraid it's not that easy.
    On the LC CPU, channel-linking "on complete" does not really work. The linked channeld gets triggert only once. (i've tested with manual "software" triggering)
    Obviously exactly as name says... only "on complete", one time ..

    Then, I tried a different approach:
    Here, the second channel is linked via "0x10 Perform a link to channel LCH1 after each cycle-steal transfer"
    channel 1 and channel 2 with the same source-address, channel 2 with half of size of channel 1 and with a dummy destination.
    Now, i get the "half ready" interrupt (which is "complete" on channel2)
    That works.
    One time.
    Because: it is not possible to use the "circular" feature. It stops as the normal mode, when the transfer is ready. The only difference is, that the source-address gets reset to the start value.
    But it stops.
    On both channels. Continous restart is not possible. You need an irq to do that.

    So, the precious timing is the same as in the output_dac approach.. no win with a 2nd channel.
    The interrupt needs to be fast and it needs to reset and restart the DMA.

    Friday, I'll continue.. maybe I have an other idea, or I'll just use the simple approach.
    Last edited by Frank B; 01-06-2021 at 07:50 PM.

  11. #11
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    Hm, or, maybe there is a difference between manual triggering and periphal requests.. maybe it works when triggered by a periphal?
    Have to test that...

  12. #12
    Junior Member
    Join Date
    Jan 2021
    Location
    Winnipeg, Canada
    Posts
    14
    Well isn't this timely... I was starting to mess around with a teensy LC with the intent of generating audio with the Audio Shield. Very interested in your results, Frank!

    I have seen other microcontrollers that treat DMA requests from peripherals differently from generic DMA, so that's an idea worth checking out. Often there's support for ping-pong dma chaining... but I haven't gotten into the reference manual for this one yet.

    I just got USB serial and MIDI endpoints connected this evening. That uses more code space than I had hoped...

  13. #13
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    Turns out that it will be fun to make this work...

    I have not that much time today. Tomorrow..


    Click image for larger version. 

Name:	2021-01-07 12_28_34-Start.png 
Views:	97 
Size:	51.5 KB 
ID:	23137
    This means we are fixed to 46.875 or 23.437 kHz, or have to use MCLK as input.
    Maybe we can use a timer to trigger the MCLK-Pin...

    Edit:
    The PT8211 does not use MCLK, so no problem with it.
    Don't know if 48MHZ or 24MHz MCLK is an issue for other devices.
    Last edited by Frank B; 01-07-2021 at 12:22 PM.

  14. #14
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    28,468
    I ran several tests. Looks like MCLK can only be 16 or 48 MHz.

    We need to keep the sample rate at 44117.6, same as Teensy 3.x. The main use case is playing 44.1 kHz WAV files, either from SD card or flash chip. Also from a long-term software maintenance point of view, I want to keep the default sample rate consistent.

    Fortunately, it looks like we can get the correct sample rate with MCLK at 48 MHz and I2S_TCR2_DIV = 16 (divide by 34), but with 32 bits per frame rather than the usual 64. Hopefully that is enough to do PT8211 without oversampling?

    For I2S, having MCLK / BCLK ratio that isn't a power of 2, and BCLK / LRCLK ratio of 32 is so limiting that it's probably not worthwhile to implement I2S. Almost all chips need MCLK, and most of the ones which don't (MEMS mics) require BCLK / LRCLK ratio 64.

    But I2S slave mode should be possible, maybe?


    Here's a quick test I cobbled together. It outputs 44.1176 kHz on LRCLK, 1.41 MHz on BCLK, and 48 MHz on MCLK.

    Code:
    // https://forum.pjrc.com/threads/65735?p=265811&viewfull=1#post265811
    
    void setup() {
      while (!Serial);
      Serial.println("MCLK Test on Teensy LC");
      SIM_SCGC6 |= SIM_SCGC6_I2S;
      I2S0_MCR = I2S_MCR_MICS(0) | I2S_MCR_MOE;
      // I2S0_MDR has no effect on Teensy LC
      CORE_PIN11_CONFIG = PORT_PCR_MUX(6);
    
      // configure transmitter
      I2S0_TMR = 0;
      I2S0_TCR1 = I2S_TCR1_TFW(0);  // watermark at half fifo size
      I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1)
                | I2S_TCR2_BCD | I2S_TCR2_DIV(16);
      I2S0_TCR3 = I2S_TCR3_TCE;
      I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF
                | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD;
      I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15);
    
      CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
      CORE_PIN9_CONFIG  = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
      
      I2S0_TCSR = I2S_TCSR_SR;
      I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    
      Serial.println("Configured");
    }
    
    void loop() {
    }

  15. #15
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    Great Idea!
    I'll try that.

  16. #16
    Junior Member
    Join Date
    Jan 2021
    Location
    Winnipeg, Canada
    Posts
    14
    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 <Arduino.h>
    #include "output_i2s.h"
    #include "memcpy_audio.h"
    
    audio_block_t * AudioOutputI2S::block_left_1st = NULL;
    audio_block_t * AudioOutputI2S::block_right_1st = NULL;
    audio_block_t * AudioOutputI2S::block_left_2nd = NULL;
    audio_block_t * AudioOutputI2S::block_right_2nd = NULL;
    uint16_t  AudioOutputI2S::block_left_offset = 0;
    uint16_t  AudioOutputI2S::block_right_offset = 0;
    bool AudioOutputI2S::update_responsibility = false;
    DMAChannel AudioOutputI2S::dma(false);
    DMAMEM __attribute__((aligned(32))) static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES];
    
    #if defined(__IMXRT1062__)
    #include "utility/imxrt_hw.h"
    #endif
    
    void AudioOutputI2S::begin(void)
    {
    	dma.begin(true); // Allocate the DMA channel first
    
    	block_left_1st = NULL;
    	block_right_1st = NULL;
    
    	config_i2s();
    
    #if defined(KINETISK)
    	CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0
    
    	dma.TCD->SADDR = i2s_tx_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(i2s_tx_buffer);
    	dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2);
    	dma.TCD->DOFF = 0;
    	dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->DLASTSGA = 0;
    	dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX);
    	dma.enable();
    
    	I2S0_TCSR = I2S_TCSR_SR;
    	I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    
    #elif defined(KINETISL)
    
    	SIM_SCGC6 |= SIM_SCGC6_I2S;
    	I2S0_MCR = I2S_MCR_MICS(0) | I2S_MCR_MOE;
    	// I2S0_MDR has no effect on Teensy LC
    	// CORE_PIN11_CONFIG = PORT_PCR_MUX(6);
    
    	// configure transmitter
    	I2S0_TMR = 0;
    	I2S0_TCR1 = I2S_TCR1_TFW(0);  // watermark at half fifo size
    	I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1)
    			| I2S_TCR2_BCD | I2S_TCR2_DIV(16);
    	I2S0_TCR3 = I2S_TCR3_TCE;
    	I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF
    			| I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD;
    	I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15);
    
    	// CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
    	// CORE_PIN9_CONFIG  = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
    
    	I2S0_TCSR = I2S_TCSR_SR;
    	I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    
    #elif defined(__IMXRT1062__)
    	CORE_PIN7_CONFIG  = 3;  //1:TX_DATA0
    	dma.TCD->SADDR = i2s_tx_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(i2s_tx_buffer);
    	dma.TCD->DOFF = 0;
    	dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->DLASTSGA = 0;
    	dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    	dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2);
    	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX);
    	dma.enable();
    
    	I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
    	I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    #endif
    	update_responsibility = update_setup();
    	dma.attachInterrupt(isr);
    }
    
    
    void AudioOutputI2S::isr(void)
    {
    #if defined(KINETISK) || defined(__IMXRT1062__)
    	int16_t *dest;
    	audio_block_t *blockL, *blockR;
    	uint32_t saddr, offsetL, offsetR;
    
    	saddr = (uint32_t)(dma.TCD->SADDR);
    	dma.clearInterrupt();
    	if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) {
    		// DMA is transmitting the first half of the buffer
    		// so we must fill the second half
    		dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2];
    		if (AudioOutputI2S::update_responsibility) AudioStream::update_all();
    	} else {
    		// DMA is transmitting the second half of the buffer
    		// so we must fill the first half
    		dest = (int16_t *)i2s_tx_buffer;
    	}
    
    	blockL = AudioOutputI2S::block_left_1st;
    	blockR = AudioOutputI2S::block_right_1st;
    	offsetL = AudioOutputI2S::block_left_offset;
    	offsetR = AudioOutputI2S::block_right_offset;
    
    	if (blockL && blockR) {
    		memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR);
    		offsetL += AUDIO_BLOCK_SAMPLES / 2;
    		offsetR += AUDIO_BLOCK_SAMPLES / 2;
    	} else if (blockL) {
    		memcpy_tointerleaveL(dest, blockL->data + offsetL);
    		offsetL += AUDIO_BLOCK_SAMPLES / 2;
    	} else if (blockR) {
    		memcpy_tointerleaveR(dest, blockR->data + offsetR);
    		offsetR += AUDIO_BLOCK_SAMPLES / 2;
    	} else {
    		memset(dest,0,AUDIO_BLOCK_SAMPLES * 2);
    	}
    
    	arm_dcache_flush_delete(dest, sizeof(i2s_tx_buffer) / 2 );
    
    	if (offsetL < AUDIO_BLOCK_SAMPLES) {
    		AudioOutputI2S::block_left_offset = offsetL;
    	} else {
    		AudioOutputI2S::block_left_offset = 0;
    		AudioStream::release(blockL);
    		AudioOutputI2S::block_left_1st = AudioOutputI2S::block_left_2nd;
    		AudioOutputI2S::block_left_2nd = NULL;
    	}
    	if (offsetR < AUDIO_BLOCK_SAMPLES) {
    		AudioOutputI2S::block_right_offset = offsetR;
    	} else {
    		AudioOutputI2S::block_right_offset = 0;
    		AudioStream::release(blockR);
    		AudioOutputI2S::block_right_1st = AudioOutputI2S::block_right_2nd;
    		AudioOutputI2S::block_right_2nd = NULL;
    	}
    #else
    	const int16_t *src, *end;
    	int16_t *dest;
    	audio_block_t *block;
    	uint32_t saddr, offset;
    
    	saddr = (uint32_t)(dma.CFG->SAR);
    	dma.clearInterrupt();
    	if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) {
    		// DMA is transmitting the first half of the buffer
    		// so we must fill the second half
    		dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2];
    		end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES];
    		if (AudioOutputI2S::update_responsibility) AudioStream::update_all();
    	} else {
    		// DMA is transmitting the second half of the buffer
    		// so we must fill the first half
    		dest = (int16_t *)i2s_tx_buffer;
    		end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2];
    	}
    
    	block = AudioOutputI2S::block_left_1st;
    	if (block) {
    		offset = AudioOutputI2S::block_left_offset;
    		src = &block->data[offset];
    		do {
    			*dest = *src++;
    			dest += 2;
    		} while (dest < end);
    		offset += AUDIO_BLOCK_SAMPLES/2;
    		if (offset < AUDIO_BLOCK_SAMPLES) {
    			AudioOutputI2S::block_left_offset = offset;
    		} else {
    			AudioOutputI2S::block_left_offset = 0;
    			AudioStream::release(block);
    			AudioOutputI2S::block_left_1st = AudioOutputI2S::block_left_2nd;
    			AudioOutputI2S::block_left_2nd = NULL;
    		}
    	} else {
    		do {
    			*dest = 0;
    			dest += 2;
    		} while (dest < end);
    	}
    	dest -= AUDIO_BLOCK_SAMPLES - 1;
    	block = AudioOutputI2S::block_right_1st;
    	if (block) {
    		offset = AudioOutputI2S::block_right_offset;
    		src = &block->data[offset];
    		do {
    			*dest = *src++;
    			dest += 2;
    		} while (dest < end);
    		offset += AUDIO_BLOCK_SAMPLES/2;
    		if (offset < AUDIO_BLOCK_SAMPLES) {
    			AudioOutputI2S::block_right_offset = offset;
    		} else {
    			AudioOutputI2S::block_right_offset = 0;
    			AudioStream::release(block);
    			AudioOutputI2S::block_right_1st = AudioOutputI2S::block_right_2nd;
    			AudioOutputI2S::block_right_2nd = NULL;
    		}
    	} else {
    		do {
    			*dest = 0;
    			dest += 2;
    		} while (dest < end);
    	}
    #endif
    }
    
    
    
    
    void AudioOutputI2S::update(void)
    {
    	// null audio device: discard all incoming data
    	//if (!active) return;
    	//audio_block_t *block = receiveReadOnly();
    	//if (block) release(block);
    
    	audio_block_t *block;
    	block = receiveReadOnly(0); // input 0 = left channel
    	if (block) {
    		__disable_irq();
    		if (block_left_1st == NULL) {
    			block_left_1st = block;
    			block_left_offset = 0;
    			__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;
    			block_left_offset = 0;
    			__enable_irq();
    			release(tmp);
    		}
    	}
    	block = receiveReadOnly(1); // input 1 = right channel
    	if (block) {
    		__disable_irq();
    		if (block_right_1st == NULL) {
    			block_right_1st = block;
    			block_right_offset = 0;
    			__enable_irq();
    		} else if (block_right_2nd == NULL) {
    			block_right_2nd = block;
    			__enable_irq();
    		} else {
    			audio_block_t *tmp = block_right_1st;
    			block_right_1st = block_right_2nd;
    			block_right_2nd = block;
    			block_right_offset = 0;
    			__enable_irq();
    			release(tmp);
    		}
    	}
    }
    
    #if defined(KINETISK) || defined(KINETISL)
    // MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate
    //
    #if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000
      // PLL is at 96 MHz in these modes
      #define MCLK_MULT 2
      #define MCLK_DIV  17
    #elif F_CPU == 72000000
      #define MCLK_MULT 8
      #define MCLK_DIV  51
    #elif F_CPU == 120000000
      #define MCLK_MULT 8
      #define MCLK_DIV  85
    #elif F_CPU == 144000000
      #define MCLK_MULT 4
      #define MCLK_DIV  51
    #elif F_CPU == 168000000
      #define MCLK_MULT 8
      #define MCLK_DIV  119
    #elif F_CPU == 180000000
      #define MCLK_MULT 16
      #define MCLK_DIV  255
      #define MCLK_SRC  0
    #elif F_CPU == 192000000
      #define MCLK_MULT 1
      #define MCLK_DIV  17
    #elif F_CPU == 216000000
      #define MCLK_MULT 12
      #define MCLK_DIV  17
      #define MCLK_SRC  1
    #elif F_CPU == 240000000
      #define MCLK_MULT 2
      #define MCLK_DIV  85
      #define MCLK_SRC  0
    #elif F_CPU == 256000000
      #define MCLK_MULT 12
      #define MCLK_DIV  17
      #define MCLK_SRC  1
    #elif F_CPU == 16000000
      #define MCLK_MULT 12
      #define MCLK_DIV  17
    #else
      #error "This CPU Clock Speed is not supported by the Audio library";
    #endif
    
    #ifndef MCLK_SRC
    #if F_CPU >= 20000000
      #define MCLK_SRC  3  // the PLL
    #else
      #define MCLK_SRC  0  // system clock
    #endif
    #endif
    #endif
    
    
    void AudioOutputI2S::config_i2s(void)
    {
    #if defined(KINETISK) || defined(KINETISL)
    	SIM_SCGC6 |= SIM_SCGC6_I2S;
    	SIM_SCGC7 |= SIM_SCGC7_DMA;
    	SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
    
    	// if either transmitter or receiver is enabled, do nothing
    	if (I2S0_TCSR & I2S_TCSR_TE) return;
    	if (I2S0_RCSR & I2S_RCSR_RE) return;
    
    	// enable MCLK output
    	I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE;
    	while (I2S0_MCR & I2S_MCR_DUF) ;
    	I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1));
    
    	// configure transmitter
    	I2S0_TMR = 0;
    	I2S0_TCR1 = I2S_TCR1_TFW(1);  // watermark at half fifo size
    	I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1)
    		| I2S_TCR2_BCD | I2S_TCR2_DIV(1);
    	I2S0_TCR3 = I2S_TCR3_TCE;
    	I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF
    		| I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD;
    	I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31);
    
    	// configure receiver (sync'd to transmitter clocks)
    	I2S0_RMR = 0;
    	I2S0_RCR1 = I2S_RCR1_RFW(1);
    	I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1)
    		| I2S_RCR2_BCD | I2S_RCR2_DIV(1);
    	I2S0_RCR3 = I2S_RCR3_RCE;
    	I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF
    		| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    	I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31);
    
    	// configure pin mux for 3 clock signals
    	CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
    	CORE_PIN9_CONFIG  = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
    	CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK
    
    #elif defined(__IMXRT1062__)
    
    	CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
    
    	// if either transmitter or receiver is enabled, do nothing
    	if (I2S1_TCSR & I2S_TCSR_TE) return;
    	if (I2S1_RCSR & I2S_RCSR_RE) return;
    //PLL:
    	int fs = AUDIO_SAMPLE_RATE_EXACT;
    	// PLL between 27*24 = 648MHz und 54*24=1296MHz
    	int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
    	int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
    
    	double C = ((double)fs * 256 * n1 * n2) / 24000000;
    	int c0 = C;
    	int c2 = 10000;
    	int c1 = C * c2 - (c0 * c2);
    	set_audioClock(c0, c1, c2);
    
    	// clear SAI1_CLK register locations
    	CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
    		   | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4
    	CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
    		   | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
    		   | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f
    
    	// Select MCLK
    	IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1
    		& ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
    		| (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));
    
    	CORE_PIN23_CONFIG = 3;  //1:MCLK
    	CORE_PIN21_CONFIG = 3;  //1:RX_BCLK
    	CORE_PIN20_CONFIG = 3;  //1:RX_SYNC
    
    	int rsync = 0;
    	int tsync = 1;
    
    	I2S1_TMR = 0;
    	//I2S1_TCSR = (1<<25); //Reset
    	I2S1_TCR1 = I2S_TCR1_RFW(1);
    	I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
    		    | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
    	I2S1_TCR3 = I2S_TCR3_TCE;
    	I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
    		    | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
    	I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));
    
    	I2S1_RMR = 0;
    	//I2S1_RCSR = (1<<25); //Reset
    	I2S1_RCR1 = I2S_RCR1_RFW(1);
    	I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP  // sync=0; rx is async;
    		    | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
    	I2S1_RCR3 = I2S_RCR3_RCE;
    	I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
    		    | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    	I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));
    
    #endif
    }
    
    
    /******************************************************************/
    
    void AudioOutputI2Sslave::begin(void)
    {
    
    	dma.begin(true); // Allocate the DMA channel first
    
    	block_left_1st = NULL;
    	block_right_1st = NULL;
    
    	AudioOutputI2Sslave::config_i2s();
    
    #if defined(KINETISK)
    	CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0
    	dma.TCD->SADDR = i2s_tx_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(i2s_tx_buffer);
    	dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2);
    	dma.TCD->DOFF = 0;
    	dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->DLASTSGA = 0;
    	dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX);
    	dma.enable();
    
    	I2S0_TCSR = I2S_TCSR_SR;
    	I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    
    #elif defined(__IMXRT1062__)
    	CORE_PIN7_CONFIG  = 3;  //1:TX_DATA0
    	dma.TCD->SADDR = i2s_tx_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(i2s_tx_buffer);
    	dma.TCD->DOFF = 0;
    	dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->DLASTSGA = 0;
    	dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;
    	dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2);
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX);
    	dma.enable();
    
    	I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
    	I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
    #endif
    
    	update_responsibility = update_setup();
    	dma.attachInterrupt(isr);
    }
    
    void AudioOutputI2Sslave::config_i2s(void)
    {
    #if defined(KINETISK)
    	SIM_SCGC6 |= SIM_SCGC6_I2S;
    	SIM_SCGC7 |= SIM_SCGC7_DMA;
    	SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
    
    	// if either transmitter or receiver is enabled, do nothing
    	if (I2S0_TCSR & I2S_TCSR_TE) return;
    	if (I2S0_RCSR & I2S_RCSR_RE) return;
    
    	// Select input clock 0
    	// Configure to input the bit-clock from pin, bypasses the MCLK divider
    	I2S0_MCR = I2S_MCR_MICS(0);
    	I2S0_MDR = 0;
    
    	// configure transmitter
    	I2S0_TMR = 0;
    	I2S0_TCR1 = I2S_TCR1_TFW(1);  // watermark at half fifo size
    	I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP;
    
    	I2S0_TCR3 = I2S_TCR3_TCE;
    	I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF
    		| I2S_TCR4_FSE | I2S_TCR4_FSP;
    
    	I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31);
    
    	// configure receiver (sync'd to transmitter clocks)
    	I2S0_RMR = 0;
    	I2S0_RCR1 = I2S_RCR1_RFW(1);
    	I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP;
    
    	I2S0_RCR3 = I2S_RCR3_RCE;
    	I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF
    		| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    
    	I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31);
    
    	// configure pin mux for 3 clock signals
    	CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
    	CORE_PIN9_CONFIG  = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
    	CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK
    
    #elif defined(__IMXRT1062__)
    
    	CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
    
    	// if either transmitter or receiver is enabled, do nothing
    	if (I2S1_TCSR & I2S_TCSR_TE) return;
    	if (I2S1_RCSR & I2S_RCSR_RE) return;
    
    	// not using MCLK in slave mode - hope that's ok?
    	//CORE_PIN23_CONFIG = 3;  // AD_B1_09  ALT3=SAI1_MCLK
    	CORE_PIN21_CONFIG = 3;  // AD_B1_11  ALT3=SAI1_RX_BCLK
    	CORE_PIN20_CONFIG = 3;  // AD_B1_10  ALT3=SAI1_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) | I2S_TCR2_BCP;
    	I2S1_TCR3 = I2S_TCR3_TCE;
    	I2S1_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF
    		| I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_RCR4_FSD;
    	I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31);
    
    	// configure receiver
    	I2S1_RMR = 0;
    	I2S1_RCR1 = I2S_RCR1_RFW(1);
    	I2S1_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP;
    	I2S1_RCR3 = I2S_RCR3_RCE;
    	I2S1_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF
    		| I2S_RCR4_FSE | I2S_RCR4_FSP;
    	I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31);
    
    #endif
    }
    Revised output_i2s.cpp.

  17. #17
    Junior Member
    Join Date
    Jan 2021
    Location
    Winnipeg, Canada
    Posts
    14
    Ooh, nice! Thanks! I've got clocks and frame for 44100 Hz (411171 Hz) on my teensy LC!

  18. #18
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    I have it working, with 2 DMA channels, 2IRQ, and without the double-buffering.
    There were some details that were not that easy to find.
    - For example, the I2S has no FRDE bit.
    - I had to use 16 Bit DMA transfers (I'll look if I can change that. But it is not that important.)

    I'll clean up my sourcecode now, and try to optimize some things before I do a Github pullrequest for Paul.

  19. #19
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    Great! Thanks. I'll check it out once on Github.

    Paul

  20. #20
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    The PR is here
    Last edited by Frank B; 01-08-2021 at 05:21 PM.

  21. #21
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    Thanks Frank.
    When I copy the new files and compile the sketch on top of this thread, I see this on the scope [measured at the output connector of the PT8211]:

    Left channel
    Click image for larger version. 

Name:	Left.png 
Views:	100 
Size:	19.4 KB 
ID:	23154

    Right channel
    Click image for larger version. 

Name:	Right.png 
Views:	67 
Size:	23.8 KB 
ID:	23155

    I will have a look at your files now.

    Paul

  22. #22
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    OK, it was an error when shifting 16 bits ints..
    Attached Files Attached Files

  23. #23
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,735
    Strange.

    I'll take a nap now and look this evening, or tomorrow.

  24. #24
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    Sure, no hurry!

  25. #25
    Senior Member PaulS's Avatar
    Join Date
    Apr 2015
    Location
    Netherlands
    Posts
    1,248
    That solved it!

    Click image for larger version. 

Name:	SDS00010.png 
Views:	70 
Size:	54.1 KB 
ID:	23158

    And still some memory left:
    Code:
    Sketch uses 18284 bytes (28%) of program storage space. Maximum is 63488 bytes.
    Global variables use 5296 bytes (64%) of dynamic memory, leaving 2896 bytes for local variables. Maximum is 8192 bytes.
    Thanks,
    Paul

Posting Permissions

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