From fb18709e2c21bbd334cd59e30150d417fe8ad633 Mon Sep 17 00:00:00 2001 From: kazu Date: Fri, 26 Feb 2021 21:41:28 +0100 Subject: [PATCH] added teensy i2s stuff --- io/teensy/I2S.h | 213 +++++++++++++++++++++++++++++++++++++ io/teensy/I2S2.h | 271 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 io/teensy/I2S.h create mode 100644 io/teensy/I2S2.h diff --git a/io/teensy/I2S.h b/io/teensy/I2S.h new file mode 100644 index 0000000..6f02e39 --- /dev/null +++ b/io/teensy/I2S.h @@ -0,0 +1,213 @@ +// https://github.com/Jean-MarcHarvengt/VGA_t4 +// https://forum.pjrc.com/threads/63243-Writing-Directly-to-SGTL5000-CODEC-DACs +// https://community.nxp.com/t5/Kinetis-Microcontrollers/is-there-any-demo-code-for-using-I2S/m-p/196195/highlight/true +// https://github.com/PaulStoffregen/Audio/blob/99b9472afd24bea13efc799742c0ea432ef2303a/output_i2s.cpp + +#include "../base.h" + +#define USE_DMA + +#ifdef USE_DMA + #include + static DMAChannel dma; +#endif + +#define SAMPLES_PER_BUFFER 1024 +#define NUM_BUFFERS 4 + +static DMAMEM __attribute__((aligned(32))) int16_t audioBuffer[SAMPLES_PER_BUFFER*NUM_BUFFERS]; +static volatile uint8_t freeBuffers = NUM_BUFFERS; +static volatile uint8_t curBuffer = 0; + +template class I2S { + + static constexpr const char* NAME = "I2S"; + + + +public: + + I2S() { + log_d(NAME, "ctor()"); + //config_sai1(); + } + + void start() { + + log_d(NAME, "start()"); + + // clear + //arm_dcache_flush_delete(txBuffer, txBufferSize); // ?? + + config_sai1(); + + #ifdef USE_DMA + dma.begin(true); // Allocate the DMA channel first + dma.TCD->SADDR = audioBuffer; //source address + dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination + dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request/////////////////////////////////////////////////////////////////// + dma.TCD->SLAST = -sizeof(audioBuffer); // last source address adjustment + dma.TCD->DOFF = 0; // increments at destination + dma.TCD->CITER_ELINKNO = sizeof(audioBuffer) / 2; + dma.TCD->DLASTSGA = 0; // destination address offset + dma.TCD->BITER_ELINKNO = sizeof(audioBuffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half complete. SET TO 0 to disable DMA interrupts + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); // I2S1 register DMA writes to + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); // i2s channel that will trigger the DMA transfer when ready for data + dma.enable(); + dma.attachInterrupt(isrDMA); + #else + attachInterruptVector(IRQ_SAI1, isrAudio); + NVIC_ENABLE_IRQ(IRQ_SAI1); + NVIC_SET_PRIORITY(IRQ_SAI1, 127); + I2S1_TCSR |= 1<<8; // start generating TX FIFO interrupts + #endif + + + + pinMode(13, OUTPUT); + + } + + bool add(int16_t* samples) { + #ifdef USE_DMA + if (freeBuffers == 0) {return false;} + --freeBuffers; + int16_t* dst = &audioBuffer[curBuffer * SAMPLES_PER_BUFFER]; + memcpy(dst, samples, SAMPLES_PER_BUFFER*sizeof(int16_t)); + arm_dcache_flush_delete(dst, SAMPLES_PER_BUFFER*sizeof(int16_t)); + curBuffer = (curBuffer + 1) % NUM_BUFFERS; + return true; + #else + //return audioBuffer.add(samples); + #endif + } + + +private: + + //uint16_t pos = 0; + + FASTRUN static void isrDMA() { + dma.clearInterrupt(); + if (freeBuffers < NUM_BUFFERS) {++freeBuffers;} + + } + + /* + // interrupt service routine + FASTRUN static void isrAudio() { + + + static constexpr volatile uint16_t* txReg = (uint16_t *)((uint32_t)&I2S1_TDR0 + 2); + //static constexpr uint16_t* txReg = (uint16_t *)((uint32_t)&I2S1_TDR0 ); + + if (CHANNELS == 2) { + const int16_t sample1 = audioBuffer.getSample(); + *txReg = sample1; + const int16_t sample2 = audioBuffer.getSample(); + *txReg = sample2; + } else if (CHANNELS == 1) { + const int16_t sample1 = audioBuffer.getSample(); + *txReg = sample1; + *txReg = sample1; + } + + //arm_dcache_flush_delete(txBuffer, txBufferSize); + //digitalWriteFast(13, (pos / 2048) & 1); + + } + */ + + +private: + + FLASHMEM static void config_sai1() { + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + double fs = SAMPLE_RATE_HZ; + // 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 = (fs * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + setAudioClock(c0, c1, c2, true); + + + // 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 + + //n1 = n1 / 2; //Double Speed for TDM + + 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 + + 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)); //Select MCLK + + + // configure transmitter + const int rsync = 0; + const int tsync = 1; + + uint16_t by = 32; // ?? + + // configure transmitter + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(1); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); // sync=0; tx is async; + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((by-1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S1_TCR5 = I2S_TCR5_WNW((by-1)) | I2S_TCR5_W0W((by-1)) | I2S_TCR5_FBT((by-1)); + + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); // sync=0; rx is async; + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((by-1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S1_RCR5 = I2S_RCR5_WNW((by-1)) | I2S_RCR5_W0W((by-1)) | I2S_RCR5_FBT((by-1)); + + //CORE_PIN23_CONFIG = 3; // MCLK + CORE_PIN21_CONFIG = 3; // RX_BCLK + CORE_PIN20_CONFIG = 3; // RX_SYNC + CORE_PIN7_CONFIG = 3; // TX_DATA0 + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + + #ifdef USE_DMA + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + #else + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE; + #endif + + } + + FLASHMEM static void setAudioClock(int nfact, int32_t nmult, uint32_t ndiv, bool force) {// sets PLL4 + + if (!force && (CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_ENABLE)) return; + + CCM_ANALOG_PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS | CCM_ANALOG_PLL_AUDIO_ENABLE + | CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2) // 2: 1/4; 1: 1/2; 0: 1/1 + | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(nfact); + + CCM_ANALOG_PLL_AUDIO_NUM = nmult & CCM_ANALOG_PLL_AUDIO_NUM_MASK; + CCM_ANALOG_PLL_AUDIO_DENOM = ndiv & CCM_ANALOG_PLL_AUDIO_DENOM_MASK; + + CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_POWERDOWN;//Switch on PLL + while (!(CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK)) {}; //Wait for pll-lock + + const int div_post_pll = 1; // other values: 2,4 + CCM_ANALOG_MISC2 &= ~(CCM_ANALOG_MISC2_DIV_MSB | CCM_ANALOG_MISC2_DIV_LSB); + if(div_post_pll>1) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_LSB; + if(div_post_pll>3) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_MSB; + + CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_BYPASS;//Disable Bypass + + } + +}; diff --git a/io/teensy/I2S2.h b/io/teensy/I2S2.h new file mode 100644 index 0000000..c0dd027 --- /dev/null +++ b/io/teensy/I2S2.h @@ -0,0 +1,271 @@ +// https://github.com/Jean-MarcHarvengt/VGA_t4 +// https://forum.pjrc.com/threads/63243-Writing-Directly-to-SGTL5000-CODEC-DACs +// https://community.nxp.com/t5/Kinetis-Microcontrollers/is-there-any-demo-code-for-using-I2S/m-p/196195/highlight/true +// https://github.com/PaulStoffregen/Audio/blob/99b9472afd24bea13efc799742c0ea432ef2303a/output_i2s.cpp +#pragma once + +#include "../../Debug.h" + +#define USE_DMA + +//#define SAMPLES_PER_BUFFER 1024 +#define SAMPLES_PER_BUFFER 1152*2 // MP3 +#define NUM_BUFFERS 4 +#define NUM_ENTRIES (SAMPLES_PER_BUFFER * NUM_BUFFERS) + +#ifdef USE_DMA + #include + static DMAChannel dma; + static DMAMEM __attribute__((aligned(32))) int16_t audioBuffer[SAMPLES_PER_BUFFER*NUM_BUFFERS]; + static volatile uint8_t freeBuffers = NUM_BUFFERS; + static volatile uint8_t curBuffer = 0; +#else + static uint16_t bufHead = 0; + static uint16_t bufTail = 0; + static int16_t audioBuffer[NUM_ENTRIES]; + static volatile uint16_t freeEntries = NUM_ENTRIES; +#endif + + + + + +class I2S2 { + +private: + + static constexpr const char* NAME = "I2S2"; + + /** hidden ctor, the class is all static */ + I2S2() {} + + + +public: + + /** start the i2s transmission with the given values */ + static void start(uint8_t channels, uint32_t sampleRate_hz, uint32_t bufferSize) { + + Log::addInfo(NAME, "start(%d, %d, %d, %d)", channels, sampleRate_hz, bufferSize) + + + + config_sai2(); + + #ifdef USE_DMA + dma.begin(true); // Allocate the DMA channel first + dma.TCD->SADDR = audioBuffer; //source address + dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination + dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request/////////////////////////////////////////////////////////////////// + dma.TCD->SLAST = -sizeof(audioBuffer); // last source address adjustment + dma.TCD->DOFF = 0; // increments at destination + dma.TCD->CITER_ELINKNO = sizeof(audioBuffer) / 2; + dma.TCD->DLASTSGA = 0; // destination address offset + dma.TCD->BITER_ELINKNO = sizeof(audioBuffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half complete. SET TO 0 to disable DMA interrupts + dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); // I2S2 register DMA writes to + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); // i2s channel that will trigger the DMA transfer when ready for data + dma.enable(); + dma.attachInterrupt(isrDMA); + #else + attachInterruptVector(IRQ_SAI2, isrAudio); + NVIC_ENABLE_IRQ(IRQ_SAI2); + NVIC_SET_PRIORITY(IRQ_SAI2, 127); + I2S2_TCSR |= 1<<8; // start generating TX FIFO interrupts + #endif + + } + + static bool add(int16_t* samples) { + + #ifdef USE_DMA + if (freeBuffers == 0) {return false;} + --freeBuffers; + int16_t* dst = &audioBuffer[curBuffer * SAMPLES_PER_BUFFER]; + memcpy(dst, samples, SAMPLES_PER_BUFFER*sizeof(int16_t)); + arm_dcache_flush_delete(dst, SAMPLES_PER_BUFFER*sizeof(int16_t)); + curBuffer = (curBuffer + 1) % NUM_BUFFERS; + return true; + #else + if (freeEntries < SAMPLES_PER_BUFFER) {return false;} + + //int16_t* dst = &audioBuffer[bufHead]; + //memcpy(dst, samples, SAMPLES_PER_BUFFER*sizeof(int16_t)); + //bufHead = (bufHead + SAMPLES_PER_BUFFER) % NUM_ENTRIES; + + + //curBuffer = (curBuffer + 1) % NUM_BUFFERS; + + for (uint16_t i = 0; i < SAMPLES_PER_BUFFER; ++i) { + audioBuffer[bufHead] = samples[i]; + bufHead = (bufHead + 1) % NUM_ENTRIES; + } + freeEntries -= SAMPLES_PER_BUFFER; + + arm_dcache_flush_delete(audioBuffer, NUM_ENTRIES*sizeof(int16_t)); + + return true; + #endif + } + + + void reset() { + for (uint32_t i = 0; i < NUM_ENTRIES; ++i) {audioBuffer[i] = 0;} + arm_dcache_flush_delete(audioBuffer, sizeof(audioBuffer)); + } + +private: + + //uint16_t pos = 0; + + #ifdef USE_DMA + + FASTRUN static void isrDMA() { + dma.clearInterrupt(); + if (freeBuffers < NUM_BUFFERS) {++freeBuffers;} + + } + + #else + + // interrupt service routine + FASTRUN static void isrAudio() { + + static constexpr volatile uint16_t* txReg = (uint16_t *)((uint32_t)&I2S2_TDR0 + 2); + //static constexpr uint16_t* txReg = (uint16_t *)((uint32_t)&I2S2_TDR0 ); + + //if (CHANNELS == 2) { + const uint16_t sample1 = audioBuffer[bufTail]; + + //*txReg = sample1; + bufTail = (bufTail + 1) & (NUM_ENTRIES-1); + if (freeEntries < NUM_ENTRIES) {++freeEntries;} + *txReg = sample1; + + //} + + } + + #endif + + + + +private: + + FLASHMEM static void config_sai2() { + + CCM_CCGR5 |= CCM_CCGR5_SAI2(CCM_CCGR_ON); + + double fs = SAMPLE_RATE_HZ; + // 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 = (fs * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + setAudioClock(c0, c1, c2, true); + + + // clear SAI2_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI2_CLK_SEL_MASK)) + | CCM_CSCMR1_SAI2_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + + //n1 = n1 / 2; //Double Speed for TDM + + CCM_CS2CDR = (CCM_CS2CDR & ~(CCM_CS2CDR_SAI2_CLK_PRED_MASK | CCM_CS2CDR_SAI2_CLK_PODF_MASK)) + | CCM_CS2CDR_SAI2_CLK_PRED(n1 - 1) + | CCM_CS2CDR_SAI2_CLK_PODF(n2 - 1); + + // kazu: OK WITHOUT?? + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL_MASK)) | (IOMUXC_GPR_GPR1_SAI2_MCLK_DIR | IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL(0)); //Select MCLK + + + // configure transmitter + const int rsync = 0; + const int tsync = 1; + + uint16_t by = 32; // ?? + + // configure transmitter + I2S2_TMR = 0; + I2S2_TCR1 = I2S_TCR1_RFW(1); + I2S2_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); // sync=0; tx is async; + I2S2_TCR3 = I2S_TCR3_TCE; + I2S2_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((by-1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S2_TCR5 = I2S_TCR5_WNW((by-1)) | I2S_TCR5_W0W((by-1)) | I2S_TCR5_FBT((by-1)); // page 1995 + + I2S2_RMR = 0; + I2S2_RCR1 = I2S_RCR1_RFW(1); + I2S2_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); // sync=0; rx is async; + I2S2_RCR3 = I2S_RCR3_RCE; + I2S2_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((by-1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S2_RCR5 = I2S_RCR5_WNW((by-1)) | I2S_RCR5_W0W((by-1)) | I2S_RCR5_FBT((by-1)); + + + + + //Serial.println(CORE_PIN2_PADCONFIG); + //Serial.println(CORE_PIN3_PADCONFIG); + //Serial.println(CORE_PIN4_PADCONFIG); + //Serial.println("--------"); + + //uint32_t s = 0b0000010111000; + //CORE_PIN2_PADCONFIG = s; + //CORE_PIN2_PADCONFIG = s; + //CORE_PIN2_PADCONFIG = s; + + + // configure pins (2,3,4) to their I2S functionality + CORE_PIN4_CONFIG = 2; // RX_BCLK + CORE_PIN3_CONFIG = 2; // RX_SYNC (left/right) + CORE_PIN2_CONFIG = 2; // TX_DATA0 + I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + + #ifdef USE_DMA + I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; // | I2S_TCSR_FR ??? + #else + I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE; + #endif + + /* + uint32_t pke = 0; + uint32_t ode = 0; + uint32_t speed = 0b00; + uint32_t dse = 0b010; + uint32_t sre = 0b0; + uint32_t s = (pke<<12)|(ode<<11)|(sre<<0)|(dse<<3)|(speed<<6); + + CORE_PIN2_PADCONFIG = s; + CORE_PIN3_PADCONFIG = s; + CORE_PIN4_PADCONFIG = s; + */ + + } + + FLASHMEM static void setAudioClock(int nfact, int32_t nmult, uint32_t ndiv, bool force) {// sets PLL4 + + if (!force && (CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_ENABLE)) return; + + CCM_ANALOG_PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS | CCM_ANALOG_PLL_AUDIO_ENABLE + | CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2) // 2: 1/4; 1: 1/2; 0: 1/1 + | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(nfact); + + CCM_ANALOG_PLL_AUDIO_NUM = nmult & CCM_ANALOG_PLL_AUDIO_NUM_MASK; + CCM_ANALOG_PLL_AUDIO_DENOM = ndiv & CCM_ANALOG_PLL_AUDIO_DENOM_MASK; + + CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_POWERDOWN;//Switch on PLL + while (!(CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK)) {}; //Wait for pll-lock + + const int div_post_pll = 1; // other values: 2,4 + CCM_ANALOG_MISC2 &= ~(CCM_ANALOG_MISC2_DIV_MSB | CCM_ANALOG_MISC2_DIV_LSB); + if(div_post_pll>1) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_LSB; + if(div_post_pll>3) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_MSB; + + CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_BYPASS;//Disable Bypass + + } + +};