working audio
This commit is contained in:
@@ -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
|
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
|
#define I2S_BUFFER_SIZE 64
|
||||||
I2S_HandleTypeDef hi2s3;
|
I2S_HandleTypeDef hi2s4;
|
||||||
DMA_HandleTypeDef hdma_spi3_tx;
|
DMA_HandleTypeDef hdma_spi4_tx;
|
||||||
uint32_t dma_buffer[I2S_BUFFER_SIZE];
|
uint32_t dma_buffer[I2S_BUFFER_SIZE];
|
||||||
//int16_t sine[WAV_SIZE] = {0};
|
//int16_t sine[WAV_SIZE] = {0};
|
||||||
// sinus oszillator
|
// sinus oszillator
|
||||||
float osc_phi = 0;
|
float osc_phi = 0;
|
||||||
float osc_phi_inc = 440.0f / 44100.0f; // generating 440HZ
|
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);
|
void MX_DMA_Init(void);
|
||||||
|
|
||||||
extern "C" void DMA1_Stream5_IRQHandler(void) // this function must be included to avoid DMA to crash!
|
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)
|
void Error_Handler2(byte errorcode)
|
||||||
@@ -88,17 +90,16 @@ extern "C" void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
|
|||||||
// setting up I2S
|
// setting up I2S
|
||||||
extern "C" void MX_I2S3_Init(void)
|
extern "C" void MX_I2S3_Init(void)
|
||||||
{
|
{
|
||||||
hi2s3.Instance = SPI3;
|
hi2s4.Instance = SPI4;
|
||||||
hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
|
hi2s4.Init.Mode = I2S_MODE_MASTER_TX;
|
||||||
hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
|
hi2s4.Init.Standard = I2S_STANDARD_PHILIPS;
|
||||||
hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
|
hi2s4.Init.DataFormat = I2S_DATAFORMAT_16B;
|
||||||
//hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
|
hi2s4.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
|
||||||
hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
|
hi2s4.Init.AudioFreq = I2S_AUDIOFREQ_44K;
|
||||||
hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_44K;
|
hi2s4.Init.CPOL = I2S_CPOL_LOW;
|
||||||
hi2s3.Init.CPOL = I2S_CPOL_LOW;
|
hi2s4.Init.ClockSource = I2S_CLOCK_PLL;
|
||||||
hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
|
hi2s4.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
|
||||||
hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
|
if (HAL_I2S_Init(&hi2s4) != HAL_OK)
|
||||||
if (HAL_I2S_Init(&hi2s3) != HAL_OK)
|
|
||||||
{
|
{
|
||||||
Error_Handler2(1); // on error: one blink
|
Error_Handler2(1); // on error: one blink
|
||||||
}
|
}
|
||||||
@@ -134,45 +135,51 @@ extern "C" void HAL_I2S_MspInit(I2S_HandleTypeDef *hi2s)
|
|||||||
PC7 MCK
|
PC7 MCK
|
||||||
*/
|
*/
|
||||||
//I2S3 used GPIO configuration in this example:
|
//I2S3 used GPIO configuration in this example:
|
||||||
// PB5 DIN / SD
|
// PB5 DIN / SD ==> PA1
|
||||||
// PA4 LRC /WD
|
// PA4 LRC /WD ==> PB12
|
||||||
// PB3 SCLK /CK
|
// PB3 SCLK /CK ==> PB13
|
||||||
// PC7 MCK
|
// PC7 MCK
|
||||||
GPIO_InitStruct.Pin = GPIO_PIN_4;
|
|
||||||
|
//LRCLL
|
||||||
|
GPIO_InitStruct.Pin = GPIO_PIN_12;
|
||||||
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
||||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||||
GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
|
GPIO_InitStruct.Alternate = GPIO_AF6_SPI4;
|
||||||
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;
|
|
||||||
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
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.Mode = GPIO_MODE_AF_PP;
|
||||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||||
GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
|
GPIO_InitStruct.Alternate = GPIO_AF5_SPI4;
|
||||||
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
|
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
|
// Peripheral DMA init
|
||||||
hdma_spi3_tx.Instance = DMA1_Stream5;
|
hdma_spi4_tx.Instance = DMA1_Stream5;
|
||||||
hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
|
hdma_spi4_tx.Init.Channel = DMA_CHANNEL_0;
|
||||||
hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
|
hdma_spi4_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
|
||||||
hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
|
hdma_spi4_tx.Init.PeriphInc = DMA_PINC_DISABLE;
|
||||||
hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;
|
hdma_spi4_tx.Init.MemInc = DMA_MINC_ENABLE;
|
||||||
hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
|
hdma_spi4_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
|
||||||
hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
|
hdma_spi4_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
|
||||||
hdma_spi3_tx.Init.Mode = DMA_CIRCULAR;
|
hdma_spi4_tx.Init.Mode = DMA_CIRCULAR;
|
||||||
hdma_spi3_tx.Init.Priority = DMA_PRIORITY_LOW;
|
hdma_spi4_tx.Init.Priority = DMA_PRIORITY_LOW;
|
||||||
hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
|
hdma_spi4_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
|
||||||
if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK)
|
if (HAL_DMA_Init(&hdma_spi4_tx) != HAL_OK)
|
||||||
{
|
{
|
||||||
Error_Handler2(5); // on error: five blinks
|
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
|
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);
|
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void initAudio()
|
void initAudio()
|
||||||
{
|
{
|
||||||
// HAL_MspInit(); // not important by default
|
// 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_DMA_Init();
|
||||||
MX_I2S3_Init();
|
MX_I2S3_Init();
|
||||||
pinMode(LED_BUILTIN, OUTPUT);
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
pinMode(PIN_I2S_SDMODE, OUTPUT);
|
pinMode(PIN_I2S_SDMODE, OUTPUT);
|
||||||
digitalWrite(PIN_I2S_SDMODE, HIGH);
|
digitalWrite(PIN_I2S_SDMODE, HIGH);
|
||||||
digitalWrite(LED_BUILTIN, 0);
|
digitalWrite(LED_BUILTIN, 0);
|
||||||
StartAudioBuffers(&hi2s3);
|
StartAudioBuffers(&hi2s4);
|
||||||
digitalWrite(LED_BUILTIN, 1);
|
digitalWrite(LED_BUILTIN, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,28 +223,34 @@ void handleAudio()
|
|||||||
// just a dummy code in loop, audio out is generated by ISR
|
// just a dummy code in loop, audio out is generated by ISR
|
||||||
digitalWrite(LED_BUILTIN, 1);
|
digitalWrite(LED_BUILTIN, 1);
|
||||||
delay(250);
|
delay(250);
|
||||||
//HAL_I2S_Transmit(&hi2s3,&sine,WAV_SIZE,1000);
|
//HAL_I2S_Transmit(&hi2s4,&sine,WAV_SIZE,1000);
|
||||||
HAL_StatusTypeDef res;
|
HAL_StatusTypeDef res;
|
||||||
int16_t signal[46876];
|
int16_t signal[4096];
|
||||||
int nsamples = sizeof(signal) / sizeof(signal[0]);
|
int nsamples = sizeof(signal) / sizeof(signal[0]);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while(i < nsamples) {
|
while (i < nsamples)
|
||||||
double t = ((double)i/2.0)/((double)nsamples);
|
{
|
||||||
signal[i] = 32767*sin(100.0 * TAU * t); // left
|
double t = ((double)i / 2.0) / ((double)nsamples);
|
||||||
signal[i+1] = signal[i]; // right
|
signal[i] = 32767 * sin(100.0 * TAU * t); // left
|
||||||
|
signal[i + 1] = signal[i]; // right
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(1) {
|
while (1)
|
||||||
res = HAL_I2S_Transmit(&hi2s3, (uint16_t*)signal, nsamples, HAL_MAX_DELAY);
|
{
|
||||||
if(res != HAL_OK) {
|
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);
|
Serial.printf("I2S - ERROR, res = %d!\r\n", res);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.printf("I2S - HAL_OK\r\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
digitalWrite(LED_BUILTIN, 0);
|
digitalWrite(LED_BUILTIN, 0);
|
||||||
delay(250);
|
delay(250);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,6 @@
|
|||||||
#define B4_HZ 493.88
|
#define B4_HZ 493.88
|
||||||
|
|
||||||
// Define a C-major scale to play all the notes up and down.
|
// 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 initAudio(void);
|
||||||
void handleAudio(void);
|
void handleAudio(void);
|
||||||
|
|||||||
707
FW/leo_muziekdoos_sam51/lib/DMA/DMA.cpp
Normal file
707
FW/leo_muziekdoos_sam51/lib/DMA/DMA.cpp
Normal file
@@ -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 <malloc.h> // memalign() function
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef USE_TINYUSB
|
||||||
|
// For Serial when selecting TinyUSB
|
||||||
|
#include <Adafruit_TinyUSB.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DMAC_RESERVED_CHANNELS // SAMD core > 1.2.1
|
||||||
|
#include <dma.h> // _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;
|
||||||
|
}
|
||||||
246
FW/leo_muziekdoos_sam51/lib/DMA/DMA.h
Normal file
246
FW/leo_muziekdoos_sam51/lib/DMA/DMA.h
Normal file
@@ -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 <dma.h>
|
||||||
|
#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_
|
||||||
577
FW/leo_muziekdoos_sam51/lib/I2S/I2S.cpp
Normal file
577
FW/leo_muziekdoos_sam51/lib/I2S/I2S.cpp
Normal file
@@ -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
|
||||||
|
}
|
||||||
75
FW/leo_muziekdoos_sam51/lib/I2S/I2S.h
Normal file
75
FW/leo_muziekdoos_sam51/lib/I2S/I2S.h
Normal file
@@ -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 <Arduino.h>
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@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
|
||||||
106
FW/leo_muziekdoos_sam51/src/audio.cpp
Normal file
106
FW/leo_muziekdoos_sam51/src/audio.cpp
Normal file
@@ -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 <I2S.h>
|
||||||
|
#include <DMA.h>
|
||||||
|
#include "utility/dma.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#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; i<BUFSIZE/2; i++){
|
||||||
|
/* create a sine wave on the left channel */
|
||||||
|
*ptr++ = sin( (2*PI / (BUFSIZE/2) ) * i) * VOLUME;
|
||||||
|
|
||||||
|
/* create a cosine wave on the right channel */
|
||||||
|
*ptr++ = cos( (2*PI / (BUFSIZE/2) ) * i) * VOLUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Configuring DMA trigger");
|
||||||
|
myDMA.setTrigger(I2S_DMAC_ID_TX_0);
|
||||||
|
myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);
|
||||||
|
|
||||||
|
Serial.print("Allocating DMA channel...");
|
||||||
|
stat = myDMA.allocate();
|
||||||
|
myDMA.printStatus(stat);
|
||||||
|
|
||||||
|
Serial.println("Setting up transfer");
|
||||||
|
myDMA.addDescriptor(
|
||||||
|
data, // move data from here
|
||||||
|
#if defined(__SAMD51__)
|
||||||
|
(void *)(&I2S->TXDATA.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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
7
FW/leo_muziekdoos_sam51/src/audio.h
Normal file
7
FW/leo_muziekdoos_sam51/src/audio.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "arduino.h"
|
||||||
|
|
||||||
|
|
||||||
|
void initAudio(void);
|
||||||
|
void handleAudio(void);
|
||||||
14
FW/leo_muziekdoos_sam51/src/main.cpp
Normal file
14
FW/leo_muziekdoos_sam51/src/main.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "audio.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
initAudio();
|
||||||
|
initStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
handleAudio();
|
||||||
|
handleStorage();
|
||||||
|
}
|
||||||
57
FW/leo_muziekdoos_sam51/src/storage.cpp
Normal file
57
FW/leo_muziekdoos_sam51/src/storage.cpp
Normal file
@@ -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<numTabs; i++) {
|
||||||
|
Serial.print('\t'); // we'll have a nice indentation
|
||||||
|
}
|
||||||
|
// Print the 8.3 name
|
||||||
|
Serial.print(entry.name());
|
||||||
|
// Recurse for directories, otherwise print the file size
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Serial.println("/");
|
||||||
|
printDirectory(entry, numTabs+1);
|
||||||
|
} else {
|
||||||
|
// files have sizes, directories do not
|
||||||
|
Serial.print("\t\t");
|
||||||
|
Serial.println(entry.size(), DEC);
|
||||||
|
}
|
||||||
|
entry.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initStorage(void)
|
||||||
|
{
|
||||||
|
if(!SD.begin(A5))
|
||||||
|
{
|
||||||
|
Serial.println("Failed to init SDcard");
|
||||||
|
}
|
||||||
|
|
||||||
|
root = SD.open("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStorage(void)
|
||||||
|
{
|
||||||
|
//printDirectory(root, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
File getmusicFile(void)
|
||||||
|
{
|
||||||
|
if(SD.exists("/003.WAV"))
|
||||||
|
{
|
||||||
|
return SD.open("/003.WAV");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
FW/leo_muziekdoos_sam51/src/storage.h
Normal file
9
FW/leo_muziekdoos_sam51/src/storage.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SPI.h"
|
||||||
|
#include "SD.h"
|
||||||
|
|
||||||
|
void initStorage(void);
|
||||||
|
void handleStorage(void);
|
||||||
|
|
||||||
|
File getmusicFile(void);
|
||||||
BIN
Tunes/001 - Pure Shores.mp3
Executable file
BIN
Tunes/001 - Pure Shores.mp3
Executable file
Binary file not shown.
BIN
Tunes/002 - Never Ever.mp3
Executable file
BIN
Tunes/002 - Never Ever.mp3
Executable file
Binary file not shown.
BIN
Tunes/003.mp3
Executable file
BIN
Tunes/003.mp3
Executable file
Binary file not shown.
BIN
Tunes/003_short.wav
Normal file
BIN
Tunes/003_short.wav
Normal file
Binary file not shown.
BIN
Tunes/003_short_24k.wav
Normal file
BIN
Tunes/003_short_24k.wav
Normal file
Binary file not shown.
BIN
Tunes/003_short_24k_8u.wav
Normal file
BIN
Tunes/003_short_24k_8u.wav
Normal file
Binary file not shown.
BIN
Tunes/004.mp3
Executable file
BIN
Tunes/004.mp3
Executable file
Binary file not shown.
Reference in New Issue
Block a user