diff --git a/FW/Leo_muziekdoos_fw/src/audio.cpp b/FW/Leo_muziekdoos_fw/src/audio.cpp index 8506e43..0cce9ca 100644 --- a/FW/Leo_muziekdoos_fw/src/audio.cpp +++ b/FW/Leo_muziekdoos_fw/src/audio.cpp @@ -7,19 +7,21 @@ Features: Circular Buffer with DMA IRQ (half full, full), Master Clock enabled This example uses the SPI3 port tested on STM32F407VET "black" board. On other boards please define LED0_BUILTIN and LED1_BUILTIN */ #define I2S_BUFFER_SIZE 64 -I2S_HandleTypeDef hi2s3; -DMA_HandleTypeDef hdma_spi3_tx; +I2S_HandleTypeDef hi2s4; +DMA_HandleTypeDef hdma_spi4_tx; uint32_t dma_buffer[I2S_BUFFER_SIZE]; //int16_t sine[WAV_SIZE] = {0}; // sinus oszillator float osc_phi = 0; float osc_phi_inc = 440.0f / 44100.0f; // generating 440HZ +float scale[] = {C4_HZ, D4_HZ, E4_HZ, F4_HZ, G4_HZ, A4_HZ, B4_HZ, A4_HZ, G4_HZ, F4_HZ, E4_HZ, D4_HZ, C4_HZ}; + void MX_DMA_Init(void); extern "C" void DMA1_Stream5_IRQHandler(void) // this function must be included to avoid DMA to crash! { - HAL_DMA_IRQHandler(&hdma_spi3_tx); + HAL_DMA_IRQHandler(&hdma_spi4_tx); } void Error_Handler2(byte errorcode) @@ -88,17 +90,16 @@ extern "C" void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) // setting up I2S extern "C" void MX_I2S3_Init(void) { - hi2s3.Instance = SPI3; - hi2s3.Init.Mode = I2S_MODE_MASTER_TX; - hi2s3.Init.Standard = I2S_STANDARD_PHILIPS; - hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B; - //hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE; - hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE; - hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_44K; - hi2s3.Init.CPOL = I2S_CPOL_LOW; - hi2s3.Init.ClockSource = I2S_CLOCK_PLL; - hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE; - if (HAL_I2S_Init(&hi2s3) != HAL_OK) + hi2s4.Instance = SPI4; + hi2s4.Init.Mode = I2S_MODE_MASTER_TX; + hi2s4.Init.Standard = I2S_STANDARD_PHILIPS; + hi2s4.Init.DataFormat = I2S_DATAFORMAT_16B; + hi2s4.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE; + hi2s4.Init.AudioFreq = I2S_AUDIOFREQ_44K; + hi2s4.Init.CPOL = I2S_CPOL_LOW; + hi2s4.Init.ClockSource = I2S_CLOCK_PLL; + hi2s4.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE; + if (HAL_I2S_Init(&hi2s4) != HAL_OK) { Error_Handler2(1); // on error: one blink } @@ -134,45 +135,51 @@ extern "C" void HAL_I2S_MspInit(I2S_HandleTypeDef *hi2s) PC7 MCK */ //I2S3 used GPIO configuration in this example: - // PB5 DIN / SD - // PA4 LRC /WD - // PB3 SCLK /CK + // PB5 DIN / SD ==> PA1 + // PA4 LRC /WD ==> PB12 + // PB3 SCLK /CK ==> PB13 // PC7 MCK - GPIO_InitStruct.Pin = GPIO_PIN_4; + + //LRCLL + GPIO_InitStruct.Pin = GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_5; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; + GPIO_InitStruct.Alternate = GPIO_AF6_SPI4; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); - // master clock - GPIO_InitStruct.Pin = GPIO_PIN_7; + + //DIN + GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + GPIO_InitStruct.Alternate = GPIO_AF5_SPI4; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + // clock + GPIO_InitStruct.Pin = GPIO_PIN_13; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = GPIO_AF6_SPI4; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + // Peripheral DMA init - hdma_spi3_tx.Instance = DMA1_Stream5; - hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0; - hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; - hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE; - hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE; - hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; - hdma_spi3_tx.Init.Mode = DMA_CIRCULAR; - hdma_spi3_tx.Init.Priority = DMA_PRIORITY_LOW; - hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; - if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK) + hdma_spi4_tx.Instance = DMA1_Stream5; + hdma_spi4_tx.Init.Channel = DMA_CHANNEL_0; + hdma_spi4_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; + hdma_spi4_tx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi4_tx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi4_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi4_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_spi4_tx.Init.Mode = DMA_CIRCULAR; + hdma_spi4_tx.Init.Priority = DMA_PRIORITY_LOW; + hdma_spi4_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_spi4_tx) != HAL_OK) { Error_Handler2(5); // on error: five blinks } - __HAL_LINKDMA(hi2s, hdmatx, hdma_spi3_tx); + __HAL_LINKDMA(hi2s, hdmatx, hdma_spi4_tx); } extern "C" void HAL_MspInit(void) // maybe useful, not included in this example { @@ -194,21 +201,17 @@ extern "C" void HAL_MspInit(void) // maybe useful, not included in this example HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); } - - - - void initAudio() { // HAL_MspInit(); // not important by default - HAL_I2S_MspInit(&hi2s3); // setting up pins and clocks; routing SPI3 to DMA + HAL_I2S_MspInit(&hi2s4); // setting up pins and clocks; routing SPI3 to DMA MX_DMA_Init(); MX_I2S3_Init(); pinMode(LED_BUILTIN, OUTPUT); pinMode(PIN_I2S_SDMODE, OUTPUT); digitalWrite(PIN_I2S_SDMODE, HIGH); digitalWrite(LED_BUILTIN, 0); - StartAudioBuffers(&hi2s3); + StartAudioBuffers(&hi2s4); digitalWrite(LED_BUILTIN, 1); } @@ -220,28 +223,34 @@ void handleAudio() // just a dummy code in loop, audio out is generated by ISR digitalWrite(LED_BUILTIN, 1); delay(250); - //HAL_I2S_Transmit(&hi2s3,&sine,WAV_SIZE,1000); + //HAL_I2S_Transmit(&hi2s4,&sine,WAV_SIZE,1000); HAL_StatusTypeDef res; - int16_t signal[46876]; + int16_t signal[4096]; int nsamples = sizeof(signal) / sizeof(signal[0]); int i = 0; - while(i < nsamples) { - double t = ((double)i/2.0)/((double)nsamples); - signal[i] = 32767*sin(100.0 * TAU * t); // left - signal[i+1] = signal[i]; // right - i += 2; + while (i < nsamples) + { + double t = ((double)i / 2.0) / ((double)nsamples); + signal[i] = 32767 * sin(100.0 * TAU * t); // left + signal[i + 1] = signal[i]; // right + i += 2; } - while(1) { - res = HAL_I2S_Transmit(&hi2s3, (uint16_t*)signal, nsamples, HAL_MAX_DELAY); - if(res != HAL_OK) { - Serial.printf("I2S - ERROR, res = %d!\r\n", res); - break; - } + while (1) + { + res = HAL_I2S_Transmit(&hi2s4, (uint16_t *)signal, nsamples, HAL_MAX_DELAY); + if (res != HAL_OK) + { + Serial.printf("I2S - ERROR, res = %d!\r\n", res); + break; + } + else + { + Serial.printf("I2S - HAL_OK\r\n"); + } } digitalWrite(LED_BUILTIN, 0); delay(250); - } \ No newline at end of file diff --git a/FW/Leo_muziekdoos_fw/src/audio.h b/FW/Leo_muziekdoos_fw/src/audio.h index ac160cc..a93f559 100644 --- a/FW/Leo_muziekdoos_fw/src/audio.h +++ b/FW/Leo_muziekdoos_fw/src/audio.h @@ -26,8 +26,6 @@ #define B4_HZ 493.88 // Define a C-major scale to play all the notes up and down. -float scale[] = { C4_HZ, D4_HZ, E4_HZ, F4_HZ, G4_HZ, A4_HZ, B4_HZ, A4_HZ, G4_HZ, F4_HZ, E4_HZ, D4_HZ, C4_HZ }; - void initAudio(void); void handleAudio(void); diff --git a/FW/leo_muziekdoos_sam51/lib/DMA/DMA.cpp b/FW/leo_muziekdoos_sam51/lib/DMA/DMA.cpp new file mode 100644 index 0000000..0943da3 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/lib/DMA/DMA.cpp @@ -0,0 +1,707 @@ +/*! + * @file Adafruit_ZeroDMA.cpp + * + * @mainpage Adafruit DMA Arduino library for SAMD microcontrollers. + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's DMA library for SAMD + * microcontrollers on the Arduino platform. SAMD21 and SAMD51 lines + * are supported. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * @section dependencies Dependencies + * + * @section author Author + * + * Written by Phil "PaintYourDragon" Burgess for Adafruit Industries, + * based partly on DMA insights from Atmel ASFCORE 3. + * + * @section license License + * + * MIT license, all text here must be included in any redistribution. + * + */ + +#include "DMA.h" +#include // memalign() function +#include + +#ifdef USE_TINYUSB +// For Serial when selecting TinyUSB +#include +#endif + +#ifdef DMAC_RESERVED_CHANNELS // SAMD core > 1.2.1 +#include // _descriptor[] and _writeback[] are extern'd here +static volatile uint32_t _channelMask = DMAC_RESERVED_CHANNELS; +#else +#include "utility/dma.h" +static volatile uint32_t _channelMask = 0; // Bitmask of allocated channels + +// DMA descriptor list entry point (and writeback buffer) per channel +__attribute__((__aligned__(16))) static DmacDescriptor ///< 128 bit alignment + _descriptor[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR, ///< Descriptor table + _writeback[DMAC_CH_NUM] SECTION_DMAC_DESCRIPTOR; ///< Writeback table +#endif + +// Pointer to ZeroDMA object for each channel is needed for the +// ISR (in C, outside of class context) to access callbacks. +static DMA_class *_dmaPtr[DMAC_CH_NUM] = {0}; // Init to NULL + +// Adapted from ASF3 interrupt_sam_nvic.c: + +static volatile unsigned long cpu_irq_critical_section_counter = 0; +static volatile unsigned char cpu_irq_prev_interrupt_state = 0; + +static void cpu_irq_enter_critical(void) { + if (!cpu_irq_critical_section_counter) { + if (__get_PRIMASK() == 0) { // IRQ enabled? + __disable_irq(); // Disable it + __DMB(); + cpu_irq_prev_interrupt_state = 1; + } else { + // Make sure the to save the prev state as false + cpu_irq_prev_interrupt_state = 0; + } + } + + cpu_irq_critical_section_counter++; +} + +static void cpu_irq_leave_critical(void) { + // Check if the user is trying to leave a critical section + // when not in a critical section + if (cpu_irq_critical_section_counter > 0) { + cpu_irq_critical_section_counter--; + + // Only enable global interrupts when the counter + // reaches 0 and the state of the global interrupt flag + // was enabled when entering critical state */ + if ((!cpu_irq_critical_section_counter) && cpu_irq_prev_interrupt_state) { + __DMB(); + __enable_irq(); + } + } +} + +// CONSTRUCTOR ------------------------------------------------------------- + +// Constructor initializes Adafruit_ZeroDMA basics but does NOT allocate a +// DMA channel (that's done in allocate()) or start a job (that's done in +// startJob()). This is because constructors in a global context are called +// before a sketch's setup() function, which may have some other hardware +// initialization of its own, don't want it clobbering us. +DMA_class::DMA_class(void) { + channel = 0xFF; // Channel not yet allocated + jobStatus = DMA_STATUS_OK; + hasDescriptors = false; // No descriptors allocated yet + loopFlag = false; + peripheralTrigger = 0; // Software trigger only by default + triggerAction = DMA_TRIGGER_ACTON_TRANSACTION; + memset(callback, 0, sizeof(callback)); +} + +// TODO: add destructor? Should stop job, delete descriptors, free channel. + +// INTERRUPT SERVICE ROUTINE ----------------------------------------------- + +/*! + @brief This is a C function that exists outside the Adafruit_ZeroDMA + context. DMA channel number is determined from the INTPEND + register, from this we get a ZeroDMA object pointer through the + _dmaPtr[] array. (It's done this way because jobStatus and + callback[] are protected elements in the ZeroDMA object -- we + can't touch them in C, but the next function after this, being + part of the ZeroDMA class, can.) +*/ +#ifdef __SAMD51__ +void DMAC_0_Handler(void) { +#else +void DMAC_Handler(void) { +#endif + cpu_irq_enter_critical(); + + uint8_t channel = DMAC->INTPEND.bit.ID; // Channel # causing interrupt + if (channel < DMAC_CH_NUM) { + DMA_class *dma; + if ((dma = _dmaPtr[channel])) { // -> Channel's ZeroDMA object +#ifdef __SAMD51__ + // Call IRQ handler with channel # + dma->_IRQhandler(channel); +#else + DMAC->CHID.bit.ID = channel; + // Call IRQ handler with interrupt flag(s) + dma->_IRQhandler(DMAC->CHINTFLAG.reg); +#endif + } + } + + cpu_irq_leave_critical(); +} + +#ifdef __SAMD51__ +void DMAC_1_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); +void DMAC_2_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); +void DMAC_3_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); +void DMAC_4_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); +#endif + +void DMA_class::_IRQhandler(uint8_t flags) { +#ifdef __SAMD51__ + // 'flags' is initially passed in as channel number, + // from which we look up the actual interrupt flags... + flags = DMAC->Channel[flags].CHINTFLAG.reg; +#endif + if (flags & DMAC_CHINTENCLR_TERR) { + // Clear error flag +#ifdef __SAMD51__ + DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_TERR; +#else + DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR; +#endif + jobStatus = DMA_STATUS_ERR_IO; + if (callback[DMA_CALLBACK_TRANSFER_ERROR]) + callback[DMA_CALLBACK_TRANSFER_ERROR](this); + } else if (flags & DMAC_CHINTENCLR_TCMPL) { + // Clear transfer complete flag +#ifdef __SAMD51__ + DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; +#else + DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; +#endif + jobStatus = DMA_STATUS_OK; + if (callback[DMA_CALLBACK_TRANSFER_DONE]) + callback[DMA_CALLBACK_TRANSFER_DONE](this); + } else if (flags & DMAC_CHINTENCLR_SUSP) { + // Clear channel suspend flag +#ifdef __SAMD51__ + DMAC->Channel[channel].CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP; +#else + DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP; +#endif + jobStatus = DMA_STATUS_SUSPEND; + if (callback[DMA_CALLBACK_CHANNEL_SUSPEND]) + callback[DMA_CALLBACK_CHANNEL_SUSPEND](this); + } +} + +// DMA CHANNEL FUNCTIONS --------------------------------------------------- + +// Allocates channel for ZeroDMA object +DMAstatus DMA_class::allocate(void) { + + if (channel < DMAC_CH_NUM) + return DMA_STATUS_OK; // Already alloc'd! + + // Find index of first free DMA channel. As currently written, + // this "does not play well with others" as it assumes _channelMask + // is the final arbiter of channels in use (this is true only within + // this library -- but other DMA-driven code may have allocated its + // own channel(s) elsewhere, sometimes with an equally broken + // approach). A possible alternate approach, I haven't tested this + // yet, might be to loop through each channel, set DMAC->CHID.bit.ID + // and then test whether CHCTRLA.bit.ENABLE is set? But for now... + for (channel = 0; (channel < DMAC_CH_NUM) && (_channelMask & (1 << channel)); + channel++) + ; + // Doesn't help that code later does a software reset of the DMA + // controller, which would blow out other DMA-using libraries + // anyway (or they're just as likely to blow out this one). + // I think it's just an all-or-nothing affair...use one library + // for DMA everything, never mix and match. + + if (channel >= DMAC_CH_NUM) // No free channel! + return DMA_STATUS_ERR_NOT_FOUND; + + cpu_irq_enter_critical(); + + if (!_channelMask) { // No channels allocated yet; initialize DMA! +#if !defined(DMAC_RESERVED_CHANNELS) +#if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) + PM->AHBMASK.bit.DMAC_ = 1; +#elif defined(__SAMD51__) + MCLK->AHBMASK.bit.DMAC_ = 1; // Initialize DMA clocks +#else + PM->AHBMASK.bit.DMAC_ = 1; // Initialize DMA clocks + PM->APBBMASK.bit.DMAC_ = 1; +#endif + DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA controller + DMAC->CTRL.bit.SWRST = 1; // Perform software reset + + // Initialize descriptor list addresses + DMAC->BASEADDR.bit.BASEADDR = (uint32_t)_descriptor; + DMAC->WRBADDR.bit.WRBADDR = (uint32_t)_writeback; + memset(_descriptor, 0, sizeof(_descriptor)); + memset(_writeback, 0, sizeof(_writeback)); + + // Re-enable DMA controller with all priority levels + DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xF); +#endif + + // Enable DMA interrupt at lowest priority +#ifdef __SAMD51__ + IRQn_Type irqs[] = {DMAC_0_IRQn, DMAC_1_IRQn, DMAC_2_IRQn, DMAC_3_IRQn, + DMAC_4_IRQn}; + for (uint8_t i = 0; i < (sizeof irqs / sizeof irqs[0]); i++) { + NVIC_EnableIRQ(irqs[i]); + NVIC_SetPriority(irqs[i], (1 << __NVIC_PRIO_BITS) - 1); + } +#else + NVIC_EnableIRQ(DMAC_IRQn); + NVIC_SetPriority(DMAC_IRQn, (1 << __NVIC_PRIO_BITS) - 1); +#endif + } + + _channelMask |= 1 << channel; // Mark channel as allocated + _dmaPtr[channel] = this; // Channel-index-to-object pointer + + // Reset the allocated channel +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 0; + DMAC->Channel[channel].CHCTRLA.bit.SWRST = 1; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLA.bit.ENABLE = 0; + DMAC->CHCTRLA.bit.SWRST = 1; +#endif + + // Clear software trigger + DMAC->SWTRIGCTRL.reg &= ~(1 << channel); + + // Configure default behaviors +#ifdef __SAMD51__ + DMAC->Channel[channel].CHPRILVL.bit.PRILVL = 0; + DMAC->Channel[channel].CHCTRLA.bit.TRIGSRC = peripheralTrigger; + DMAC->Channel[channel].CHCTRLA.bit.TRIGACT = triggerAction; + DMAC->Channel[channel].CHCTRLA.bit.BURSTLEN = + DMAC_CHCTRLA_BURSTLEN_SINGLE_Val; // Single-beat burst length +#else + DMAC->CHCTRLB.bit.LVL = 0; + DMAC->CHCTRLB.bit.TRIGSRC = peripheralTrigger; + DMAC->CHCTRLB.bit.TRIGACT = triggerAction; +#endif + + cpu_irq_leave_critical(); + + return DMA_STATUS_OK; +} + +void DMA_class::setPriority(dma_priority pri) { +#ifdef __SAMD51__ + DMAC->Channel[channel].CHPRILVL.bit.PRILVL = pri; +#else + DMAC->CHCTRLB.bit.LVL = pri; +#endif +} + +// Deallocate DMA channel +// TODO: should this delete/deallocate the descriptor list? +DMAstatus DMA_class::free(void) { + + DMAstatus status = DMA_STATUS_OK; + + cpu_irq_enter_critical(); // jobStatus is volatile + + if (jobStatus == DMA_STATUS_BUSY) { + status = DMA_STATUS_BUSY; // Can't leave when busy + } else if ((channel < DMAC_CH_NUM) && (_channelMask & (1 << channel))) { + // Valid in-use channel; release it + _channelMask &= ~(1 << channel); // Clear bit + if (!_channelMask) { // No more channels in use? +#ifdef __SAMD51__ + NVIC_DisableIRQ(DMAC_0_IRQn); // Disable DMA interrupt + DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA + MCLK->AHBMASK.bit.DMAC_ = 0; // Disable DMA clock +#else + NVIC_DisableIRQ(DMAC_IRQn); // Disable DMA interrupt + DMAC->CTRL.bit.DMAENABLE = 0; // Disable DMA + PM->APBBMASK.bit.DMAC_ = 0; // Disable DMA clocks + PM->AHBMASK.bit.DMAC_ = 0; +#endif + } + _dmaPtr[channel] = NULL; + channel = 0xFF; + } else { + status = DMA_STATUS_ERR_NOT_INITIALIZED; // Channel not in use + } + + cpu_irq_leave_critical(); + + return status; +} + +// Start DMA transfer job. Channel and descriptors should be allocated +// before calling this. +DMAstatus DMA_class::startJob(void) { + DMAstatus status = DMA_STATUS_OK; + + cpu_irq_enter_critical(); // Job status is volatile + + if (jobStatus == DMA_STATUS_BUSY) { + status = DMA_STATUS_BUSY; // Resource is busy + } else if (channel >= DMAC_CH_NUM) { + status = DMA_STATUS_ERR_NOT_INITIALIZED; // Channel not in use + } else if (!hasDescriptors || (_descriptor[channel].BTCNT.reg <= 0)) { + status = DMA_STATUS_ERR_INVALID_ARG; // Bad transfer size + } else { + uint8_t i, interruptMask = 0; + for (i = 0; i < DMA_CALLBACK_N; i++) + if (callback[i]) + interruptMask |= (1 << i); + jobStatus = DMA_STATUS_BUSY; +#ifdef __SAMD51__ + DMAC->Channel[channel].CHINTENSET.reg = + DMAC_CHINTENSET_MASK & interruptMask; + DMAC->Channel[channel].CHINTENCLR.reg = + DMAC_CHINTENCLR_MASK & ~interruptMask; + DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 1; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK & interruptMask; + DMAC->CHINTENCLR.reg = DMAC_CHINTENCLR_MASK & ~interruptMask; + DMAC->CHCTRLA.bit.ENABLE = 1; // Enable the transfer channel +#endif + } + + cpu_irq_leave_critical(); + + return status; +} + +// Set and enable callback function for ZeroDMA object. This can be called +// before or after channel and/or descriptors are allocated, but needs +// to be called before job is started. +void DMA_class::setCallback(void (*cb)(DMA_class *), + dma_callback_type type) { + callback[type] = cb; +} + +// Suspend/resume don't quite do what I thought -- avoid using for now. +void DMA_class::suspend(void) { + cpu_irq_enter_critical(); +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLB.reg |= DMAC_CHCTRLB_CMD_SUSPEND; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_SUSPEND; +#endif + cpu_irq_leave_critical(); +} + +#define MAX_JOB_RESUME_COUNT 10000 ///< Loop iteration threshold for timeout +void DMA_class::resume(void) { + cpu_irq_enter_critical(); // jobStatus is volatile + if (jobStatus == DMA_STATUS_SUSPEND) { + int count; + uint32_t bitMask = 1 << channel; +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLB.reg |= DMAC_CHCTRLB_CMD_RESUME; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_RESUME; +#endif + + for (count = 0; + (count < MAX_JOB_RESUME_COUNT) && !(DMAC->BUSYCH.reg & bitMask); + count++) + ; + + jobStatus = (count < MAX_JOB_RESUME_COUNT) ? DMA_STATUS_BUSY + : DMA_STATUS_ERR_TIMEOUT; + } + cpu_irq_leave_critical(); +} + +// Abort is OK though. +void DMA_class::abort(void) { + if (channel <= DMAC_CH_NUM) { + cpu_irq_enter_critical(); +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 0; // Disable channel +#else + DMAC->CHID.bit.ID = channel; // Select channel + DMAC->CHCTRLA.reg = 0; // Disable +#endif + jobStatus = DMA_STATUS_ABORTED; + cpu_irq_leave_critical(); + } +} + +// Set DMA peripheral trigger. +// This can be done before or after channel is allocated. +void DMA_class::setTrigger(uint8_t trigger) { + peripheralTrigger = trigger; // Save value for allocate() + + // If channel already allocated, configure peripheral trigger + // (old lib required configure before alloc -- either way OK now) + if (channel < DMAC_CH_NUM) { + cpu_irq_enter_critical(); +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLA.bit.TRIGSRC = trigger; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLB.bit.TRIGSRC = trigger; +#endif + cpu_irq_leave_critical(); + } +} + +// Set DMA trigger action. +// This can be done before or after channel is allocated. +void DMA_class::setAction(dma_transfer_trigger_action action) { + triggerAction = action; // Save value for allocate() + + // If channel already allocated, configure trigger action + // (old lib required configure before alloc -- either way OK now) + if (channel < DMAC_CH_NUM) { + cpu_irq_enter_critical(); +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLA.bit.TRIGACT = action; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLB.bit.TRIGACT = action; +#endif + cpu_irq_leave_critical(); + } +} + +// Issue software trigger. Channel must be allocated & descriptors added! +void DMA_class::trigger(void) { + if ((channel <= DMAC_CH_NUM) & hasDescriptors) + DMAC->SWTRIGCTRL.reg |= (1 << channel); +} + +uint8_t DMA_class::getChannel(void) { return channel; } + +// DMA DESCRIPTOR FUNCTIONS ------------------------------------------------ + +// Allocates a new DMA descriptor (if needed) and appends it to the +// channel's descriptor list. Returns pointer to DmacDescriptor, +// or NULL on various errors. You'll want to keep the pointer for +// later if you need to modify or free the descriptor. +// Channel must be allocated first! +DmacDescriptor *DMA_class::addDescriptor(void *src, void *dst, + uint32_t count, + dma_beat_size size, bool srcInc, + bool dstInc, uint32_t stepSize, + bool stepSel) { + + // Channel must be allocated first + if (channel >= DMAC_CH_NUM) + return NULL; + + // Can't do while job's busy + if (jobStatus == DMA_STATUS_BUSY) + return NULL; + + DmacDescriptor *desc; + + // Scan descriptor list to find last entry. If an entry's + // DESCADDR value is 0, that's the end of the list and it's + // currently un-looped. If the DESCADDR value is the same + // as the first entry, that's the end of the list and it's + // looped. Either way, set the last entry's DESCADDR value + // to the new descriptor, and the descriptor's own DESCADDR + // will be set later either to 0 or the list head. + if (hasDescriptors) { + // DMA descriptors must be 128-bit (16 byte) aligned. + // memalign() is considered 'obsolete' but it's replacements + // (aligned_alloc() or posix_memalign()) are not currently + // available in the version of ARM GCC in use, but this is, + // so here we are. + if (!(desc = (DmacDescriptor *)memalign(16, sizeof(DmacDescriptor)))) + return NULL; + DmacDescriptor *prev = &_descriptor[channel]; + while (prev->DESCADDR.reg && + (prev->DESCADDR.reg != (uint32_t)&_descriptor[channel])) { + prev = (DmacDescriptor *)prev->DESCADDR.reg; + } + prev->DESCADDR.reg = (uint32_t)desc; + } else { + desc = &_descriptor[channel]; + } + hasDescriptors = true; + + uint8_t bytesPerBeat; // Beat transfer size IN BYTES + switch (size) { + default: + bytesPerBeat = 1; + break; + case DMA_BEAT_SIZE_HWORD: + bytesPerBeat = 2; + break; + case DMA_BEAT_SIZE_WORD: + bytesPerBeat = 4; + break; + } + + desc->BTCTRL.bit.VALID = true; + desc->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE; + desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT; + desc->BTCTRL.bit.BEATSIZE = size; + desc->BTCTRL.bit.SRCINC = srcInc; + desc->BTCTRL.bit.DSTINC = dstInc; + desc->BTCTRL.bit.STEPSEL = stepSel; + desc->BTCTRL.bit.STEPSIZE = stepSize; + desc->BTCNT.reg = count; + desc->SRCADDR.reg = (uint32_t)src; + + if (srcInc) { + if (stepSel) { + desc->SRCADDR.reg += bytesPerBeat * count * (1 << stepSize); + } else { + desc->SRCADDR.reg += bytesPerBeat * count; + } + } + + desc->DSTADDR.reg = (uint32_t)dst; + + if (dstInc) { + if (!stepSel) { + desc->DSTADDR.reg += bytesPerBeat * count * (1 << stepSize); + } else { + desc->DSTADDR.reg += bytesPerBeat * count; + } + } + + desc->DESCADDR.reg = loopFlag ? (uint32_t)&_descriptor[channel] : 0; + + return desc; +} + +// Modify DMA descriptor with a new source address, destination address & +// block transfer count. All other attributes (including increment enables, +// etc.) are unchanged. Mostly for changing the data being pushed to a +// peripheral (DAC, SPI, whatev.) +void DMA_class::changeDescriptor(DmacDescriptor *desc, void *src, + void *dst, uint32_t count) { + + uint8_t bytesPerBeat; // Beat transfer size IN BYTES + switch (desc->BTCTRL.bit.BEATSIZE) { + default: + bytesPerBeat = 1; + break; + case DMA_BEAT_SIZE_HWORD: + bytesPerBeat = 2; + break; + case DMA_BEAT_SIZE_WORD: + bytesPerBeat = 4; + break; + } + + if (count) + desc->BTCNT.reg = count; + + if (src) { + desc->SRCADDR.reg = (uint32_t)src; + if (desc->BTCTRL.bit.SRCINC) { + if (desc->BTCTRL.bit.STEPSEL) { + desc->SRCADDR.reg += + desc->BTCNT.reg * bytesPerBeat * (1 << desc->BTCTRL.bit.STEPSIZE); + } else { + desc->SRCADDR.reg += desc->BTCNT.reg * bytesPerBeat; + } + } + } + + if (dst) { + desc->DSTADDR.reg = (uint32_t)dst; + if (desc->BTCTRL.bit.DSTINC) { + if (!desc->BTCTRL.bit.STEPSEL) { + desc->DSTADDR.reg += + desc->BTCNT.reg * bytesPerBeat * (1 << desc->BTCTRL.bit.STEPSIZE); + } else { + desc->DSTADDR.reg += desc->BTCNT.reg * bytesPerBeat; + } + } + } + +// I think this code is here by accident -- disabling for now. +#if 0 + cpu_irq_enter_critical(); + jobStatus = DMA_STATUS_OK; +#ifdef __SAMD51__ + DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 1; +#else + DMAC->CHID.bit.ID = channel; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; +#endif + cpu_irq_leave_critical(); +#endif +} + +// TODO: delete descriptor, delete whole descriptor chain + +// Select whether channel's descriptor list should repeat or not. +// This can be done before or after channel & any descriptors are allocated. +void DMA_class::loop(boolean flag) { + // The loop selection is 'sticky' -- that is, you can enable or + // disable looping before a descriptor list is built, or after + // the fact. This requires some extra steps in the library code + // but avoids a must-do-in-X-order constraint on user. + loopFlag = flag; + + if (hasDescriptors) { // Descriptor list already started? + // Scan descriptor list to find last entry. If an entry's + // DESCADDR value is 0, that's the end of the list and it's + // currently un-looped. If the DESCADDR value is the same + // as the first entry, that's the end of the list and it's + // already looped. + DmacDescriptor *desc = &_descriptor[channel]; + while (desc->DESCADDR.reg && + (desc->DESCADDR.reg != (uint32_t)&_descriptor[channel])) { + desc = (DmacDescriptor *)desc->DESCADDR.reg; + } + // Loop or unloop descriptor list as appropriate + desc->DESCADDR.reg = loopFlag ? (uint32_t)&_descriptor[channel] : 0; + } +} + +// MISCELLANY -------------------------------------------------------------- + +void DMA_class::printStatus(DMAstatus s) { + if (s == DMA_STATUS_JOBSTATUS) + s = jobStatus; + Serial.print("Status: "); + switch (s) { + case DMA_STATUS_OK: + Serial.println("OK"); + break; + case DMA_STATUS_ERR_NOT_FOUND: + Serial.println("NOT FOUND"); + break; + case DMA_STATUS_ERR_NOT_INITIALIZED: + Serial.println("NOT INITIALIZED"); + break; + case DMA_STATUS_ERR_INVALID_ARG: + Serial.println("INVALID ARGUMENT"); + break; + case DMA_STATUS_ERR_IO: + Serial.println("IO ERROR"); + break; + case DMA_STATUS_ERR_TIMEOUT: + Serial.println("TIMEOUT"); + break; + case DMA_STATUS_BUSY: + Serial.println("BUSY"); + break; + case DMA_STATUS_SUSPEND: + Serial.println("SUSPENDED"); + break; + case DMA_STATUS_ABORTED: + Serial.println("ABORTED"); + break; + default: + Serial.print("Unknown 0x"); + Serial.println((int)s); + break; + } +} + +bool DMA_class::isActive() { + return _writeback[channel].BTCTRL.bit.VALID; +} diff --git a/FW/leo_muziekdoos_sam51/lib/DMA/DMA.h b/FW/leo_muziekdoos_sam51/lib/DMA/DMA.h new file mode 100644 index 0000000..d1b4133 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/lib/DMA/DMA.h @@ -0,0 +1,246 @@ +/*! + * @file Adafruit_ZeroDMA.h + * + * This is part of Adafruit's DMA library for SAMD microcontrollers on + * the Arduino platform. SAMD21 and SAMD51 lines are supported. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "PaintYourDragon" Burgess for Adafruit Industries, + * based partly on DMA insights from Atmel ASFCORE 3. + * + * MIT license, all text here must be included in any redistribution. + * + */ + +#ifndef _ADAFRUIT_ZERODMA_H_ +#define _ADAFRUIT_ZERODMA_H_ + +#include "Arduino.h" +#ifdef DMAC_RESERVED_CHANNELS // SAMD core > 1.2.1 +#include +#else +#include "utility/dma.h" +#endif + +/** Status codes returned by some DMA functions and/or held in + a channel's jobStatus variable. */ +enum DMAstatus { + DMA_STATUS_OK = 0, + DMA_STATUS_ERR_NOT_FOUND, + DMA_STATUS_ERR_NOT_INITIALIZED, + DMA_STATUS_ERR_INVALID_ARG, + DMA_STATUS_ERR_IO, + DMA_STATUS_ERR_TIMEOUT, + DMA_STATUS_BUSY, + DMA_STATUS_SUSPEND, + DMA_STATUS_ABORTED, + DMA_STATUS_JOBSTATUS = -1 // For printStatus() function +}; + +/*! + @brief Class encapsulating DMA jobs and descriptors. +*/ +class DMA_class { +public: + DMA_class(void); + + // DMA channel functions + + /*! + @brief Allocate channel for ZeroDMA object. This allocates a CHANNEL, + not a DESCRIPTOR. + @return ZeroDMAstatus type: + DMA_STATUS_OK on success. + DMA_STATUS_ERR_NOT_FOUND if no DMA channels are free. + + */ + DMAstatus allocate(void); + + /*! + @brief Start a previously allocated-and-configured DMA job. + @return ZeroDMAstatus type: + DMA_STATUS_OK on success. + DMA_STATUS_BUSY if resource is busy. + DMA_STATUS_ERR_NOT_INITIALIZED if attempting to start job + on a channel that failed to allocate. + DMA_STATUS_ERR_INVALID_ARG if a bad transfer size was specified. + */ + DMAstatus startJob(void); + + /*! + @brief Deallocates a previously-allocated DMA channel. This deallocates + the CHANNEL, not any associated DESCRIPTORS. + + @return ZeroDMAstatus type: + DMA_STATUS_OK on success. + DMA_STATUS_BUSY if channel is busy (can't deallocate while in use). + DMA_STATUS_ERR_NOT_INITIALIZED if channel isn't in use. + */ + DMAstatus free(void); + + /*! + @brief Activate a previously allocated-and-configured DMA channel's + software trigger. + */ + void trigger(void); + + /*! + @brief Set DMA peripheral trigger. This can be done before or after + channel is allocated. + @param trigger A device-specific DMA peripheral trigger ID, typically + defined in a header file associated with that chip. + Example triffer IDs might include SERCOM2_DMAC_ID_TX + (SERCOM transfer complete) ADC_DMAC_ID_RESRDY (ADC + results ready). + */ + void setTrigger(uint8_t trigger); + + /*! + @brief Set DMA trigger action. This can be done before or after + channel is allocated. + @param action One of DMA_TRIGGER_ACTON_BLOCK, DMA_TRIGGER_ACTON_BEAT or + DMA_TRIGGER_ACTON_TRANSACTION for desired behavior. + */ + void setAction(dma_transfer_trigger_action action); + + /*! + @brief Set and enable callback function for ZeroDMA object. This can be + called before or after channel and/or descriptors are allocated, + but needs to be called before job is started. + @param callback Pointer to callback function which accepts a pointer + to a ZeroDMA object (or NULL to disable callback). + @param type Which DMA operation to attach this function to (a + channel can have multiple callbacks assigned if needed, + one for each of these situations): + DMA_CALLBACK_DONE on successful completion of a DMA + transfer. + DMA_CALLBACK_TRANSFER_ERR if a bus error is detected + during an AHB access or when the DMAC fetches an + invalid descriptor. + DMA_CALLBACK_CHANNEL_SUSPEND when a channel is suspended. + */ + void setCallback(void (*callback)(DMA_class *) = NULL, + dma_callback_type type = DMA_CALLBACK_TRANSFER_DONE); + + /*! + @brief Select whether a channel's descriptor list should repeat or not. + This can be called before or after channel and any descriptors + are allocated. + @param flag 'true' if DMA descriptor list should repeat indefinitely, + 'false' if DMA transfer stops at end of descriptor list. + */ + void loop(boolean flag); + + /*! + @brief Suspend a DMA channel. AVOID USING FOR NOW. + */ + void suspend(void); + + /*! + @brief Resume a previously-suspended DMA channel. AVOID USING FOR NOW. + */ + void resume(void); + + /*! + @brief Cancel a DMA transfer operation. + */ + void abort(void); + + /*! + @brief Set DMA channel level priority. + @param pri DMA_PRIORITY_0 (lowest priority) through DMA_PRIORITY_3 + (highest priority). + */ + void setPriority(dma_priority pri); + + /*! + @brief Print (to Serial console) a string corresponding to a DMA + job status value. + @param s Job status as might be returned by allocate(), startJob(), + etc., e.g. DMA_STATUS_OK, DMA_STATUS_ERR_NOT_FOUND, ... + */ + void printStatus(DMAstatus s = DMA_STATUS_JOBSTATUS); + + /*! + @brief Get the DMA channel index associated with a ZeroDMA object. + @return uint8_t Channel index (0 to DMAC_CH_NUM-1, or 0xFF). + */ + uint8_t getChannel(void); + + // DMA descriptor functions + + /*! + @brief Allocate and append a DMA descriptor to a channel's descriptor + list. Channel must be allocated first. + @param src Source address. + @param dst Destination address. + @param count Transfer count. + @param size Per-count transfer size (DMA_BEAT_SIZE_BYTE, + DMA_BEAT_SIZE_HWORD or DMA_BEAT_SIZE_WORD for 8, 16, + 32 bits respectively). + @param srcInc If true, increment the source address following each + count. + @param dstInc If true, increment the destination address following + each count. + @param stepSize If source/dest address increment in use, this indicates + the 'step size' (allowing it to skip over elements). + DMA_ADDRESS_INCREMENT_STEP_SIZE_1 for a contiguous + transfer, "_SIZE_2 for alternate items, "_SIZE_4 + 8, 16, 32, 64 or 128 for other skip ranges. + @param stepSel DMA_STEPSEL_SRC or DMA_STEPSEL_DST depending which + pointer the step size should apply to (can't be used + on both simultaneously). + @return DmacDescriptor* Pointer to DmacDescriptor structure, or NULL + on various errors. Calling code should keep the + pointer for later if it needs to change or free + the descriptor. + */ + DmacDescriptor * + addDescriptor(void *src, void *dst, uint32_t count = 0, + dma_beat_size size = DMA_BEAT_SIZE_BYTE, bool srcInc = true, + bool dstInc = true, + uint32_t stepSize = DMA_ADDRESS_INCREMENT_STEP_SIZE_1, + bool stepSel = DMA_STEPSEL_DST); + + /*! + @brief Change a previously-allocated DMA descriptor. Only the most + common settings (source, dest, count) are available here. For + anything more esoteric, you'll need to modify the descriptor + structure yourself. + @param d Pointer to descriptor structure (as returned by + addDescriptor()). + @param src New source address. + @param dst New destination address. + @param count New transfer count. + */ + void changeDescriptor(DmacDescriptor *d, void *src = NULL, void *dst = NULL, + uint32_t count = 0); + + /*! + @brief Interrupt handler function, used internally by the library, + DO NOT TOUCH! + @param flags Channel number, actually. + */ + void _IRQhandler(uint8_t flags); + + /*! + @brief Test if DMA transfer is in-progress. Might be better to use + callback and flag, unsure. + @return 'true' if DMA channel is busy, 'false' otherwise. + */ + bool isActive(); + +protected: + uint8_t channel; ///< DMA channel index (0 to DMAC_CH_NUM-1, or 0xFF) + volatile enum DMAstatus jobStatus; ///< Last known DMA job status + bool hasDescriptors; ///< 'true' if one or more descriptors assigned + bool loopFlag; ///< 'true' if descriptor chain loops back to start + uint8_t peripheralTrigger; ///< Value set by setTrigger() + dma_transfer_trigger_action triggerAction; ///< Value set by setAction() + void (*callback[DMA_CALLBACK_N])(DMA_class *); ///< Callback func *s +}; + +#endif // _ADAFRUIT_ZERODMA_H_ diff --git a/FW/leo_muziekdoos_sam51/lib/I2S/I2S.cpp b/FW/leo_muziekdoos_sam51/lib/I2S/I2S.cpp new file mode 100644 index 0000000..46dbf46 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/lib/I2S/I2S.cpp @@ -0,0 +1,577 @@ +/*! + * @file Adafruit_ZeroI2S.cpp + * + * @mainpage Adafruit I2S peripheral driver for SAMD21 and SAMD51 chips + * + * @section intro_sec Introduction + * + * I2S peripheral driver for SAMD21 and SAMD51 chips + * + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * @section author Author + * + * Written by Dean Miller for Adafruit Industries. + * + * @section license License + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#include "I2S.h" +#include "wiring_private.h" + +#ifndef DEBUG_PRINTLN +#define DEBUG_PRINTLN Serial.println ///< where to print the debug output +#endif + +/**************************************************************************/ +/*! + @brief Class Constructor + @param FS_PIN frame sync pin + @param SCK_PIN bit clock pin + @param TX_PIN data output pin + @param RX_PIN data input pin +*/ +/**************************************************************************/ +I2S_class::I2S_class(uint8_t FS_PIN, uint8_t SCK_PIN, + uint8_t TX_PIN, uint8_t RX_PIN) + : _fs(FS_PIN), _sck(SCK_PIN), _tx(TX_PIN), _rx(RX_PIN) {} + +#if defined(PIN_I2S_SDI) && defined(PIN_I2S_SDO) +/**************************************************************************/ +/*! + @brief Class Constructor with defaults +*/ +/**************************************************************************/ +I2S_class::I2S_class() + : _fs(PIN_I2S_FS), _sck(PIN_I2S_SCK), _tx(PIN_I2S_SDO), _rx(PIN_I2S_SDI) {} +#else +/**************************************************************************/ +/*! + @brief Class Constructor with defaults +*/ +/**************************************************************************/ +Adafruit_ZeroI2S::Adafruit_ZeroI2S() + : _fs(PIN_I2S_FS), _sck(PIN_I2S_SCK), _tx(PIN_I2S_SD) { + _rx = -1; +} +#endif + +/**************************************************************************/ +/*! + @brief start up the I2S peripheral + @param width the width of each I2S frame + @param fs_freq the frame sync frequency (a.k.a. sample rate) + @param mck_mult master clock output will be fs_freq * mck_mult for chips + that have a mclk. + @returns true on success, false on any error +*/ +/**************************************************************************/ +bool I2S_class::begin(I2SSlotSize width, int fs_freq, int mck_mult) { +#if defined(__SAMD51__) + + pinPeripheral(_fs, PIO_I2S); + pinPeripheral(_sck, PIO_I2S); + pinPeripheral(_rx, PIO_I2S); + pinPeripheral(_tx, PIO_I2S); + + I2S->CTRLA.bit.ENABLE = 0; + + // initialize clock control + MCLK->APBDMASK.reg |= MCLK_APBDMASK_I2S; + + uint32_t mckFreq = (fs_freq * mck_mult); + uint32_t sckFreq = fs_freq * I2S_NUM_SLOTS * ((width + 1) << 3); + + uint32_t gclkval = GCLK_PCHCTRL_GEN_GCLK1_Val; + uint32_t gclkFreq = VARIANT_GCLK1_FREQ; + uint8_t mckoutdiv = min((gclkFreq / mckFreq) - 1U, 63U); + uint8_t mckdiv = min((gclkFreq / sckFreq) - 1U, 63U); + + if (((VARIANT_GCLK1_FREQ / mckFreq) - 1) > 63) { + gclkval = GCLK_PCHCTRL_GEN_GCLK4_Val; + gclkFreq = 12000000; + } + + GCLK->PCHCTRL[I2S_GCLK_ID_0].reg = gclkval | (1 << GCLK_PCHCTRL_CHEN_Pos); + GCLK->PCHCTRL[I2S_GCLK_ID_1].reg = gclkval | (1 << GCLK_PCHCTRL_CHEN_Pos); + + // software reset + I2S->CTRLA.bit.SWRST = 1; + while (I2S->SYNCBUSY.bit.SWRST || I2S->SYNCBUSY.bit.ENABLE) + ; // wait for sync + + // CLKCTRL[0] is used for the tx channel + I2S->CLKCTRL[0].reg = + I2S_CLKCTRL_MCKSEL_GCLK | I2S_CLKCTRL_MCKOUTDIV(mckoutdiv) | + I2S_CLKCTRL_MCKDIV(mckdiv) | I2S_CLKCTRL_SCKSEL_MCKDIV | + I2S_CLKCTRL_MCKEN | I2S_CLKCTRL_FSSEL_SCKDIV | I2S_CLKCTRL_BITDELAY_I2S | + I2S_CLKCTRL_FSWIDTH_HALF | I2S_CLKCTRL_NBSLOTS(I2S_NUM_SLOTS - 1) | + I2S_CLKCTRL_SLOTSIZE(width); + + uint8_t wordSize=0; + + switch (width) { + case I2S_8_BIT: + wordSize = I2S_TXCTRL_DATASIZE_8_Val; + break; + case I2S_16_BIT: + wordSize = I2S_TXCTRL_DATASIZE_16_Val; + break; + case I2S_24_BIT: + wordSize = I2S_TXCTRL_DATASIZE_24_Val; + break; + case I2S_32_BIT: + wordSize = I2S_TXCTRL_DATASIZE_32_Val; + break; + } + + I2S->TXCTRL.reg = I2S_TXCTRL_DMA_SINGLE | I2S_TXCTRL_MONO_STEREO | + I2S_TXCTRL_BITREV_MSBIT | I2S_TXCTRL_EXTEND_ZERO | + I2S_TXCTRL_WORDADJ_RIGHT | I2S_TXCTRL_DATASIZE(wordSize) | + I2S_TXCTRL_TXSAME_ZERO | I2S_TXCTRL_TXDEFAULT_ZERO; + + I2S->RXCTRL.reg = I2S_RXCTRL_DMA_SINGLE | I2S_RXCTRL_MONO_STEREO | + I2S_RXCTRL_BITREV_MSBIT | I2S_RXCTRL_EXTEND_ZERO | + I2S_RXCTRL_WORDADJ_RIGHT | I2S_RXCTRL_DATASIZE(wordSize) | + I2S_RXCTRL_SLOTADJ_RIGHT | I2S_RXCTRL_CLKSEL_CLK0 | + I2S_RXCTRL_SERMODE_RX; + + while (I2S->SYNCBUSY.bit.ENABLE) + ; // wait for sync + I2S->CTRLA.bit.ENABLE = 1; + + return true; + +#else // SAMD21 + _i2sserializer = -1; + _i2sclock = -1; + uint32_t _clk_pin, _clk_mux, _data_pin, _data_mux, _fs_pin, _fs_mux; + + // Clock pin, can only be one of 3 options + uint32_t clockport = g_APinDescription[_sck].ulPort; + uint32_t clockpin = g_APinDescription[_sck].ulPin; + if ((clockport == 0) && (clockpin == 10)) { + // PA10 + _i2sclock = 0; + _clk_pin = PIN_PA10G_I2S_SCK0; + _clk_mux = MUX_PA10G_I2S_SCK0; +#if defined(PIN_PB11G_I2S_SCK1) + } else if ((clockport == 1) && (clockpin == 11)) { + // PB11 + _i2sclock = 1; + _clk_pin = PIN_PB11G_I2S_SCK1; + _clk_mux = MUX_PB11G_I2S_SCK1; +#endif +#if defined(PIN_PA20G_I2S_SCK0) + } else if ((clockport == 0) && (clockpin == 20)) { + // PA20 + _i2sclock = 0; + _clk_pin = PIN_PA20G_I2S_SCK0; + _clk_mux = MUX_PA20G_I2S_SCK0; +#endif + } else { + DEBUG_PRINTLN("Clock isnt on a valid pin"); + return false; + } + pinPeripheral(_sck, (EPioType)_clk_mux); + + // FS pin, can only be one of 2 options + uint32_t fsport = g_APinDescription[_fs].ulPort; + uint32_t fspin = g_APinDescription[_fs].ulPin; + if ((fsport == 0) && (fspin == 11)) { + // PA11 + _fs_pin = PIN_PA11G_I2S_FS0; + _fs_mux = MUX_PA11G_I2S_FS0; +#if defined(PIN_PA21G_I2S_FS0) + } else if ((fsport == 0) && (fspin == 21)) { + // PA21 + _fs_pin = PIN_PA21G_I2S_FS0; + _fs_mux = MUX_PA21G_I2S_FS0; +#endif + } else { + DEBUG_PRINTLN("FS isnt on a valid pin"); + return false; + } + pinPeripheral(_fs, (EPioType)_fs_mux); + + uint32_t i2sGCLK; + if (_i2sclock == 0) + i2sGCLK = I2S_GCLK_ID_0; + else + i2sGCLK = I2S_GCLK_ID_1; + + uint32_t divider = fs_freq * 2 * (width + 1) * 8; + // configure the clock divider + while (GCLK->STATUS.bit.SYNCBUSY) + ; + GCLK->GENDIV.bit.ID = I2S_CLOCK_GENERATOR; + GCLK->GENDIV.bit.DIV = SystemCoreClock / divider; + + // use the DFLL as the source + while (GCLK->STATUS.bit.SYNCBUSY) + ; + GCLK->GENCTRL.bit.ID = I2S_CLOCK_GENERATOR; + GCLK->GENCTRL.bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val; + GCLK->GENCTRL.bit.IDC = 1; + GCLK->GENCTRL.bit.GENEN = 1; + + // enable + while (GCLK->STATUS.bit.SYNCBUSY) + ; + GCLK->CLKCTRL.bit.ID = i2sGCLK; + GCLK->CLKCTRL.bit.GEN = I2S_CLOCK_GENERATOR; + GCLK->CLKCTRL.bit.CLKEN = 1; + + while (GCLK->STATUS.bit.SYNCBUSY) + ; + + // Data pin, can only be one of 3 options + uint32_t datapin = g_APinDescription[_tx].ulPin; + uint32_t dataport = g_APinDescription[_tx].ulPort; + if ((dataport == 0) && (datapin == 7)) { + // PA07 + _i2sserializer = 0; + _data_pin = PIN_PA07G_I2S_SD0; + _data_mux = MUX_PA07G_I2S_SD0; + } else if ((dataport == 0) && (datapin == 8)) { + // PA08 + _i2sserializer = 1; + _data_pin = PIN_PA08G_I2S_SD1; + _data_mux = MUX_PA08G_I2S_SD1; + } else if ((dataport == 0) && (datapin == 19)) { + // PA19 + _i2sserializer = 0; + _data_pin = PIN_PA19G_I2S_SD0; + _data_mux = MUX_PA19G_I2S_SD0; + } else { + DEBUG_PRINTLN("Data isnt on a valid pin"); + return false; + } + pinPeripheral(_tx, (EPioType)_data_mux); + + PM->APBCMASK.reg |= PM_APBCMASK_I2S; + + I2S->CTRLA.bit.ENABLE = 0; + while (I2S->SYNCBUSY.bit.ENABLE) + ; + + if (_i2sclock == 0) + I2S->CTRLA.bit.CKEN0 = 0; + else + I2S->CTRLA.bit.CKEN1 = 0; + while (I2S->SYNCBUSY.bit.CKEN0 || I2S->SYNCBUSY.bit.CKEN1) + ; + + I2S->CLKCTRL[_i2sclock].reg = + I2S_CLKCTRL_MCKSEL_GCLK | I2S_CLKCTRL_SCKSEL_MCKDIV | + I2S_CLKCTRL_FSSEL_SCKDIV | I2S_CLKCTRL_BITDELAY_I2S | + I2S_CLKCTRL_NBSLOTS(I2S_NUM_SLOTS - 1) | I2S_CLKCTRL_SLOTSIZE(width); + + uint8_t wordSize; + switch (width) { + case I2S_8_BIT: + wordSize = I2S_SERCTRL_DATASIZE_8_Val; + break; + case I2S_16_BIT: + wordSize = I2S_SERCTRL_DATASIZE_16_Val; + break; + case I2S_24_BIT: + wordSize = I2S_SERCTRL_DATASIZE_24_Val; + break; + case I2S_32_BIT: + wordSize = I2S_SERCTRL_DATASIZE_32_Val; + break; + default: + DEBUG_PRINTLN("invalid width!"); + return false; + } + + if (_i2sserializer == 0) + I2S->CTRLA.bit.SEREN0 = 0; + else + I2S->CTRLA.bit.SEREN1 = 0; + while (I2S->SYNCBUSY.bit.SEREN0 || I2S->SYNCBUSY.bit.SEREN1) + ; + + I2S->SERCTRL[_i2sserializer].reg = + I2S_SERCTRL_DMA_SINGLE | I2S_SERCTRL_MONO_STEREO | + I2S_SERCTRL_BITREV_MSBIT | I2S_SERCTRL_EXTEND_ZERO | + I2S_SERCTRL_WORDADJ_RIGHT | I2S_SERCTRL_DATASIZE(wordSize) | + I2S_SERCTRL_SLOTADJ_RIGHT | + ((uint32_t)_i2sclock << I2S_SERCTRL_CLKSEL_Pos); + + return true; +#endif +} + +/**************************************************************************/ +/*! + @brief enable data output. Note that on SAMD21 chips either rx or tx can be + enabled on an Adafruit_ZeroI2S instance, while on SAMD51 the same + Adafruit_ZeroI2S instance can have both rx and tx channels enabled. +*/ +/**************************************************************************/ +void I2S_class::enableTx() { +#if defined(__SAMD51__) + I2S->CTRLA.bit.CKEN0 = 1; + while (I2S->SYNCBUSY.bit.CKEN0) + ; + + I2S->CTRLA.bit.TXEN = 1; + while (I2S->SYNCBUSY.bit.TXEN) + ; +#else + if (_i2sserializer > -1 && _i2sclock > -1) { + I2S->CTRLA.bit.ENABLE = 0; + while (I2S->SYNCBUSY.bit.ENABLE) + ; + + I2S->SERCTRL[_i2sserializer].bit.SERMODE = I2S_SERCTRL_SERMODE_TX; + + if (_i2sserializer == 0) + I2S->CTRLA.bit.SEREN0 = 1; + else + I2S->CTRLA.bit.SEREN1 = 1; + + if (_i2sclock == 0) + I2S->CTRLA.bit.CKEN0 = 1; + else + I2S->CTRLA.bit.CKEN1 = 1; + + I2S->CTRLA.bit.ENABLE = 1; + while (I2S->SYNCBUSY.bit.ENABLE || I2S->SYNCBUSY.bit.CKEN0 || + I2S->SYNCBUSY.bit.CKEN1 || I2S->SYNCBUSY.bit.SEREN0 || + I2S->SYNCBUSY.bit.SEREN1) + ; + } +#endif +} + +/**************************************************************************/ +/*! + @brief disable data output +*/ +/**************************************************************************/ +void I2S_class::disableTx() { +#if defined(__SAMD51__) + I2S->CTRLA.bit.TXEN = 0; + while (I2S->SYNCBUSY.bit.TXEN) + ; +#else +#endif +} + +/**************************************************************************/ +/*! + @brief enable data input. Note that on SAMD21 chips either rx or tx can be + enabled on an Adafruit_ZeroI2S instance, while on SAMD51 the same + Adafruit_ZeroI2S instance can have both rx and tx channels enabled. +*/ +/**************************************************************************/ +void I2S_class::enableRx() { +#if defined(__SAMD51__) + I2S->CTRLA.bit.CKEN0 = 1; + while (I2S->SYNCBUSY.bit.CKEN0) + ; + + I2S->CTRLA.bit.RXEN = 1; + while (I2S->SYNCBUSY.bit.RXEN) + ; +#else + if (_i2sserializer > -1 && _i2sclock > -1) { + I2S->CTRLA.bit.ENABLE = 0; + while (I2S->SYNCBUSY.bit.ENABLE) + ; + + I2S->SERCTRL[_i2sserializer].bit.SERMODE = I2S_SERCTRL_SERMODE_RX; + + if (_i2sserializer == 0) + I2S->CTRLA.bit.SEREN0 = 1; + else + I2S->CTRLA.bit.SEREN1 = 1; + + if (_i2sclock == 0) + I2S->CTRLA.bit.CKEN0 = 1; + else + I2S->CTRLA.bit.CKEN1 = 1; + + I2S->CTRLA.bit.ENABLE = 1; + while (I2S->SYNCBUSY.bit.ENABLE || I2S->SYNCBUSY.bit.CKEN0 || + I2S->SYNCBUSY.bit.CKEN1 || I2S->SYNCBUSY.bit.SEREN0 || + I2S->SYNCBUSY.bit.SEREN1) + ; + } +#endif +} + +/**************************************************************************/ +/*! + @brief disable data input +*/ +/**************************************************************************/ +void I2S_class::disableRx() { +#if defined(__SAMD51__) + I2S->CTRLA.bit.RXEN = 0; + while (I2S->SYNCBUSY.bit.RXEN) + ; +#else +#endif +} + +/**************************************************************************/ +/*! + @brief enable master clock output on devices that have a master clock + output. +*/ +/**************************************************************************/ +void I2S_class::enableMCLK() { +#ifdef PIN_I2S_MCK + pinPeripheral(PIN_I2S_MCK, PIO_I2S); +#endif +} + +/**************************************************************************/ +/*! + @brief disable master clock output on devices that have a master clock + output. +*/ +/**************************************************************************/ +void I2S_class::disableMCLK() { +#ifdef PIN_I2S_MCK + pinMode(PIN_I2S_MCK, INPUT); +#endif +} + +/**************************************************************************/ +/*! + @brief check if data can be written to the TX data register + @returns true if data can be written, false otherwise +*/ +/**************************************************************************/ +bool I2S_class::txReady() { +#if defined(__SAMD51__) + return !((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.TXDATA); +#else + if (_i2sserializer > -1) { + if (_i2sserializer == 0) { + return !((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.DATA0); + } else { + return !((!I2S->INTFLAG.bit.TXRDY1) || I2S->SYNCBUSY.bit.DATA1); + } + } else + return false; +#endif +} + +/**************************************************************************/ +/*! + @brief check if data is available to be read from the RX data register + @returns true if data is available, false otherwise +*/ +/**************************************************************************/ +bool I2S_class::rxReady() { +#if defined(__SAMD51__) + return !((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.RXDATA); +#else + if (_i2sserializer > -1) { + if (_i2sserializer == 0) { + return !((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.DATA0); + } else { + return !((!I2S->INTFLAG.bit.RXRDY1) || I2S->SYNCBUSY.bit.DATA1); + } + } else + return false; +#endif +} + +/**************************************************************************/ +/*! + @brief perform a blocking write to the I2S peripheral. This function will + only return once all data has been sent. + @param left the left channel data + @param right the right channel data +*/ +/**************************************************************************/ +void I2S_class::write(int32_t left, int32_t right) { +#if defined(__SAMD51__) + while ((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.TXDATA) + ; + I2S->INTFLAG.bit.TXUR0 = 1; + I2S->TXDATA.reg = left; + + while ((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.TXDATA) + ; + I2S->INTFLAG.bit.TXUR0 = 1; + I2S->TXDATA.reg = right; +#else + if (_i2sserializer > -1) { + if (_i2sserializer == 0) { + while ((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.DATA0) + ; + I2S->INTFLAG.bit.TXUR0 = 1; + I2S->DATA[0].reg = left; + + while ((!I2S->INTFLAG.bit.TXRDY0) || I2S->SYNCBUSY.bit.DATA0) + ; + I2S->INTFLAG.bit.TXUR0 = 1; + I2S->DATA[0].reg = right; + } else { + while ((!I2S->INTFLAG.bit.TXRDY1) || I2S->SYNCBUSY.bit.DATA1) + ; + I2S->INTFLAG.bit.TXUR1 = 1; + I2S->DATA[1].reg = left; + + while ((!I2S->INTFLAG.bit.TXRDY1) || I2S->SYNCBUSY.bit.DATA1) + ; + I2S->INTFLAG.bit.TXUR1 = 1; + I2S->DATA[1].reg = right; + } + } +#endif +} + +/**************************************************************************/ +/*! + @brief perform a blocking read to the I2S peripheral. This function will + only return once all data has been read. + @param left pointer to where the left channel data will be written + @param right pointer to where the right channel data will be written +*/ +/**************************************************************************/ +void I2S_class::read(int32_t *left, int32_t *right) { +#if defined(__SAMD51__) + while ((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.RXDATA) + ; + *left = I2S->RXDATA.reg; + + while ((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.RXDATA) + ; + *right = I2S->RXDATA.reg; +#else + if (_i2sserializer > -1) { + if (_i2sserializer == 0) { + while ((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.DATA0) + ; + *left = I2S->DATA[0].reg; + + while ((!I2S->INTFLAG.bit.RXRDY0) || I2S->SYNCBUSY.bit.DATA0) + ; + *right = I2S->DATA[0].reg; + } else { + while ((!I2S->INTFLAG.bit.RXRDY1) || I2S->SYNCBUSY.bit.DATA1) + ; + *left = I2S->DATA[1].reg; + + while ((!I2S->INTFLAG.bit.RXRDY1) || I2S->SYNCBUSY.bit.DATA1) + ; + *right = I2S->DATA[1].reg; + } + } +#endif +} diff --git a/FW/leo_muziekdoos_sam51/lib/I2S/I2S.h b/FW/leo_muziekdoos_sam51/lib/I2S/I2S.h new file mode 100644 index 0000000..f5b5dca --- /dev/null +++ b/FW/leo_muziekdoos_sam51/lib/I2S/I2S.h @@ -0,0 +1,75 @@ + +/*! + * @file Adafruit_ZeroI2S.h + * + * This is a library for the I2S peripheral on SAMD21 and SAMD51 devices + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Dean Miller for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#ifndef ADAFRUIT_ZEROI2S_H +#define ADAFRUIT_ZEROI2S_H + +#include + +/**************************************************************************/ +/*! + @brief available I2S slot sizes +*/ +/**************************************************************************/ +typedef enum _I2SSlotSize { + I2S_8_BIT = 0, + I2S_16_BIT, + I2S_24_BIT, + I2S_32_BIT +} I2SSlotSize; + +/**************************************************************************/ +/*! + @brief number of I2S slots to use (stereo) +*/ +/**************************************************************************/ +#define I2S_NUM_SLOTS 2 + +/**************************************************************************/ +/*! + @brief Class that stores state and functions for interacting with I2S + peripheral on SAMD21 and SAMD51 devices +*/ +/**************************************************************************/ +class I2S_class { +public: + I2S_class(uint8_t FS_PIN, uint8_t SCK_PIN, uint8_t TX_PIN, + uint8_t RX_PIN); + I2S_class(); + ~I2S_class() {} + + bool begin(I2SSlotSize width, int fs_freq, int mck_mult = 256); + + void enableTx(); + void disableTx(); + void enableRx(); + void disableRx(); + void enableMCLK(); + void disableMCLK(); + + bool txReady(); + bool rxReady(); + void write(int32_t left, int32_t right); + void read(int32_t *left, int32_t *right); + +private: + int8_t _fs, _sck, _tx, _rx; +#ifndef __SAMD51__ + int8_t _i2sserializer, _i2sclock; +#endif +}; + +#endif diff --git a/FW/leo_muziekdoos_sam51/src/audio.cpp b/FW/leo_muziekdoos_sam51/src/audio.cpp new file mode 100644 index 0000000..4c6974c --- /dev/null +++ b/FW/leo_muziekdoos_sam51/src/audio.cpp @@ -0,0 +1,106 @@ +// Arduino Zero / Feather M0 I2S audio tone generation example. +// Author: Tony DiCola +// +// Connect an I2S DAC or amp (like the UDA1334A) to the Arduino Zero +// and play back simple sine, sawtooth, triangle, and square waves. +// Makes your Zero sound like a NES! +// +// NOTE: The I2S signal generated by the Zero does NOT have a MCLK / +// master clock signal. You must use an I2S receiver that can operate +// without a MCLK signal (like the UDA1334A). +// +// For an Arduino Zero / Feather M0 connect it to you I2S hardware as follows: +// - Digital 0 -> I2S LRCLK / FS (left/right / frame select clock) +// - Digital 1 -> I2S BCLK / SCLK (bit / serial clock) +// - Digital 9 -> I2S DIN / SD (data output) +// - Ground +// +// Released under a MIT license: https://opensource.org/licenses/MIT +#include "audio.h" + +#include +#include +#include "utility/dma.h" +#include + +#include "storage.h" + +/* max volume for 32 bit data */ +#define VOLUME ( (1UL << 31) - 1) + +/* create a buffer for both the left and right channel data */ +#define BUFSIZE 256 +int data[BUFSIZE]; + +DMA_class myDMA; +DMAstatus stat; // DMA status codes returned by some functions + +I2S_class i2s; + +bool transfer_is_done = false; +bool playbackIsDone = true; + +void dma_callback(DMA_class *dma) { + /* we don't need to do anything here */ + transfer_is_done = true; +} + +void initAudio(void) +{ + Serial.begin(115200); + //while(!Serial); // Wait for Serial monitor before continuing + + Serial.println("I2S output via DMA"); + + int *ptr = data; + + /*the I2S module will be expecting data interleaved LRLR*/ + for(int i=0; iTXDATA.reg), // to here (M4) +#else + (void *)(&I2S->DATA[0].reg), // to here (M0+) +#endif + BUFSIZE, // this many... + DMA_BEAT_SIZE_WORD, // bytes/hword/words + true, // increment source addr? + false); + myDMA.loop(true); + Serial.println("Adding callback"); + myDMA.setCallback(dma_callback); + + /* begin I2S on the default pins. 24 bit depth at + * 44100 samples per second + */ + i2s.begin(I2S_32_BIT, 44100); + i2s.enableTx(); + + stat = myDMA.startJob(); +} + +uint32_t filendex = 0; +uint32_t filesize = 0; +File musicFileHandle; + +void handleAudio(void) +{ + +} \ No newline at end of file diff --git a/FW/leo_muziekdoos_sam51/src/audio.h b/FW/leo_muziekdoos_sam51/src/audio.h new file mode 100644 index 0000000..c542873 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/src/audio.h @@ -0,0 +1,7 @@ +#pragma once + +#include "arduino.h" + + +void initAudio(void); +void handleAudio(void); \ No newline at end of file diff --git a/FW/leo_muziekdoos_sam51/src/main.cpp b/FW/leo_muziekdoos_sam51/src/main.cpp new file mode 100644 index 0000000..ce22fe3 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/src/main.cpp @@ -0,0 +1,14 @@ +#include "audio.h" +#include "storage.h" + +void setup() +{ + initAudio(); + initStorage(); +} + +void loop() +{ + handleAudio(); + handleStorage(); +} diff --git a/FW/leo_muziekdoos_sam51/src/storage.cpp b/FW/leo_muziekdoos_sam51/src/storage.cpp new file mode 100644 index 0000000..f9ed0a1 --- /dev/null +++ b/FW/leo_muziekdoos_sam51/src/storage.cpp @@ -0,0 +1,57 @@ +#include "storage.h" + +File root; + + +void printDirectory(File dir, int numTabs) { + // Begin at the start of the directory + dir.rewindDirectory(); + + while(true) { + File entry = dir.openNextFile(); + if (! entry) { + // no more files + //Serial.println("**nomorefiles**"); + break; + } + for (uint8_t i=0; i