| DMAMEM static uint16_t dac_buffer1[AUDIO_BLOCK_SAMPLES]; | DMAMEM static uint16_t dac_buffer1[AUDIO_BLOCK_SAMPLES]; | ||||
| DMAMEM static uint16_t dac_buffer2[AUDIO_BLOCK_SAMPLES]; | DMAMEM static uint16_t dac_buffer2[AUDIO_BLOCK_SAMPLES]; | ||||
| audio_block_t * AudioOutputAnalog::block_left_1st = NULL; | audio_block_t * AudioOutputAnalog::block_left_1st = NULL; | ||||
| audio_block_t * AudioOutputAnalog::block_left_2nd = NULL; | |||||
| bool AudioOutputAnalog::update_responsibility = false; | bool AudioOutputAnalog::update_responsibility = false; | ||||
| DMAChannel AudioOutputAnalog::dma1(false); | DMAChannel AudioOutputAnalog::dma1(false); | ||||
| DMAChannel AudioOutputAnalog::dma2(false); | |||||
| void AudioOutputAnalog::begin(void) | void AudioOutputAnalog::begin(void) | ||||
| { | { | ||||
| dma1.begin(true); // Allocate the DMA channels first | dma1.begin(true); // Allocate the DMA channels first | ||||
| dma2.begin(true); // Allocate the DMA channels first | |||||
| delay(2500); | delay(2500); | ||||
| Serial.println("AudioOutputAnalog begin"); | Serial.println("AudioOutputAnalog begin"); | ||||
| // commandeer FTM1 for timing (PWM on pin 3 & 4 will become 22 kHz) | // commandeer FTM1 for timing (PWM on pin 3 & 4 will become 22 kHz) | ||||
| FTM1_SC = 0; | FTM1_SC = 0; | ||||
| FTM1_CNT = 0; | FTM1_CNT = 0; | ||||
| FTM1_MOD = (uint32_t)((F_PLL/2) / AUDIO_SAMPLE_RATE_EXACT + 0.5); | |||||
| FTM1_SC = FTM_SC_CLKS(1); | |||||
| FTM1_MOD = (uint32_t)((F_PLL/2) / 44117.64706/*AUDIO_SAMPLE_RATE_EXACT*/ + 0.5); | |||||
| FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_DMA; | |||||
| dma1.sourceBuffer(dac_buffer1, sizeof(dac_buffer1)); | dma1.sourceBuffer(dac_buffer1, sizeof(dac_buffer1)); | ||||
| dma1.destination(*(int16_t *)&DAC0_DAT0L); | dma1.destination(*(int16_t *)&DAC0_DAT0L); | ||||
| dma1.interruptAtCompletion(); | dma1.interruptAtCompletion(); | ||||
| dma1.disableOnCompletion(); | dma1.disableOnCompletion(); | ||||
| dma1.triggerAtCompletionOf(dma2); | |||||
| dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM1_OV); | dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM1_OV); | ||||
| dma1.attachInterrupt(isr1); | dma1.attachInterrupt(isr1); | ||||
| dma2.sourceBuffer(dac_buffer2, sizeof(dac_buffer2)); | |||||
| dma2.destination(*(int16_t *)&DAC0_DAT0L); | |||||
| dma2.interruptAtCompletion(); | |||||
| dma2.disableOnCompletion(); | |||||
| dma2.triggerAtCompletionOf(dma1); | |||||
| dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM1_OV); | |||||
| dma2.attachInterrupt(isr2); | |||||
| update_responsibility = update_setup(); | |||||
| /* | |||||
| dma.TCD->SADDR = dac_buffer; | |||||
| dma.TCD->SOFF = 2; | |||||
| dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
| dma.TCD->NBYTES_MLNO = 2; | |||||
| dma.TCD->SLAST = -sizeof(dac_buffer); | |||||
| dma.TCD->DADDR = &DAC0_DAT0L; | |||||
| dma.TCD->DOFF = 0; | |||||
| dma.TCD->CITER_ELINKNO = sizeof(dac_buffer) / 2; | |||||
| dma.TCD->DLASTSGA = 0; | |||||
| dma.TCD->BITER_ELINKNO = sizeof(dac_buffer) / 2; | |||||
| dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
| dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB); | |||||
| update_responsibility = update_setup(); | update_responsibility = update_setup(); | ||||
| dma.enable(); | |||||
| dma.attachInterrupt(isr); | |||||
| */ | |||||
| // Enable DMA transfers on timer | |||||
| dma1.enable(); | |||||
| } | } | ||||
| void AudioOutputAnalog::isr1(void) | void AudioOutputAnalog::isr1(void) | ||||
| { | { | ||||
| if(!dma1.complete()) return; | |||||
| dma1.clearInterrupt(); | dma1.clearInterrupt(); | ||||
| } | |||||
| void AudioOutputAnalog::isr2(void) | |||||
| { | |||||
| dma2.clearInterrupt(); | |||||
| // Point DMA to the other buffer (which also resets the number of bytes to transfer) | |||||
| bool finishedFirst = (dma1.sourceAddress() == (void *)&dac_buffer1[AUDIO_BLOCK_SAMPLES]); | |||||
| if(finishedFirst) { | |||||
| // Just finished copying the first block, set up the second | |||||
| dma1.sourceBuffer(dac_buffer2, sizeof(dac_buffer2)); | |||||
| } else { | |||||
| // Just finished the second buffer, set up the first | |||||
| dma1.sourceBuffer(dac_buffer1, sizeof(dac_buffer1)); | |||||
| } | |||||
| // restart DMA on timer calls | |||||
| dma1.enable(); | |||||
| // Then refill the buffer | |||||
| int16_t *dest; | |||||
| const int16_t * end; | |||||
| if(finishedFirst) { | |||||
| dest = (int16_t *)dac_buffer1; | |||||
| end = (int16_t *)&dac_buffer1[AUDIO_BLOCK_SAMPLES]; | |||||
| } else { | |||||
| dest = (int16_t *)dac_buffer2; | |||||
| end = (int16_t *)&dac_buffer2[AUDIO_BLOCK_SAMPLES]; | |||||
| } | |||||
| const int16_t *src; | |||||
| audio_block_t *block; | |||||
| block = AudioOutputAnalog::block_left_1st; | |||||
| if (block) { | |||||
| src = block->data; | |||||
| do { | |||||
| *dest++ = ((*src++) >> 4) + 0x800; | |||||
| } while (dest < end); | |||||
| AudioStream::release(block); | |||||
| AudioOutputAnalog::block_left_1st = AudioOutputAnalog::block_left_2nd; | |||||
| AudioOutputAnalog::block_left_2nd = NULL; | |||||
| } else { | |||||
| do { | |||||
| *dest++ = 0x800; | |||||
| } while (dest < end); | |||||
| } | |||||
| if (AudioOutputAnalog::update_responsibility) AudioStream::update_all(); | |||||
| } | } | ||||
| void AudioOutputAnalog::update(void) | void AudioOutputAnalog::update(void) | ||||
| { | { | ||||
| audio_block_t *block; | audio_block_t *block; | ||||
| if (block_left_1st == NULL) { | if (block_left_1st == NULL) { | ||||
| block_left_1st = block; | block_left_1st = block; | ||||
| __enable_irq(); | __enable_irq(); | ||||
| } else if (block_left_2nd == NULL) { | |||||
| block_left_2nd = block; | |||||
| __enable_irq(); | |||||
| } else { | } else { | ||||
| audio_block_t *tmp = block_left_1st; | audio_block_t *tmp = block_left_1st; | ||||
| block_left_1st = block; | |||||
| block_left_1st = block_left_2nd; | |||||
| block_left_2nd = block; | |||||
| __enable_irq(); | __enable_irq(); | ||||
| release(tmp); | release(tmp); | ||||
| } | } | ||||
| } | } | ||||
| #endif // defined(__MK20DX256__) | #endif // defined(__MK20DX256__) | ||||