Teensy LC AudioOutputI2S(+slave)dds
| AudioOutputI2S audioOutput; | AudioOutputI2S audioOutput; | ||||
| //AudioOutputSPDIF audioOutput; | //AudioOutputSPDIF audioOutput; | ||||
| //AudioOutputAnalog audioOutput; | //AudioOutputAnalog audioOutput; | ||||
| //On Teensy LC, use this for the Teensy Audio Shield: | |||||
| //AudioOutputI2Sslave audioOutput; | |||||
| AudioConnection patchCord1(playWav1, 0, audioOutput, 0); | AudioConnection patchCord1(playWav1, 0, audioOutput, 0); | ||||
| AudioConnection patchCord2(playWav1, 1, audioOutput, 1); | AudioConnection patchCord2(playWav1, 1, audioOutput, 1); | ||||
| AudioControlSGTL5000 sgtl5000_1; | AudioControlSGTL5000 sgtl5000_1; | ||||
| playWav1.play(filename); | playWav1.play(filename); | ||||
| // A brief delay for the library read WAV info | // A brief delay for the library read WAV info | ||||
| delay(5); | |||||
| delay(25); | |||||
| // Simply wait for the file to finish playing. | // Simply wait for the file to finish playing. | ||||
| while (playWav1.isPlaying()) { | while (playWav1.isPlaying()) { |
| */ | */ | ||||
| #include <Arduino.h> | |||||
| #include "input_i2s.h" | #include "input_i2s.h" | ||||
| #include "output_i2s.h" | #include "output_i2s.h" | ||||
| #if !defined(KINETISL) | |||||
| DMAMEM __attribute__((aligned(32))) static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; | DMAMEM __attribute__((aligned(32))) static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; | ||||
| audio_block_t * AudioInputI2S::block_left = NULL; | audio_block_t * AudioInputI2S::block_left = NULL; | ||||
| audio_block_t * AudioInputI2S::block_right = NULL; | audio_block_t * AudioInputI2S::block_right = NULL; | ||||
| { | { | ||||
| dma.begin(true); // Allocate the DMA channel first | dma.begin(true); // Allocate the DMA channel first | ||||
| //block_left_1st = NULL; | |||||
| //block_right_1st = NULL; | |||||
| AudioOutputI2Sslave::config_i2s(); | AudioOutputI2Sslave::config_i2s(); | ||||
| #if defined(KINETISK) | #if defined(KINETISK) | ||||
| CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | ||||
| #endif | #endif | ||||
| } | } | ||||
| #elif defined(KINETISL) | |||||
| /************************************************************************************** | |||||
| * Teensy LC | |||||
| ***************************************************************************************/ | |||||
| #define NUM_SAMPLES (AUDIO_BLOCK_SAMPLES / 2) | |||||
| DMAMEM static int16_t i2s_rx_buffer1[NUM_SAMPLES * 2]; | |||||
| DMAMEM static int16_t i2s_rx_buffer2[NUM_SAMPLES * 2]; | |||||
| audio_block_t * AudioInputI2S::block_left = NULL; | |||||
| audio_block_t * AudioInputI2S::block_right = NULL; | |||||
| DMAChannel AudioInputI2S::dma1(false); | |||||
| DMAChannel AudioInputI2S::dma2(false); | |||||
| bool AudioInputI2S::update_responsibility = false; | |||||
| void AudioInputI2S::begin(void) | |||||
| { | |||||
| memset(i2s_rx_buffer1, 0, sizeof( i2s_rx_buffer1 ) ); | |||||
| memset(i2s_rx_buffer2, 0, sizeof( i2s_rx_buffer2 ) ); | |||||
| dma1.begin(true); | |||||
| dma2.begin(true); | |||||
| AudioOutputI2S::config_i2s(); | |||||
| CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | |||||
| //configure both DMA channels | |||||
| dma1.CFG->SAR = (void *)((uint32_t)&I2S0_RDR0 + 2); | |||||
| dma1.CFG->DCR = (dma1.CFG->DCR & 0xF08E0FFF) | DMA_DCR_SSIZE(2); | |||||
| dma1.destinationBuffer(i2s_rx_buffer1, sizeof(i2s_rx_buffer1)); | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); | |||||
| dma1.interruptAtCompletion(); | |||||
| dma1.disableOnCompletion(); | |||||
| dma1.attachInterrupt(isr1); | |||||
| dma2.CFG->SAR = dma1.CFG->SAR; | |||||
| dma2.CFG->DCR = dma1.CFG->DCR; | |||||
| dma2.destinationBuffer(i2s_rx_buffer2, sizeof(i2s_rx_buffer2)); | |||||
| dma2.interruptAtCompletion(); | |||||
| dma2.disableOnCompletion(); | |||||
| dma2.attachInterrupt(isr2); | |||||
| I2S0_RCSR = 0; | |||||
| I2S0_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FWDE | I2S_RCSR_FR; | |||||
| I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // TX clock enable, because sync'd to TX | |||||
| update_responsibility = update_setup(); | |||||
| dma1.enable(); | |||||
| } | |||||
| void AudioInputI2S::update(void) | |||||
| { | |||||
| //Keep it simple | |||||
| //If we have a block, transmit and release it. | |||||
| if (block_left) { | |||||
| transmit(block_left, 0); | |||||
| release(block_left); | |||||
| block_left = nullptr; | |||||
| } | |||||
| if (block_right) { | |||||
| transmit(block_right, 1); | |||||
| release(block_right); | |||||
| block_right = nullptr; | |||||
| } | |||||
| // allocate 2 new blocks, but if one fails, allocate neither | |||||
| block_left = allocate(); | |||||
| if (block_left != nullptr) { | |||||
| block_right = allocate(); | |||||
| if (block_right == nullptr) { | |||||
| release(block_left); | |||||
| block_left = nullptr; | |||||
| } | |||||
| } | |||||
| } | |||||
| //todo : ("unroll-loops") or optimize better | |||||
| inline __attribute__((always_inline, hot, optimize("O2") )) | |||||
| static void deinterleave(const int16_t *src,audio_block_t *block_left, audio_block_t *block_right, const unsigned offset) | |||||
| { | |||||
| //we can assume that we have either two blocks or none | |||||
| if (!block_left) return; | |||||
| for (unsigned i=0; i < NUM_SAMPLES; i++) { | |||||
| block_left->data[i + offset] = src[i*2]; | |||||
| block_right->data[i + offset] = src[i*2+1]; | |||||
| } | |||||
| } | |||||
| void AudioInputI2S::isr1(void) | |||||
| { | |||||
| //DMA Channel 1 Interrupt | |||||
| //Start Channel 2: | |||||
| dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); | |||||
| dma2.enable(); | |||||
| //Reset & Copy Data Channel 1 | |||||
| dma1.clearInterrupt(); | |||||
| dma1.destinationBuffer(i2s_rx_buffer1, sizeof(i2s_rx_buffer1)); | |||||
| deinterleave(&i2s_rx_buffer1[0], AudioInputI2S::block_left, AudioInputI2S::block_right, 0); | |||||
| } | |||||
| void AudioInputI2S::isr2(void) | |||||
| { | |||||
| //DMA Channel 2 Interrupt | |||||
| //Start Channel 1: | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); | |||||
| dma1.enable(); | |||||
| //Reset & Copy Data Channel 2 | |||||
| dma2.clearInterrupt(); | |||||
| dma2.destinationBuffer(i2s_rx_buffer2, sizeof(i2s_rx_buffer2)); | |||||
| deinterleave(&i2s_rx_buffer2[0], AudioInputI2S::block_left, AudioInputI2S::block_right, NUM_SAMPLES); | |||||
| if (AudioInputI2S::update_responsibility) AudioStream::update_all(); | |||||
| } | |||||
| void AudioInputI2Sslave::begin(void) | |||||
| { | |||||
| memset(i2s_rx_buffer1, 0, sizeof( i2s_rx_buffer1 ) ); | |||||
| memset(i2s_rx_buffer2, 0, sizeof( i2s_rx_buffer2 ) ); | |||||
| dma1.begin(true); | |||||
| dma2.begin(true); | |||||
| AudioOutputI2Sslave::config_i2s(); | |||||
| CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | |||||
| //configure both DMA channels | |||||
| dma1.CFG->SAR = (void *)((uint32_t)&I2S0_RDR0 + 2); | |||||
| dma1.CFG->DCR = (dma1.CFG->DCR & 0xF08E0FFF) | DMA_DCR_SSIZE(2); | |||||
| dma1.destinationBuffer(i2s_rx_buffer1, sizeof(i2s_rx_buffer1)); | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); | |||||
| dma1.interruptAtCompletion(); | |||||
| dma1.disableOnCompletion(); | |||||
| dma1.attachInterrupt(isr1); | |||||
| dma2.CFG->SAR = dma1.CFG->SAR; | |||||
| dma2.CFG->DCR = dma1.CFG->DCR; | |||||
| dma2.destinationBuffer(i2s_rx_buffer2, sizeof(i2s_rx_buffer2)); | |||||
| dma2.interruptAtCompletion(); | |||||
| dma2.disableOnCompletion(); | |||||
| dma2.attachInterrupt(isr2); | |||||
| I2S0_RCSR = 0; | |||||
| I2S0_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FWDE | I2S_RCSR_FR; | |||||
| update_responsibility = update_setup(); | |||||
| dma1.enable(); | |||||
| } | |||||
| #endif |
| #ifndef _input_i2s_h_ | #ifndef _input_i2s_h_ | ||||
| #define _input_i2s_h_ | #define _input_i2s_h_ | ||||
| #include "Arduino.h" | |||||
| #include "AudioStream.h" | |||||
| #include "DMAChannel.h" | |||||
| #include <Arduino.h> | |||||
| #include <AudioStream.h> | |||||
| #include <DMAChannel.h> | |||||
| class AudioInputI2S : public AudioStream | class AudioInputI2S : public AudioStream | ||||
| { | { | ||||
| protected: | protected: | ||||
| AudioInputI2S(int dummy): AudioStream(0, NULL) {} // to be used only inside AudioInputI2Sslave !! | AudioInputI2S(int dummy): AudioStream(0, NULL) {} // to be used only inside AudioInputI2Sslave !! | ||||
| static bool update_responsibility; | static bool update_responsibility; | ||||
| #if !defined(KINETISL) | |||||
| static DMAChannel dma; | static DMAChannel dma; | ||||
| static void isr(void); | static void isr(void); | ||||
| #else | |||||
| static DMAChannel dma1, dma2; | |||||
| static void isr1(void); | |||||
| static void isr2(void); | |||||
| #endif | |||||
| private: | private: | ||||
| static audio_block_t *block_left; | static audio_block_t *block_left; | ||||
| static audio_block_t *block_right; | static audio_block_t *block_right; | ||||
| #if !defined(KINETISL) | |||||
| static uint16_t block_offset; | static uint16_t block_offset; | ||||
| #endif | |||||
| }; | }; | ||||
| { | { | ||||
| public: | public: | ||||
| AudioInputI2Sslave(void) : AudioInputI2S(0) { begin(); } | AudioInputI2Sslave(void) : AudioInputI2S(0) { begin(); } | ||||
| void begin(void); | |||||
| friend void dma_ch1_isr(void); | |||||
| void begin(void); | |||||
| }; | }; | ||||
| #endif | |||||
| #endif |
| #include <Arduino.h> | #include <Arduino.h> | ||||
| #include "output_i2s.h" | #include "output_i2s.h" | ||||
| #if !defined(KINETISL) | |||||
| #include "memcpy_audio.h" | #include "memcpy_audio.h" | ||||
| // high-level explanation of how this I2S & DMA code works: | |||||
| // https://forum.pjrc.com/threads/65229?p=263104&viewfull=1#post263104 | |||||
| audio_block_t * AudioOutputI2S::block_left_1st = NULL; | audio_block_t * AudioOutputI2S::block_left_1st = NULL; | ||||
| audio_block_t * AudioOutputI2S::block_right_1st = NULL; | audio_block_t * AudioOutputI2S::block_right_1st = NULL; | ||||
| audio_block_t * AudioOutputI2S::block_left_2nd = NULL; | audio_block_t * AudioOutputI2S::block_left_2nd = NULL; | ||||
| #include "utility/imxrt_hw.h" | #include "utility/imxrt_hw.h" | ||||
| #endif | #endif | ||||
| // high-level explanation of how this I2S & DMA code works: | |||||
| // https://forum.pjrc.com/threads/65229?p=263104&viewfull=1#post263104 | |||||
| void AudioOutputI2S::begin(void) | void AudioOutputI2S::begin(void) | ||||
| { | { | ||||
| dma.begin(true); // Allocate the DMA channel first | dma.begin(true); // Allocate the DMA channel first | ||||
| } | } | ||||
| } | } | ||||
| #if defined(KINETISK) || defined(KINETISL) | |||||
| #if defined(KINETISK) | |||||
| // MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate | // 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 | #if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000 | ||||
| void AudioOutputI2S::config_i2s(void) | void AudioOutputI2S::config_i2s(void) | ||||
| { | { | ||||
| #if defined(KINETISK) || defined(KINETISL) | |||||
| #if defined(KINETISK) | |||||
| SIM_SCGC6 |= SIM_SCGC6_I2S; | SIM_SCGC6 |= SIM_SCGC6_I2S; | ||||
| SIM_SCGC7 |= SIM_SCGC7_DMA; | SIM_SCGC7 |= SIM_SCGC7_DMA; | ||||
| SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | ||||
| #endif | #endif | ||||
| } | } | ||||
| #elif defined(KINETISL) | |||||
| /************************************************************************************** | |||||
| * Teensy LC | |||||
| ***************************************************************************************/ | |||||
| // added jan 2021, Frank Bösing | |||||
| audio_block_t * AudioOutputI2S::block_left = NULL; | |||||
| audio_block_t * AudioOutputI2S::block_right = NULL; | |||||
| bool AudioOutputI2S::update_responsibility = false; | |||||
| #define NUM_SAMPLES (AUDIO_BLOCK_SAMPLES / 2) | |||||
| DMAMEM static int16_t i2s_tx_buffer1[NUM_SAMPLES * 2]; | |||||
| DMAMEM static int16_t i2s_tx_buffer2[NUM_SAMPLES * 2]; | |||||
| DMAChannel AudioOutputI2S::dma1(false); | |||||
| DMAChannel AudioOutputI2S::dma2(false); | |||||
| void AudioOutputI2S::begin(void) | |||||
| { | |||||
| memset(i2s_tx_buffer1, 0, sizeof( i2s_tx_buffer1 ) ); | |||||
| memset(i2s_tx_buffer2, 0, sizeof( i2s_tx_buffer2 ) ); | |||||
| dma1.begin(true); // Allocate the DMA channel first | |||||
| dma2.begin(true); | |||||
| config_i2s(); | |||||
| CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 | |||||
| //configure both DMA channels | |||||
| dma1.sourceBuffer(i2s_tx_buffer1, sizeof(i2s_tx_buffer1)); | |||||
| dma1.CFG->DAR = (void *)((uint32_t)&I2S0_TDR0); | |||||
| dma1.CFG->DCR = (dma1.CFG->DCR & 0xF0F0F0FF) | DMA_DCR_DSIZE(2); | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); | |||||
| dma1.interruptAtCompletion(); | |||||
| dma1.disableOnCompletion(); | |||||
| dma1.attachInterrupt(isr1); | |||||
| dma2.sourceBuffer(i2s_tx_buffer2, sizeof(i2s_tx_buffer2)); | |||||
| dma2.CFG->DAR = dma1.CFG->DAR; | |||||
| dma2.CFG->DCR = dma1.CFG->DCR; | |||||
| dma2.interruptAtCompletion(); | |||||
| dma2.disableOnCompletion(); | |||||
| dma2.attachInterrupt(isr2); | |||||
| update_responsibility = update_setup(); | |||||
| dma1.enable(); | |||||
| I2S0_TCSR = I2S_TCSR_SR; | |||||
| I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FWDE; | |||||
| } | |||||
| void AudioOutputI2S::config_i2s(void) | |||||
| { | |||||
| SIM_SCGC6 |= SIM_SCGC6_I2S;//Enable I2S periphal | |||||
| // enable MCLK, 16MHZ | |||||
| I2S0_MCR = I2S_MCR_MICS(0) | I2S_MCR_MOE; | |||||
| //MDR is not available on Teensy LC | |||||
| // configure transmitter | |||||
| I2S0_TMR = 0; | |||||
| 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); | |||||
| // configure receiver (sync'd to transmitter clocks) | |||||
| I2S0_RMR = 0; | |||||
| I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP; | |||||
| I2S0_RCR3 = I2S_RCR3_RCE; | |||||
| I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; | |||||
| I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); | |||||
| // configure pin mux | |||||
| 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 | |||||
| //No Mclk here - it would be 16MHz | |||||
| //CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK | |||||
| } | |||||
| void AudioOutputI2S::update(void) | |||||
| { | |||||
| if (!block_left) block_left = receiveReadOnly(0);// input 0 = left channel | |||||
| if (!block_right) block_right = receiveReadOnly(1);// input 1 = right channel | |||||
| } | |||||
| inline __attribute__((always_inline, hot)) | |||||
| static void interleave(const int16_t *dest,const audio_block_t *block_left, const audio_block_t *block_right, const size_t offset) | |||||
| { | |||||
| //return; | |||||
| uint32_t *p = (uint32_t*)dest; | |||||
| uint32_t *end = p + NUM_SAMPLES; | |||||
| if (block_left != nullptr && block_right != nullptr) { | |||||
| uint16_t *l = (uint16_t*)&block_left->data[offset]; | |||||
| uint16_t *r = (uint16_t*)&block_right->data[offset]; | |||||
| do { | |||||
| *p++ = (((uint32_t)(*l++)) << 16) | (uint32_t)(*r++); | |||||
| *p++ = (((uint32_t)(*l++)) << 16) | (uint32_t)(*r++); | |||||
| *p++ = (((uint32_t)(*l++)) << 16) | (uint32_t)(*r++); | |||||
| *p++ = (((uint32_t)(*l++)) << 16) | (uint32_t)(*r++); | |||||
| } while (p < end); | |||||
| return; | |||||
| } | |||||
| if (block_left != nullptr) { | |||||
| uint16_t *l = (uint16_t*)&block_left->data[offset]; | |||||
| do { | |||||
| *p++ = (uint32_t)(*l++) << 16; | |||||
| *p++ = (uint32_t)(*l++) << 16; | |||||
| *p++ = (uint32_t)(*l++) << 16; | |||||
| *p++ = (uint32_t)(*l++) << 16; | |||||
| } while (p < end); | |||||
| return; | |||||
| } | |||||
| if (block_right != nullptr) { | |||||
| uint16_t *r = (uint16_t*)&block_right->data[offset]; | |||||
| do { | |||||
| *p++ =(uint32_t)(*r++); | |||||
| *p++ =(uint32_t)(*r++); | |||||
| *p++ =(uint32_t)(*r++); | |||||
| *p++ =(uint32_t)(*r++); | |||||
| } while (p < end); | |||||
| return; | |||||
| } | |||||
| do { | |||||
| *p++ = 0; | |||||
| *p++ = 0; | |||||
| } while (p < end); | |||||
| } | |||||
| void AudioOutputI2S::isr1(void) | |||||
| { //DMA Channel 1 Interrupt | |||||
| //Start Channel 2: | |||||
| dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); | |||||
| dma2.enable(); | |||||
| //Reset & Copy Data Channel 1 | |||||
| dma1.clearInterrupt(); | |||||
| dma1.sourceBuffer(i2s_tx_buffer1, sizeof(i2s_tx_buffer1)); | |||||
| interleave(&i2s_tx_buffer1[0], AudioOutputI2S::block_left, AudioOutputI2S::block_right, 0); | |||||
| } | |||||
| void __attribute__((interrupt("IRQ"))) AudioOutputI2S::isr2(void) | |||||
| { //DMA Channel 2 Interrupt | |||||
| //Start Channel 1: | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); | |||||
| dma1.enable(); | |||||
| //Reset & Copy Data Channel 2 | |||||
| dma2.clearInterrupt(); | |||||
| dma2.sourceBuffer(i2s_tx_buffer2, sizeof(i2s_tx_buffer2)); | |||||
| audio_block_t *block_left = AudioOutputI2S::block_left; | |||||
| audio_block_t *block_right = AudioOutputI2S::block_right; | |||||
| interleave(&i2s_tx_buffer2[0], block_left, block_right, NUM_SAMPLES); | |||||
| if (block_left) AudioStream::release(block_left); | |||||
| if (block_right) AudioStream::release(block_right); | |||||
| AudioOutputI2S::block_left = nullptr; | |||||
| AudioOutputI2S::block_right = nullptr; | |||||
| if (AudioOutputI2S::update_responsibility) AudioStream::update_all(); | |||||
| } | |||||
| void AudioOutputI2Sslave::begin(void) | |||||
| { | |||||
| memset(i2s_tx_buffer1, 0, sizeof( i2s_tx_buffer1 ) ); | |||||
| memset(i2s_tx_buffer2, 0, sizeof( i2s_tx_buffer2 ) ); | |||||
| dma1.begin(true); // Allocate the DMA channels first | |||||
| dma2.begin(true); | |||||
| config_i2s(); | |||||
| CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 | |||||
| //configure both DMA channels | |||||
| dma1.sourceBuffer(i2s_tx_buffer1, sizeof(i2s_tx_buffer1)); | |||||
| dma1.CFG->DAR = (void *)((uint32_t)&I2S0_TDR0 + 2); | |||||
| dma1.CFG->DCR = (dma1.CFG->DCR & 0xF0F0F0FF) | DMA_DCR_DSIZE(2); | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); | |||||
| dma1.interruptAtCompletion(); | |||||
| dma1.disableOnCompletion(); | |||||
| dma1.attachInterrupt(isr1); | |||||
| dma2.sourceBuffer(i2s_tx_buffer2, sizeof(i2s_tx_buffer2)); | |||||
| dma2.CFG->DAR = dma1.CFG->DAR; | |||||
| dma2.CFG->DCR = dma1.CFG->DCR; | |||||
| dma2.interruptAtCompletion(); | |||||
| dma2.disableOnCompletion(); | |||||
| dma2.attachInterrupt(isr2); | |||||
| update_responsibility = update_setup(); | |||||
| dma1.enable(); | |||||
| I2S0_TCSR = I2S_TCSR_SR; | |||||
| I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FWDE; | |||||
| } | |||||
| void AudioOutputI2Sslave::config_i2s(void) | |||||
| { | |||||
| SIM_SCGC6 |= SIM_SCGC6_I2S;//Enable I2S periphal | |||||
| // enable MCLK, 16MHZ | |||||
| I2S0_MCR = I2S_MCR_MICS(1) | I2S_MCR_MOE; | |||||
| //MDR is not available on Teensy LC | |||||
| // configure transmitter | |||||
| I2S0_TMR = 0; | |||||
| 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_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; | |||||
| I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); | |||||
| // configure pin mux | |||||
| 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 !!16MHz!! | |||||
| } | |||||
| #endif | |||||
| #ifndef output_i2s_h_ | #ifndef output_i2s_h_ | ||||
| #define output_i2s_h_ | #define output_i2s_h_ | ||||
| #include "Arduino.h" | |||||
| #include "AudioStream.h" | |||||
| #include "DMAChannel.h" | |||||
| #include <Arduino.h> | |||||
| #include <AudioStream.h> | |||||
| #include <DMAChannel.h> | |||||
| #if !defined(KINETISL) | |||||
| class AudioOutputI2S : public AudioStream | class AudioOutputI2S : public AudioStream | ||||
| { | { | ||||
| static void config_i2s(void); | static void config_i2s(void); | ||||
| }; | }; | ||||
| #elif defined(KINETISL) | |||||
| /************************************************************************************** | |||||
| * Teensy LC | |||||
| ***************************************************************************************/ | |||||
| class AudioOutputI2S : public AudioStream | |||||
| { | |||||
| public: | |||||
| AudioOutputI2S(void) : AudioStream(2, inputQueueArray) { begin(); } | |||||
| virtual void update(void); | |||||
| void begin(void); | |||||
| friend class AudioInputI2S; | |||||
| protected: | |||||
| AudioOutputI2S(int dummy): AudioStream(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! | |||||
| static void config_i2s(void); | |||||
| static audio_block_t *block_left; | |||||
| static audio_block_t *block_right; | |||||
| static bool update_responsibility; | |||||
| static DMAChannel dma1; | |||||
| static DMAChannel dma2; | |||||
| static void isr1(void); | |||||
| static void isr2(void); | |||||
| private: | |||||
| audio_block_t *inputQueueArray[2]; | |||||
| }; | |||||
| class AudioOutputI2Sslave : public AudioOutputI2S | |||||
| { | |||||
| public: | |||||
| AudioOutputI2Sslave(void) : AudioOutputI2S(0) { begin(); } ; | |||||
| void begin(void); | |||||
| friend class AudioInputI2Sslave; | |||||
| friend void dma_ch0_isr(void); | |||||
| friend void dma_ch1_isr(void); | |||||
| protected: | |||||
| static void config_i2s(void); | |||||
| }; | |||||
| #endif | |||||
| #endif | #endif |