SAI3 Audio alternate pins

Dogbone06

Well-known member
Hey! I've been looking into the Teensy Audio library and it's very extensive. Many many files.

The original Teensy Audio pins are (SAI2):
1732911449292.png


I am using SAI3 and the following pins, which are alternates from the 1062 datasheet:
1732911504806.png


Image from datasheet for reference:
1732911567135.png


So one must change this probably in multiple places to make the Audio library use these pins instead.
Anyone know in which files I must change this?
 
The Audio library currently uses SAI3 to generate MQS audio. That being said, a very quick look at the datasheet suggests you could make modified versions of the I2S2 files, changing the registers from I2S2 to I2S3, and some of the pin configuration stuff, and stand a chance of getting something working. You're looking for the output_i2s2.cpp and .h files, and the input_i2s2.cpp and .h ones.
 
The Audio library currently uses SAI3 to generate MQS audio. That being said, a very quick look at the datasheet suggests you could make modified versions of the I2S2 files, changing the registers from I2S2 to I2S3, and some of the pin configuration stuff, and stand a chance of getting something working. You're looking for the output_i2s2.cpp and .h files, and the input_i2s2.cpp and .h ones.
Thanks for your reply! As I'm not using a Microphone, I could skip the input file, correct?
 
You probably could - not true the other way around, because the hardware initialisation is in output_i2s2.cpp. A full implementation would do both, add the objects to the Design Tool, including adding the clash markers with MQS, and edit library.txt for Arduino IDE syntax highlighting...
 
You probably could - not true the other way around, because the hardware initialisation is in output_i2s2.cpp. A full implementation would do both, add the objects to the Design Tool, including adding the clash markers with MQS, and edit library.txt for Arduino IDE syntax highlighting...
I'll give this a solid try by copying the i2s2 files and naming them i2s3. If I am successful I will share the files here to help others that want to use SAI3 on their custom setup.
 
Depending on your hardware, you might need MCLK as well as BCLK (alt. pin on the next datasheet page...)
I have other hardware where I use the original pins. Using a 3W amplifier (MAX98357) and only 3 pins. It works great, so I figure I can do the same here but with SAI3 instead. This would be for Mono audio I believe(?). If I want left and right I need more pins(?).
 
I²S can do two channels with a single audio data pin - that's what LRCLK is for, to signal the switch from left- to right-channel data. Depending on how the /SD_MODE pin is wired on your amplifier(s), you could fit two MAX98357s and have stereo.

If you configure the SAI module in TDM mode, it looks as if you could get 4-channel audio with four MAX98357s (figure 21 in the datasheet I just found). You'd then be looking at modifying output_tdm2.cpp/.h.
 
I²S can do two channels with a single audio data pin - that's what LRCLK is for, to signal the switch from left- to right-channel data. Depending on how the /SD_MODE pin is wired on your amplifier(s), you could fit two MAX98357s and have stereo.

If you configure the SAI module in TDM mode, it looks as if you could get 4-channel audio with four MAX98357s (figure 21 in the datasheet I just found). You'd then be looking at modifying output_tdm2.cpp/.h.
That's actually very cool!
I've made the I2S3 files for output, no success yet, but I will do the Input as well for the sake of it.
And I added output_i2s3.h as include in Audio.h
 
This is what I have done:

* Audio.h now also includes output_i2s3.h

* Copied files:
output_i2s2.cpp
output_i2s2.h
and made them into
output_i2s3.cpp
output_i2s3.h

* Inside the new files I have renamed all names that contain:
- SAI2 -> SAI3, a few registers had other changes as well in the name.
- i2s2 -> i2s3
- I2S2 -> I2S3

The pins are changed by looking in the datasheet. For example:
This:
Code:
CORE_PIN4_CONFIG  = 2;  //2:TX_BCLK
CORE_PIN3_CONFIG  = 2;  //2:TX_SYNC
IOMUXC_SAI2_RX_BCLK_SELECT_INPUT =0; //page 422 bitclock IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06
IOMUXC_SAI2_RX_SYNC_SELECT_INPUT =0; //IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_05

Is changed to this:
Code:
IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03  = 8; //8: SAI3_TX_BCLK
IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02  = 8; //8: SAI3_TX_SYNC
IOMUXC_SAI3_IPP_IND_SAI_RXBCLK_SELECT_INPUT = 1; // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03
IOMUXC_SAI3_IPP_IND_SAI_RXSYNC_SELECT_INPUT = 1; // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02

And of course the same searches have been made in output_i2s3.h.

I get no audio, perhaps the actual audio code is wrong? I am trying to just make a tone sound:
Code:
AudioSynthWaveform waveform;
AudioOutputI2S3 i2s3;
AudioConnection patchCord1(waveform, i2s3);

waveform.begin(WAVEFORM_SINE);
waveform.amplitude(10);
waveform.frequency(1000);

Wonder what I'm missing here. I have not touched the input files yet. Maybe that's it.

output_i2s3.cpp
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.
 */
#if defined(__IMXRT1062__)
#include <Arduino.h>
#include "output_i2s3.h"
#include "memcpy_audio.h"
#include "utility/imxrt_hw.h"

audio_block_t * AudioOutputI2S3::block_left_1st = NULL;
audio_block_t * AudioOutputI2S3::block_right_1st = NULL;
audio_block_t * AudioOutputI2S3::block_left_2nd = NULL;
audio_block_t * AudioOutputI2S3::block_right_2nd = NULL;
uint16_t  AudioOutputI2S3::block_left_offset = 0;
uint16_t  AudioOutputI2S3::block_right_offset = 0;
bool AudioOutputI2S3::update_responsibility = false;
DMAChannel AudioOutputI2S3::dma(false);
DMAMEM __attribute__((aligned(32))) static uint32_t i2s3_tx_buffer[AUDIO_BLOCK_SAMPLES];

#include "utility/imxrt_hw.h"

void AudioOutputI2S3::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    block_left_1st = NULL;
    block_right_1st = NULL;

    config_i2s();

    // if AudioInputI2S3 set I2S_TCSR_TE (for clock sync), disable it
    I2S3_TCSR = 0;
    while (I2S3_TCSR & I2S_TCSR_TE) ; //wait for transmit disabled

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_01  = 8;  //SD_B1_01, 8=SAI3_TX_DATA, page 547

    dma.TCD->SADDR = i2s3_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(i2s3_tx_buffer);
    dma.TCD->DOFF = 0;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->DLASTSGA = 0;
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.TCD->DADDR = (void *)((uint32_t)&I2S3_TDR0 + 2);
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_TX);
    dma.enable();

    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR;

    update_responsibility = update_setup();
    dma.attachInterrupt(isr);
}

void AudioOutputI2S3::isr(void)
{
    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)i2s3_tx_buffer + sizeof(i2s3_tx_buffer) / 2) {
        // DMA is transmitting the first half of the buffer
        // so we must fill the second half
        dest = (int16_t *)&i2s3_tx_buffer[AUDIO_BLOCK_SAMPLES/2];
        if (AudioOutputI2S3::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 *)i2s3_tx_buffer;
    }

    blockL = AudioOutputI2S3::block_left_1st;
    blockR = AudioOutputI2S3::block_right_1st;
    offsetL = AudioOutputI2S3::block_left_offset;
    offsetR = AudioOutputI2S3::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);       
    }

    #if IMXRT_CACHE_ENABLED >= 2       
    arm_dcache_flush_delete(dest, sizeof(i2s3_tx_buffer) / 2 );
    #endif   
    
    if (offsetL < AUDIO_BLOCK_SAMPLES) {
        AudioOutputI2S3::block_left_offset = offsetL;
    } else {
        AudioOutputI2S3::block_left_offset = 0;
        AudioStream::release(blockL);
        AudioOutputI2S3::block_left_1st = AudioOutputI2S3::block_left_2nd;
        AudioOutputI2S3::block_left_2nd = NULL;
    }
    if (offsetR < AUDIO_BLOCK_SAMPLES) {
        AudioOutputI2S3::block_right_offset = offsetR;
    } else {
        AudioOutputI2S3::block_right_offset = 0;
        AudioStream::release(blockR);
        AudioOutputI2S3::block_right_1st = AudioOutputI2S3::block_right_2nd;
        AudioOutputI2S3::block_right_2nd = NULL;
    }
}




void AudioOutputI2S3::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);
        }
    }
}

void AudioOutputI2S3::config_i2s(void)
{
    CCM_CCGR5 |= CCM_CCGR5_SAI3(CCM_CCGR_ON);

    // if either transmitter or receiver is enabled, do nothing
    if (I2S3_TCSR & I2S_TCSR_TE) return;
    if (I2S3_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 SAI3_CLK register locations
    CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI3_CLK_SEL_MASK))
           | CCM_CSCMR1_SAI3_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4,
    CCM_CS2CDR = (CCM_CS2CDR & ~(CCM_CS1CDR_SAI3_CLK_PRED_MASK | CCM_CS1CDR_SAI3_CLK_PODF_MASK))
           | CCM_CS1CDR_SAI3_CLK_PRED(n1-1)
           | CCM_CS1CDR_SAI3_CLK_PODF(n2-1);
    IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI3_MCLK3_SEL_MASK))
            | (IOMUXC_GPR_GPR1_SAI3_MCLK_DIR | IOMUXC_GPR_GPR1_SAI3_MCLK3_SEL(0));    //Select MCLK

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_04 = 8;  //SD_B1_04, 2=SAI3_MCLK,                page 550
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03 = 8;  //SD_B1_03, 2=SAI3_TX_BCLK, page 549
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02 = 8;  //SD_B1_02, 2=SAI3_TX_SYNC, page 548

    int rsync = 1;
    int tsync = 0;

    I2S3_TMR = 0;
    //I2S3_TCSR = (1<<25); //Reset
    I2S3_TCR1 = I2S_TCR1_RFW(1);
    I2S3_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
        | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
    I2S3_TCR3 = I2S_TCR3_TCE;
    I2S3_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
        | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
    I2S3_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));

    I2S3_RMR = 0;
    //I2S3_RCSR = (1<<25); //Reset
    I2S3_RCR1 = I2S_RCR1_RFW(1);
    I2S3_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP  // sync=0; rx is async;
        | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
    I2S3_RCR3 = I2S_RCR3_RCE;
    I2S3_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
        | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    I2S3_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));

}

/******************************************************************/

void AudioOutputI2S3slave::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    //pinMode(2, OUTPUT);
    block_left_1st = NULL;
    block_right_1st = NULL;

    config_i2s();

    CORE_PIN2_CONFIG  = 2;  //2:TX_DATA0

    dma.TCD->SADDR = i2s3_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(i2s3_tx_buffer);
    dma.TCD->DOFF = 0;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->DLASTSGA = 0;
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.TCD->DADDR = (void *)((uint32_t)&I2S3_TDR0 + 2);
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_TX);
    dma.enable();

    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR;

    update_responsibility = update_setup();
    dma.attachInterrupt(isr);

}

void AudioOutputI2S3slave::config_i2s(void)
{

    CCM_CCGR5 |= CCM_CCGR5_SAI3(CCM_CCGR_ON);

    if (I2S3_TCSR & I2S_TCSR_TE) return;
    if (I2S3_RCSR & I2S_RCSR_RE) return;
    

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03  = 8;  //8: SAI3_TX_BCLK
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02  = 8;  //8: SAI3_TX_SYNC
    IOMUXC_SAI3_IPP_IND_SAI_RXBCLK_SELECT_INPUT = 1;    // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03
    IOMUXC_SAI3_IPP_IND_SAI_RXSYNC_SELECT_INPUT = 1; // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02
    
    int rsync = 1;
    int tsync = 0;
    uint32_t noBits = 32;
    uint32_t noBitsM1=noBits-1;

    // configure transmitter
    I2S3_TMR = 0;
    I2S3_TCR1 = I2S_TCR1_RFW(1);  // watermark at half fifo size
    I2S3_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP;
    I2S3_TCR3 = I2S_TCR3_TCE;
    
    I2S3_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(noBitsM1) | I2S_TCR4_MF
            | I2S_TCR4_FSE | I2S_TCR4_FSP;
    I2S3_TCR5 = I2S_TCR5_WNW(noBitsM1) | I2S_TCR5_W0W(noBitsM1) | I2S_TCR5_FBT(31);

    // configure receiver
    I2S3_RMR = 0;
    I2S3_RCR1 = I2S_RCR1_RFW(1);
    I2S3_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_TCR2_BCP;
    I2S3_RCR3 = I2S_RCR3_RCE;

    
    I2S3_RCR4 = I2S_RCR4_FRSZ(1)
                | I2S_RCR4_SYWD(noBitsM1)
                | I2S_RCR4_MF
                | I2S_RCR4_FSE
                | I2S_RCR4_FSP;
    I2S3_RCR5 = I2S_RCR5_WNW(noBitsM1)
                | I2S_RCR5_W0W(noBitsM1)
                | I2S_RCR5_FBT(31);
    
}

#endif //defined(__IMXRT1062__)

output_i2s3.h
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.
 */

#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
#ifndef output_i2s3_h_
#define output_i2s3_h_

#include <Arduino.h>     // github.com/PaulStoffregen/cores/blob/master/teensy4/Arduino.h
#include <AudioStream.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/AudioStream.h
#include <DMAChannel.h>  // github.com/PaulStoffregen/cores/blob/master/teensy4/DMAChannel.h


class AudioOutputI2S3 : public AudioStream
{
public:
    AudioOutputI2S3(void) : AudioStream(2, inputQueueArray) { begin(); }
    virtual void update(void);
    void begin(void);
    friend class AudioInputI2S3;
protected:
    AudioOutputI2S3(int dummy): AudioStream(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !!
    static void config_i2s(void);
    static audio_block_t *block_left_1st;
    static audio_block_t *block_right_1st;
    static bool update_responsibility;
    static DMAChannel dma;
    static void isr(void);
private:
    static audio_block_t *block_left_2nd;
    static audio_block_t *block_right_2nd;
    static uint16_t block_left_offset;
    static uint16_t block_right_offset;
    audio_block_t *inputQueueArray[2];
};


class AudioOutputI2S3slave : public AudioOutputI2S3
{
public:
    AudioOutputI2S3slave(void) : AudioOutputI2S3(0) { begin(); } ;
    void begin(void);
    friend class AudioInputI2S3slave;
    friend void dma_ch0_isr(void);
protected:
    static void config_i2s(void);
};


#endif
#endif //defined(__IMXRT1062__)
 
Last edited:
Library mods look OK, though of course hard to tell without hardware… Your test code is incomplete, of course … the classic error here is to forget AudioMemory(<enough>) in setup(); 10 is usually <enough> for simple audio not using TDM etc.
 
Thanks for these pointers, I've changed that as well but still no audio. Maybe it's some other factor. Or other register that does not have the name to search for. I have now also created:
input_i2s3.cpp
input_i2s3.h

I attach them below, same mods to them as well. But I am not using microphone so it wont matter I guess.

input_i2s3.cpp
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.
 */


#if defined(__IMXRT1062__)
#include <Arduino.h>
#include "input_i2s3.h"
#include "output_i2s3.h"

DMAMEM __attribute__((aligned(32))) static uint32_t i2s3_rx_buffer[AUDIO_BLOCK_SAMPLES];
audio_block_t * AudioInputI2S3::block_left = NULL;
audio_block_t * AudioInputI2S3::block_right = NULL;
uint16_t AudioInputI2S3::block_offset = 0;
bool AudioInputI2S3::update_responsibility = false;
DMAChannel AudioInputI2S3::dma(false);


void AudioInputI2S3::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    //block_left_1st = NULL;
    //block_right_1st = NULL;

    // TODO: should we set & clear the I2S_RCSR_SR bit here?
    AudioOutputI2S3::config_i2s();

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_00 = 8; //SD_B1_00, 8=SAI3_RX_DATA, page 546
    IOMUXC_SAI3_IPP_IND_SAI_RXDATA_SELECT_INPUT_0 = 1; // 1=GPIO_SD_B1_00_ALT8, page 942

    dma.TCD->SADDR = (void *)((uint32_t)&I2S3_RDR0+2);
    dma.TCD->SOFF = 0;
    dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
    dma.TCD->NBYTES_MLNO = 2;
    dma.TCD->SLAST = 0;
    dma.TCD->DADDR = i2s3_rx_buffer;
    dma.TCD->DOFF = 2;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_rx_buffer) / 2;
    dma.TCD->DLASTSGA = -sizeof(i2s3_rx_buffer);
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_rx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_RX);
    dma.enable();

    I2S3_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; // page 2099
    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // page 2087

    update_responsibility = update_setup();
    dma.attachInterrupt(isr);
}

void AudioInputI2S3::isr(void)
{
    uint32_t daddr, offset;
    const int16_t *src, *end;
    int16_t *dest_left, *dest_right;
    audio_block_t *left, *right;

    daddr = (uint32_t)(dma.TCD->DADDR);
    dma.clearInterrupt();

    if (daddr < (uint32_t)i2s3_rx_buffer + sizeof(i2s3_rx_buffer) / 2) {
        // DMA is receiving to the first half of the buffer
        // need to remove data from the second half
        src = (int16_t *)&i2s3_rx_buffer[AUDIO_BLOCK_SAMPLES/2];
        end = (int16_t *)&i2s3_rx_buffer[AUDIO_BLOCK_SAMPLES];
        if (AudioInputI2S3::update_responsibility) AudioStream::update_all();
    } else {
        // DMA is receiving to the second half of the buffer
        // need to remove data from the first half
        src = (int16_t *)&i2s3_rx_buffer[0];
        end = (int16_t *)&i2s3_rx_buffer[AUDIO_BLOCK_SAMPLES/2];
    }
    left = AudioInputI2S3::block_left;
    right = AudioInputI2S3::block_right;
    if (left != NULL && right != NULL) {
        offset = AudioInputI2S3::block_offset;
        if (offset <= AUDIO_BLOCK_SAMPLES/2) {
            dest_left = &(left->data[offset]);
            dest_right = &(right->data[offset]);
            AudioInputI2S3::block_offset = offset + AUDIO_BLOCK_SAMPLES/2;

            arm_dcache_delete((void*)src, sizeof(i2s3_rx_buffer) / 2);

            do {
                *dest_left++ = *src++;
                *dest_right++ = *src++;
            } while (src < end);
        }
    }
}



void AudioInputI2S3::update(void)
{
    audio_block_t *new_left=NULL, *new_right=NULL, *out_left=NULL, *out_right=NULL;

    // allocate 2 new blocks, but if one fails, allocate neither
    new_left = allocate();
    if (new_left != NULL) {
        new_right = allocate();
        if (new_right == NULL) {
            release(new_left);
            new_left = NULL;
        }
    }
    __disable_irq();
    if (block_offset >= AUDIO_BLOCK_SAMPLES) {
        // the DMA filled 2 blocks, so grab them and get the
        // 2 new blocks to the DMA, as quickly as possible
        out_left = block_left;
        block_left = new_left;
        out_right = block_right;
        block_right = new_right;
        block_offset = 0;
        __enable_irq();
        // then transmit the DMA's former blocks
        transmit(out_left, 0);
        release(out_left);
        transmit(out_right, 1);
        release(out_right);
        //Serial.print(".");
    } else if (new_left != NULL) {
        // the DMA didn't fill blocks, but we allocated blocks
        if (block_left == NULL) {
            // the DMA doesn't have any blocks to fill, so
            // give it the ones we just allocated
            block_left = new_left;
            block_right = new_right;
            block_offset = 0;
            __enable_irq();
        } else {
            // the DMA already has blocks, doesn't need these
            __enable_irq();
            release(new_left);
            release(new_right);
        }
    } else {
        // The DMA didn't fill blocks, and we could not allocate
        // memory... the system is likely starving for memory!
        // Sadly, there's nothing we can do.
        __enable_irq();
    }
}


/******************************************************************/


void AudioInputI2S3slave::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    //block_left_1st = NULL;
    //block_right_1st = NULL;

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_00 = 8;  //SD_B1_00, 8=SAI3_RX_DATA, page 546
    IOMUXC_SAI3_IPP_IND_SAI_RXDATA_SELECT_INPUT_0 = 1; // 1=GPIO_SD_B1_00_ALT8, page 942

    AudioOutputI2S3slave::config_i2s();


    dma.TCD->SADDR = (void *)((uint32_t)&I2S3_RDR0+2);
    dma.TCD->SOFF = 0;
    dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
    dma.TCD->NBYTES_MLNO = 2;
    dma.TCD->SLAST = 0;
    dma.TCD->DADDR = i2s3_rx_buffer;
    dma.TCD->DOFF = 2;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_rx_buffer) / 2;
    dma.TCD->DLASTSGA = -sizeof(i2s3_rx_buffer);
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_rx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_RX);
    dma.enable();
    
    
    I2S3_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; // page 2099
    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // page 2087
    update_responsibility = update_setup();
    dma.attachInterrupt(isr);

}
#endif

input_i2s3.h
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.
 */

#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
#ifndef _input_i2s3_h_
#define _input_i2s3_h_

#include <Arduino.h>     // github.com/PaulStoffregen/cores/blob/master/teensy4/Arduino.h
#include <AudioStream.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/AudioStream.h
#include <DMAChannel.h>  // github.com/PaulStoffregen/cores/blob/master/teensy4/DMAChannel.h

class AudioInputI2S3 : public AudioStream
{
public:
    AudioInputI2S3(void) : AudioStream(0, NULL) { begin(); }
    virtual void update(void);
    void begin(void);
protected:
    AudioInputI2S3(int dummy): AudioStream(0, NULL) {} // to be used only inside AudioInputI2Sslave !!
    static bool update_responsibility;
    static DMAChannel dma;
    static void isr(void);
private:
    static audio_block_t *block_left;
    static audio_block_t *block_right;
    static uint16_t block_offset;
};


class AudioInputI2S3slave : public AudioInputI2S3
{
public:
    AudioInputI2S3slave(void) : AudioInputI2S3(0) { begin(); }
    void begin(void);
    friend void dma_ch1_isr(void);
};

#endif
#endif //#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
 
Getting a bit late here in the UK … then again, maybe sleep will cause inspiration! If I get a chance will look in more detail tomorrow, but time will be limited, family visiting.

If this proves stubborn, do you have a way to get some hardware to me for testing?
 
Getting a bit late here in the UK … then again, maybe sleep will cause inspiration! If I get a chance will look in more detail tomorrow, but time will be limited, family visiting.

If this proves stubborn, do you have a way to get some hardware to me for testing?
If it proves stubborn, yes I could provide hardware. Thanks for checking, I hope this will also help others making custom stuff in the future. :)
 
You don't seem to be setting the selector registers (*_SELECT_INPUT_0) for all the pins? So some of them will still be trying to use the EMC_* pins instead of SD_B1_*.

Setting the pre and post divider for the SAI3 clock needs to be done in CCM_CS1CDR, not CCM_CS2CDR.
 
You don't seem to be setting the selector registers (*_SELECT_INPUT_0) for all the pins? So some of them will still be trying to use the EMC_* pins instead of SD_B1_*.

Setting the pre and post divider for the SAI3 clock needs to be done in CCM_CS1CDR, not CCM_CS2CDR.

IT WORKS!!! The CCM_CS1CDR was the last caveat!! 😁
 
Last edited:
...: SOLUTION :...
Thanks to the good people of this community SAI3 for Audio is possible! 👏

SAI3 can be used by using the files below for Audio Output. Here's how to do it.

NOTE: This uses the following pins for SAI3 Audio Output.
SD_B1_01: SAI3_TX_DATA
SD_B1_02: SAI3_TX_SYNC
SD_B1_03: SAI3_TX_BCLK


This works very well together with the MAX98357 3W amplifier.

1. Open Audio.h and add:
Code:
#include "output_i2s3.h"
into the list of includes. There's a long list, you can't miss it.

2. Download these two files below and put in the same folder as Audio.h.

output_i2s3.cpp

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.
 */
#if defined(__IMXRT1062__)
#include <Arduino.h>
#include "output_i2s3.h"
#include "memcpy_audio.h"
#include "utility/imxrt_hw.h"

audio_block_t * AudioOutputI2S3::block_left_1st = NULL;
audio_block_t * AudioOutputI2S3::block_right_1st = NULL;
audio_block_t * AudioOutputI2S3::block_left_2nd = NULL;
audio_block_t * AudioOutputI2S3::block_right_2nd = NULL;
uint16_t  AudioOutputI2S3::block_left_offset = 0;
uint16_t  AudioOutputI2S3::block_right_offset = 0;
bool AudioOutputI2S3::update_responsibility = false;
DMAChannel AudioOutputI2S3::dma(false);
DMAMEM __attribute__((aligned(32))) static uint32_t i2s3_tx_buffer[AUDIO_BLOCK_SAMPLES];

#include "utility/imxrt_hw.h"

void AudioOutputI2S3::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    block_left_1st = NULL;
    block_right_1st = NULL;

    config_i2s();

    // if AudioInputI2S3 set I2S_TCSR_TE (for clock sync), disable it
    I2S3_TCSR = 0;
    while (I2S3_TCSR & I2S_TCSR_TE) ; //wait for transmit disabled

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_01  = 8;  //SD_B1_01, 8=SAI3_TX_DATA, page 547

    dma.TCD->SADDR = i2s3_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(i2s3_tx_buffer);
    dma.TCD->DOFF = 0;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->DLASTSGA = 0;
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.TCD->DADDR = (void *)((uint32_t)&I2S3_TDR0 + 2);
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_TX);
    dma.enable();

    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR;

    update_responsibility = update_setup();
    dma.attachInterrupt(isr);
}

void AudioOutputI2S3::isr(void)
{
    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)i2s3_tx_buffer + sizeof(i2s3_tx_buffer) / 2) {
        // DMA is transmitting the first half of the buffer
        // so we must fill the second half
        dest = (int16_t *)&i2s3_tx_buffer[AUDIO_BLOCK_SAMPLES/2];
        if (AudioOutputI2S3::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 *)i2s3_tx_buffer;
    }

    blockL = AudioOutputI2S3::block_left_1st;
    blockR = AudioOutputI2S3::block_right_1st;
    offsetL = AudioOutputI2S3::block_left_offset;
    offsetR = AudioOutputI2S3::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);    
    }

    #if IMXRT_CACHE_ENABLED >= 2    
    arm_dcache_flush_delete(dest, sizeof(i2s3_tx_buffer) / 2 );
    #endif
 
    if (offsetL < AUDIO_BLOCK_SAMPLES) {
        AudioOutputI2S3::block_left_offset = offsetL;
    } else {
        AudioOutputI2S3::block_left_offset = 0;
        AudioStream::release(blockL);
        AudioOutputI2S3::block_left_1st = AudioOutputI2S3::block_left_2nd;
        AudioOutputI2S3::block_left_2nd = NULL;
    }
    if (offsetR < AUDIO_BLOCK_SAMPLES) {
        AudioOutputI2S3::block_right_offset = offsetR;
    } else {
        AudioOutputI2S3::block_right_offset = 0;
        AudioStream::release(blockR);
        AudioOutputI2S3::block_right_1st = AudioOutputI2S3::block_right_2nd;
        AudioOutputI2S3::block_right_2nd = NULL;
    }
}




void AudioOutputI2S3::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);
        }
    }
}

void AudioOutputI2S3::config_i2s(void)
{
    CCM_CCGR5 |= CCM_CCGR5_SAI3(CCM_CCGR_ON);

    // if either transmitter or receiver is enabled, do nothing
    if (I2S3_TCSR & I2S_TCSR_TE) return;
    if (I2S3_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 SAI3_CLK register locations
    CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI3_CLK_SEL_MASK))
           | CCM_CSCMR1_SAI3_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4,
    CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI3_CLK_PRED_MASK | CCM_CS1CDR_SAI3_CLK_PODF_MASK))
           | CCM_CS1CDR_SAI3_CLK_PRED(n1-1)
           | CCM_CS1CDR_SAI3_CLK_PODF(n2-1);
    IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI3_MCLK3_SEL_MASK))
            | (IOMUXC_GPR_GPR1_SAI3_MCLK_DIR | IOMUXC_GPR_GPR1_SAI3_MCLK3_SEL(0));    //Select MCLK

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_04 = 8;  //SD_B1_04, 2=SAI3_MCLK,                page 550
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03 = 8;  //SD_B1_03, 2=SAI3_TX_BCLK, page 549
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02 = 8;  //SD_B1_02, 2=SAI3_TX_SYNC, page 548

    int rsync = 1;
    int tsync = 0;

    I2S3_TMR = 0;
    //I2S3_TCSR = (1<<25); //Reset
    I2S3_TCR1 = I2S_TCR1_RFW(1);
    I2S3_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
        | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
    I2S3_TCR3 = I2S_TCR3_TCE;
    I2S3_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
        | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
    I2S3_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));

    I2S3_RMR = 0;
    //I2S3_RCSR = (1<<25); //Reset
    I2S3_RCR1 = I2S_RCR1_RFW(1);
    I2S3_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP  // sync=0; rx is async;
        | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
    I2S3_RCR3 = I2S_RCR3_RCE;
    I2S3_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
        | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    I2S3_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));

}

/******************************************************************/

void AudioOutputI2S3slave::begin(void)
{
    dma.begin(true); // Allocate the DMA channel first

    //pinMode(2, OUTPUT);
    block_left_1st = NULL;
    block_right_1st = NULL;

    config_i2s();

    CORE_PIN2_CONFIG  = 2;  //2:TX_DATA0

    dma.TCD->SADDR = i2s3_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(i2s3_tx_buffer);
    dma.TCD->DOFF = 0;
    dma.TCD->CITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->DLASTSGA = 0;
    dma.TCD->BITER_ELINKNO = sizeof(i2s3_tx_buffer) / 2;
    dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    dma.TCD->DADDR = (void *)((uint32_t)&I2S3_TDR0 + 2);
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI3_TX);
    dma.enable();

    I2S3_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR;

    update_responsibility = update_setup();
    dma.attachInterrupt(isr);

}

void AudioOutputI2S3slave::config_i2s(void)
{

    CCM_CCGR5 |= CCM_CCGR5_SAI3(CCM_CCGR_ON);

    if (I2S3_TCSR & I2S_TCSR_TE) return;
    if (I2S3_RCSR & I2S_RCSR_RE) return;
 

    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03  = 8;  //8: SAI3_TX_BCLK
    IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02  = 8;  //8: SAI3_TX_SYNC
    IOMUXC_SAI3_IPP_IND_SAI_RXBCLK_SELECT_INPUT = 1;    // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_03
    IOMUXC_SAI3_IPP_IND_SAI_RXSYNC_SELECT_INPUT = 1; // IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_02
 
    int rsync = 1;
    int tsync = 0;
    uint32_t noBits = 32;
    uint32_t noBitsM1=noBits-1;

    // configure transmitter
    I2S3_TMR = 0;
    I2S3_TCR1 = I2S_TCR1_RFW(1);  // watermark at half fifo size
    I2S3_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP;
    I2S3_TCR3 = I2S_TCR3_TCE;
 
    I2S3_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(noBitsM1) | I2S_TCR4_MF
            | I2S_TCR4_FSE | I2S_TCR4_FSP;
    I2S3_TCR5 = I2S_TCR5_WNW(noBitsM1) | I2S_TCR5_W0W(noBitsM1) | I2S_TCR5_FBT(31);

    // configure receiver
    I2S3_RMR = 0;
    I2S3_RCR1 = I2S_RCR1_RFW(1);
    I2S3_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_TCR2_BCP;
    I2S3_RCR3 = I2S_RCR3_RCE;

 
    I2S3_RCR4 = I2S_RCR4_FRSZ(1)
                | I2S_RCR4_SYWD(noBitsM1)
                | I2S_RCR4_MF
                | I2S_RCR4_FSE
                | I2S_RCR4_FSP;
    I2S3_RCR5 = I2S_RCR5_WNW(noBitsM1)
                | I2S_RCR5_W0W(noBitsM1)
                | I2S_RCR5_FBT(31);
 
}

#endif //defined(__IMXRT1062__)

output_i2s3.h
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.
 */

#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
#ifndef output_i2s3_h_
#define output_i2s3_h_

#include <Arduino.h>     // github.com/PaulStoffregen/cores/blob/master/teensy4/Arduino.h
#include <AudioStream.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/AudioStream.h
#include <DMAChannel.h>  // github.com/PaulStoffregen/cores/blob/master/teensy4/DMAChannel.h


class AudioOutputI2S3 : public AudioStream
{
public:
    AudioOutputI2S3(void) : AudioStream(2, inputQueueArray) { begin(); }
    virtual void update(void);
    void begin(void);
    friend class AudioInputI2S3;
protected:
    AudioOutputI2S3(int dummy): AudioStream(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !!
    static void config_i2s(void);
    static audio_block_t *block_left_1st;
    static audio_block_t *block_right_1st;
    static bool update_responsibility;
    static DMAChannel dma;
    static void isr(void);
private:
    static audio_block_t *block_left_2nd;
    static audio_block_t *block_right_2nd;
    static uint16_t block_left_offset;
    static uint16_t block_right_offset;
    audio_block_t *inputQueueArray[2];
};


class AudioOutputI2S3slave : public AudioOutputI2S3
{
public:
    AudioOutputI2S3slave(void) : AudioOutputI2S3(0) { begin(); } ;
    void begin(void);
    friend class AudioInputI2S3slave;
    friend void dma_ch0_isr(void);
protected:
    static void config_i2s(void);
};


#endif
#endif //defined(__IMXRT1062__)
 
Back
Top