commit
10eabf6bc2
|
@ -220,7 +220,10 @@ endif
|
|||
SRC_ASF := $(addprefix asf4/$(CHIP_FAMILY)/, $(SRC_ASF))
|
||||
|
||||
SRC_C = \
|
||||
audio_dma.c \
|
||||
background.c \
|
||||
clocks.c \
|
||||
events.c \
|
||||
fatfs_port.c \
|
||||
flash_api.c \
|
||||
mphalport.c \
|
||||
|
@ -276,7 +279,7 @@ SRC_COMMON_HAL = \
|
|||
neopixel_write/__init__.c \
|
||||
os/__init__.c \
|
||||
storage/__init__.c \
|
||||
supervisor/__init__.c \
|
||||
supervisor/__init__.c \
|
||||
supervisor/Runtime.c \
|
||||
time/__init__.c \
|
||||
analogio/__init__.c \
|
||||
|
@ -287,12 +290,11 @@ SRC_COMMON_HAL = \
|
|||
pulseio/PulseOut.c \
|
||||
pulseio/PWMOut.c \
|
||||
usb_hid/__init__.c \
|
||||
usb_hid/Device.c
|
||||
# audiobusio/__init__.c \
|
||||
audiobusio/PDMIn.c \
|
||||
usb_hid/Device.c \
|
||||
audioio/__init__.c \
|
||||
audioio/AudioOut.c \
|
||||
nvm/__init__.c \
|
||||
# nvm/__init__.c \
|
||||
audiobusio/PDMIn.c \
|
||||
nvm/ByteArray.c \
|
||||
touchio/__init__.c \
|
||||
touchio/TouchIn.c \
|
||||
|
@ -338,6 +340,8 @@ SRC_COMMON_HAL_EXPANDED = $(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \
|
|||
$(addprefix common-hal/, $(SRC_COMMON_HAL))
|
||||
|
||||
SRC_SHARED_MODULE = \
|
||||
audioio/RawSample.c \
|
||||
audioio/WaveFile.c \
|
||||
bitbangio/__init__.c \
|
||||
bitbangio/I2C.c \
|
||||
bitbangio/OneWire.c \
|
||||
|
@ -355,6 +359,19 @@ SRC_SHARED_MODULE = \
|
|||
uheap/__init__.c \
|
||||
ustack/__init__.c
|
||||
|
||||
ifeq ($(CHIP_FAMILY),samd21)
|
||||
SRC_COMMON_HAL += \
|
||||
audiobusio/__init__.c \
|
||||
audiobusio/I2SOut.c
|
||||
endif
|
||||
ifneq ($(CHIP_VARIANT),SAMD51G18A)
|
||||
ifneq ($(CHIP_VARIANT),SAMD51G19A)
|
||||
SRC_COMMON_HAL += \
|
||||
audiobusio/__init__.c \
|
||||
audiobusio/I2SOut.c
|
||||
endif
|
||||
endif
|
||||
|
||||
SRC_SHARED_MODULE_EXPANDED = $(addprefix shared-bindings/, $(SRC_SHARED_MODULE)) \
|
||||
$(addprefix shared-module/, $(SRC_SHARED_MODULE))
|
||||
|
||||
|
|
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "audio_dma.h"
|
||||
#include "clocks.h"
|
||||
#include "events.h"
|
||||
#include "shared_dma.h"
|
||||
|
||||
#include "shared-bindings/audioio/RawSample.h"
|
||||
#include "shared-bindings/audioio/WaveFile.h"
|
||||
|
||||
#include "py/mpstate.h"
|
||||
|
||||
uint32_t audiosample_sample_rate(mp_obj_t sample_obj) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
return sample->sample_rate;
|
||||
}
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
return file->sample_rate;
|
||||
}
|
||||
return 16000;
|
||||
}
|
||||
|
||||
uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
return sample->bits_per_sample;
|
||||
}
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
return file->bits_per_sample;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
uint8_t audiosample_channel_count(mp_obj_t sample_obj) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
return sample->channel_count;
|
||||
}
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
return file->channel_count;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
audioio_rawsample_reset_buffer(sample, single_channel, audio_channel);
|
||||
}
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
audioio_wavefile_reset_buffer(file, single_channel, audio_channel);
|
||||
}
|
||||
}
|
||||
|
||||
bool audiosample_get_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t channel, uint8_t** buffer, uint32_t* buffer_length) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
return audioio_rawsample_get_buffer(sample, single_channel, channel, buffer, buffer_length);
|
||||
}
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
return audioio_wavefile_get_buffer(file, single_channel, channel, buffer, buffer_length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel,
|
||||
bool* single_buffer, bool* samples_signed,
|
||||
uint32_t* max_buffer_length, uint8_t* spacing) {
|
||||
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
|
||||
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
|
||||
audioio_rawsample_get_buffer_structure(sample, single_channel, single_buffer,
|
||||
samples_signed, max_buffer_length, spacing);
|
||||
} else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
|
||||
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
|
||||
audioio_wavefile_get_buffer_structure(file, single_channel, single_buffer, samples_signed,
|
||||
max_buffer_length, spacing);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t find_free_audio_dma_channel(void) {
|
||||
uint8_t channel;
|
||||
for (channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
|
||||
if (!dma_channel_enabled(channel)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT];
|
||||
void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer_length,
|
||||
uint8_t** output_buffer, uint32_t* output_buffer_length,
|
||||
uint8_t* output_spacing) {
|
||||
if (dma->first_buffer_free) {
|
||||
*output_buffer = dma->first_buffer;
|
||||
} else {
|
||||
*output_buffer = dma->second_buffer;
|
||||
}
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-align"
|
||||
if (dma->signed_to_unsigned || dma->unsigned_to_signed) {
|
||||
*output_buffer_length = buffer_length / dma->spacing;
|
||||
*output_spacing = 1;
|
||||
uint32_t out_i = 0;
|
||||
if (dma->bytes_per_sample == 1) {
|
||||
for (uint32_t i = 0; i < buffer_length; i += dma->spacing) {
|
||||
if (dma->signed_to_unsigned) {
|
||||
((uint8_t*) *output_buffer)[out_i] = ((int8_t*) buffer)[i] + 0x80;
|
||||
} else {
|
||||
((int8_t*) *output_buffer)[out_i] = ((uint8_t*) buffer)[i] - 0x80;
|
||||
}
|
||||
out_i += 1;
|
||||
}
|
||||
} else if (dma->bytes_per_sample == 2) {
|
||||
for (uint32_t i = 0; i < buffer_length / 2; i += dma->spacing) {
|
||||
if (dma->signed_to_unsigned) {
|
||||
((uint16_t*) *output_buffer)[out_i] = ((int16_t*) buffer)[i] + 0x8000;
|
||||
} else {
|
||||
((int16_t*) *output_buffer)[out_i] = ((uint16_t*) buffer)[i] - 0x8000;
|
||||
}
|
||||
out_i += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*output_buffer = buffer;
|
||||
*output_buffer_length = buffer_length;
|
||||
*output_spacing = dma->spacing;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
dma->first_buffer_free = !dma->first_buffer_free;
|
||||
}
|
||||
|
||||
void audio_dma_load_next_block(audio_dma_t* dma) {
|
||||
uint8_t* buffer;
|
||||
uint32_t buffer_length;
|
||||
bool last_buffer = audiosample_get_buffer(dma->sample, dma->single_channel, dma->audio_channel,
|
||||
&buffer, &buffer_length);
|
||||
|
||||
DmacDescriptor* descriptor = dma->second_descriptor;
|
||||
if (dma->first_descriptor_free) {
|
||||
descriptor = dma_descriptor(dma->dma_channel);
|
||||
}
|
||||
dma->first_descriptor_free = !dma->first_descriptor_free;
|
||||
|
||||
uint8_t* output_buffer;
|
||||
uint32_t output_buffer_length;
|
||||
uint8_t output_spacing;
|
||||
audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length,
|
||||
&output_spacing);
|
||||
|
||||
descriptor->BTCNT.reg = output_buffer_length / dma->beat_size / output_spacing;
|
||||
descriptor->SRCADDR.reg = ((uint32_t) output_buffer) + output_buffer_length;
|
||||
if (last_buffer) {
|
||||
if (dma->loop) {
|
||||
audiosample_reset_buffer(dma->sample, dma->single_channel, dma->audio_channel);
|
||||
} else {
|
||||
descriptor->DESCADDR.reg = 0;
|
||||
}
|
||||
}
|
||||
descriptor->BTCTRL.bit.VALID = true;
|
||||
}
|
||||
|
||||
static void setup_audio_descriptor(DmacDescriptor* descriptor, uint8_t beat_size,
|
||||
uint8_t spacing, uint32_t output_register_address) {
|
||||
uint32_t beat_size_reg = DMAC_BTCTRL_BEATSIZE_BYTE;
|
||||
if (beat_size == 2) {
|
||||
beat_size_reg = DMAC_BTCTRL_BEATSIZE_HWORD;
|
||||
} else if (beat_size == 4) {
|
||||
beat_size_reg = DMAC_BTCTRL_BEATSIZE_WORD;
|
||||
}
|
||||
descriptor->BTCTRL.reg = beat_size_reg |
|
||||
DMAC_BTCTRL_SRCINC |
|
||||
DMAC_BTCTRL_EVOSEL_BLOCK |
|
||||
DMAC_BTCTRL_STEPSIZE(spacing - 1) |
|
||||
DMAC_BTCTRL_STEPSEL_SRC;
|
||||
descriptor->DSTADDR.reg = output_register_address;
|
||||
}
|
||||
|
||||
// Playback should be shutdown before calling this.
|
||||
audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
|
||||
mp_obj_t sample,
|
||||
bool loop,
|
||||
bool single_channel,
|
||||
uint8_t audio_channel,
|
||||
bool output_signed,
|
||||
uint32_t output_register_address,
|
||||
uint8_t dma_trigger_source) {
|
||||
uint8_t dma_channel = find_free_audio_dma_channel();
|
||||
if (dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
|
||||
return AUDIO_DMA_DMA_BUSY;
|
||||
}
|
||||
|
||||
dma->sample = sample;
|
||||
dma->loop = loop;
|
||||
dma->single_channel = single_channel;
|
||||
dma->audio_channel = audio_channel;
|
||||
dma->dma_channel = dma_channel;
|
||||
dma->signed_to_unsigned = false;
|
||||
dma->unsigned_to_signed = false;
|
||||
dma->second_descriptor = NULL;
|
||||
dma->spacing = 1;
|
||||
dma->first_descriptor_free = true;
|
||||
audiosample_reset_buffer(sample, single_channel, audio_channel);
|
||||
|
||||
bool single_buffer;
|
||||
bool samples_signed;
|
||||
uint32_t max_buffer_length;
|
||||
audiosample_get_buffer_structure(sample, single_channel, &single_buffer, &samples_signed,
|
||||
&max_buffer_length, &dma->spacing);
|
||||
uint8_t output_spacing = dma->spacing;
|
||||
if (output_signed != samples_signed) {
|
||||
output_spacing = 1;
|
||||
max_buffer_length /= dma->spacing;
|
||||
dma->first_buffer = (uint8_t*) m_malloc(max_buffer_length, false);
|
||||
if (dma->first_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
dma->first_buffer_free = true;
|
||||
if (!single_buffer) {
|
||||
dma->second_buffer = (uint8_t*) m_malloc(max_buffer_length, false);
|
||||
if (dma->second_buffer == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
}
|
||||
dma->signed_to_unsigned = !output_signed && samples_signed;
|
||||
dma->unsigned_to_signed = output_signed && !samples_signed;
|
||||
}
|
||||
|
||||
dma->event_channel = 0xff;
|
||||
if (!single_buffer) {
|
||||
dma->second_descriptor = (DmacDescriptor*) m_malloc(sizeof(DmacDescriptor), false);
|
||||
if (dma->second_descriptor == NULL) {
|
||||
return AUDIO_DMA_MEMORY_ERROR;
|
||||
}
|
||||
|
||||
// We're likely double buffering so set up the block interrupts.
|
||||
turn_on_event_system();
|
||||
dma->event_channel = find_sync_event_channel();
|
||||
init_event_channel_interrupt(dma->event_channel, CORE_GCLK, EVSYS_ID_GEN_DMAC_CH_0 + dma_channel);
|
||||
find_sync_event_channel();
|
||||
|
||||
// We keep the audio_dma_t for internal use and the sample as a root pointer because it
|
||||
// contains the audiodma structure.
|
||||
audio_dma_state[dma->dma_channel] = dma;
|
||||
MP_STATE_PORT(playing_audio)[dma->dma_channel] = dma->sample;
|
||||
}
|
||||
|
||||
dma->beat_size = 1;
|
||||
dma->bytes_per_sample = 1;
|
||||
if (audiosample_bits_per_sample(sample) == 16) {
|
||||
dma->beat_size = 2;
|
||||
dma->bytes_per_sample = 2;
|
||||
}
|
||||
// Transfer both channels at once.
|
||||
if (!single_channel && audiosample_channel_count(sample) == 2) {
|
||||
dma->beat_size *= 2;
|
||||
}
|
||||
|
||||
DmacDescriptor* first_descriptor = dma_descriptor(dma_channel);
|
||||
setup_audio_descriptor(first_descriptor, dma->beat_size, output_spacing, output_register_address);
|
||||
if (single_buffer) {
|
||||
first_descriptor->DESCADDR.reg = 0;
|
||||
if (dma->loop) {
|
||||
first_descriptor->DESCADDR.reg = (uint32_t) first_descriptor;
|
||||
}
|
||||
} else {
|
||||
first_descriptor->DESCADDR.reg = (uint32_t) dma->second_descriptor;
|
||||
setup_audio_descriptor(dma->second_descriptor, dma->beat_size, output_spacing, output_register_address);
|
||||
dma->second_descriptor->DESCADDR.reg = (uint32_t) first_descriptor;
|
||||
}
|
||||
|
||||
// Load the first two blocks up front.
|
||||
audio_dma_load_next_block(dma);
|
||||
if (!single_buffer) {
|
||||
audio_dma_load_next_block(dma);
|
||||
}
|
||||
|
||||
dma_configure(dma_channel, dma_trigger_source, true);
|
||||
dma_enable_channel(dma_channel);
|
||||
|
||||
return AUDIO_DMA_OK;
|
||||
}
|
||||
|
||||
void audio_dma_stop(audio_dma_t* dma) {
|
||||
dma_disable_channel(dma->dma_channel);
|
||||
disable_event_channel(dma->event_channel);
|
||||
MP_STATE_PORT(playing_audio)[dma->dma_channel] = NULL;
|
||||
|
||||
dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
void audio_dma_init(audio_dma_t* dma) {
|
||||
dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
void audio_dma_reset(void) {
|
||||
for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
|
||||
audio_dma_state[i] = NULL;
|
||||
dma_disable_channel(i);
|
||||
dma_descriptor(i)->BTCTRL.bit.VALID = false;
|
||||
MP_STATE_PORT(playing_audio)[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool audio_dma_get_playing(audio_dma_t* dma) {
|
||||
if (dma->dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
|
||||
return false;
|
||||
}
|
||||
uint32_t status = dma_transfer_status(dma->dma_channel);
|
||||
if ((status & DMAC_CHINTFLAG_TCMPL) != 0) {
|
||||
audio_dma_stop(dma);
|
||||
}
|
||||
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
// WARN(tannewt): DO NOT print from here. Printing calls background tasks such as this and causes a
|
||||
// stack overflow.
|
||||
void audio_dma_background(void) {
|
||||
for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
|
||||
audio_dma_t* dma = audio_dma_state[i];
|
||||
if (dma == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool block_done = event_interrupt_active(dma->event_channel);
|
||||
if (!block_done) {
|
||||
continue;
|
||||
}
|
||||
|
||||
audio_dma_load_next_block(dma);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
|
||||
#define MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
|
||||
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
#include "py/obj.h"
|
||||
#include "shared-module/audioio/RawSample.h"
|
||||
#include "shared-module/audioio/WaveFile.h"
|
||||
|
||||
typedef struct {
|
||||
mp_obj_t sample;
|
||||
uint8_t dma_channel;
|
||||
uint8_t event_channel;
|
||||
uint8_t audio_channel;
|
||||
uint8_t bytes_per_sample;
|
||||
uint8_t beat_size;
|
||||
uint8_t spacing;
|
||||
bool loop;
|
||||
bool single_channel;
|
||||
bool signed_to_unsigned;
|
||||
bool unsigned_to_signed;
|
||||
bool first_buffer_free;
|
||||
uint8_t* first_buffer;
|
||||
uint8_t* second_buffer;
|
||||
bool first_descriptor_free;
|
||||
DmacDescriptor* second_descriptor;
|
||||
} audio_dma_t;
|
||||
|
||||
typedef enum {
|
||||
AUDIO_DMA_OK,
|
||||
AUDIO_DMA_DMA_BUSY,
|
||||
AUDIO_DMA_MEMORY_ERROR,
|
||||
} audio_dma_result;
|
||||
|
||||
uint32_t audiosample_sample_rate(mp_obj_t sample_obj);
|
||||
uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj);
|
||||
uint8_t audiosample_channel_count(mp_obj_t sample_obj);
|
||||
|
||||
void audio_dma_init(audio_dma_t* dma);
|
||||
void audio_dma_reset(void);
|
||||
|
||||
// This sets everything up but doesn't start the timer.
|
||||
// Sample is the python object for the sample to play.
|
||||
// loop is true if we should loop the sample.
|
||||
// single_channel is true if we only output a single channel. When false, all channels will be
|
||||
// output.
|
||||
// audio_channel is the index of the channel to dma. single_channel must be false in this case.
|
||||
// output_signed is true if the dma'd data should be signed. False and it will be unsigned.
|
||||
// output_register_address is the address to copy data to.
|
||||
// dma_trigger_source is the DMA trigger source which cause another copy
|
||||
audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
|
||||
mp_obj_t sample,
|
||||
bool loop,
|
||||
bool single_channel,
|
||||
uint8_t audio_channel,
|
||||
bool output_signed,
|
||||
uint32_t output_register_address,
|
||||
uint8_t dma_trigger_source);
|
||||
void audio_dma_stop(audio_dma_t* dma);
|
||||
bool audio_dma_get_playing(audio_dma_t* dma);
|
||||
|
||||
void audio_dma_background(void);
|
||||
|
||||
#endif // MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
|
|
@ -25,12 +25,12 @@
|
|||
*/
|
||||
#include "background.h"
|
||||
|
||||
// #include "common-hal/audioio/AudioOut.h"
|
||||
#include "audio_dma.h"
|
||||
#include "usb.h"
|
||||
#include "usb_mass_storage.h"
|
||||
|
||||
void run_background_tasks(void) {
|
||||
// audioout_background();
|
||||
audio_dma_background();
|
||||
usb_msc_background();
|
||||
usb_cdc_background();
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
|
|||
|
||||
QSPI_FLASH_FILESYSTEM = 1
|
||||
|
||||
CHIP_VARIANT = SAMD51J19A
|
||||
CHIP_VARIANT = SAMD51G19A
|
||||
CHIP_FAMILY = samd51
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "clocks.h"
|
||||
|
||||
#include "hpl_gclk_config.h"
|
||||
|
||||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
|
||||
#include "py/runtime.h"
|
||||
|
||||
// TODO(tannewt): Should we have a way of sharing GCLKs based on their speed? Divisor doesn't
|
||||
// gaurantee speed because it depends on the source.
|
||||
uint8_t find_free_gclk(uint16_t divisor) {
|
||||
if (divisor > 0xff) {
|
||||
if (gclk_enabled(1)) {
|
||||
return 0xff;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
uint8_t first_8_bit = 2;
|
||||
#ifdef SAMD21
|
||||
first_8_bit = 3;
|
||||
if (divisor <= (1 << 5) && !gclk_enabled(2)) {
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
for (uint8_t i = first_8_bit; i < GCLK_GEN_NUM; i++) {
|
||||
if (!gclk_enabled(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
bool gclk_enabled(uint8_t gclk) {
|
||||
#ifdef SAMD51
|
||||
return GCLK->GENCTRL[gclk].bit.GENEN;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
// Explicitly do a byte write so the peripheral knows we're just wanting to read the channel
|
||||
// rather than write to it.
|
||||
*((uint8_t*) &GCLK->GENCTRL.reg) = gclk;
|
||||
while (GCLK->STATUS.bit.SYNCBUSY == 1) {}
|
||||
bool enabled = GCLK->GENCTRL.bit.GENEN;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
return enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
void disable_gclk(uint8_t gclk) {
|
||||
#ifdef SAMD51
|
||||
GCLK->GENCTRL[gclk].bit.GENEN = false;
|
||||
while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk);
|
||||
while (GCLK->STATUS.bit.SYNCBUSY == 1) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
void connect_gclk_to_peripheral(uint8_t gclk, uint8_t peripheral) {
|
||||
#ifdef SAMD21
|
||||
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(peripheral) | GCLK_CLKCTRL_GEN(gclk) | GCLK_CLKCTRL_CLKEN;
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
GCLK->PCHCTRL[peripheral].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(gclk);
|
||||
while(GCLK->SYNCBUSY.reg != 0) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
void disconnect_gclk_from_peripheral(uint8_t gclk, uint8_t peripheral) {
|
||||
#ifdef SAMD21
|
||||
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(peripheral) | GCLK_CLKCTRL_GEN(gclk);
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
GCLK->PCHCTRL[peripheral].reg = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void enable_clock_generator(uint8_t gclk, uint8_t source, uint16_t divisor) {
|
||||
#ifdef SAMD21
|
||||
GCLK->GENDIV.reg = GCLK_GENDIV_ID(gclk) | GCLK_GENDIV_DIV(divisor);
|
||||
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk) | GCLK_GENCTRL_SRC(source) | GCLK_GENCTRL_GENEN;
|
||||
while (GCLK->STATUS.bit.SYNCBUSY != 0) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
GCLK->GENCTRL[gclk].reg = GCLK_GENCTRL_SRC(source) | GCLK_GENCTRL_DIV(divisor) | GCLK_GENCTRL_GENEN;
|
||||
while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
void disable_clock_generator(uint8_t gclk) {
|
||||
#ifdef SAMD21
|
||||
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk);
|
||||
while (GCLK->STATUS.bit.SYNCBUSY != 0) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
GCLK->GENCTRL[gclk].reg = 0;
|
||||
while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_gclks(void) {
|
||||
// Never reset GCLK0 because its used for the core
|
||||
#if CONF_GCLK_GEN_1_GENEN == 0
|
||||
disable_gclk(1);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_2_GENEN == 0
|
||||
disable_gclk(2);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_3_GENEN == 0
|
||||
disable_gclk(3);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_4_GENEN == 0
|
||||
disable_gclk(4);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_5_GENEN == 0
|
||||
disable_gclk(5);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_6_GENEN == 0
|
||||
disable_gclk(6);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_7_GENEN == 0
|
||||
disable_gclk(7);
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
#if CONF_GCLK_GEN_8_GENEN == 0
|
||||
disable_gclk(8);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_9_GENEN == 0
|
||||
disable_gclk(9);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_10_GENEN == 0
|
||||
disable_gclk(10);
|
||||
#endif
|
||||
#if CONF_GCLK_GEN_11_GENEN == 0
|
||||
disable_gclk(11);
|
||||
#endif
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
|
||||
#define MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "include/sam.h"
|
||||
|
||||
#ifdef SAMD51
|
||||
#define CLOCK_48MHZ GCLK_GENCTRL_SRC_DFLL_Val
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
#define CLOCK_48MHZ GCLK_GENCTRL_SRC_DFLL48M_Val
|
||||
#endif
|
||||
|
||||
#define CORE_GCLK 0
|
||||
|
||||
uint8_t find_free_gclk(uint16_t divisor);
|
||||
|
||||
bool gclk_enabled(uint8_t gclk);
|
||||
void reset_gclks(void);
|
||||
|
||||
void connect_gclk_to_peripheral(uint8_t gclk, uint8_t peripheral);
|
||||
void disconnect_gclk_from_peripheral(uint8_t gclk, uint8_t peripheral);
|
||||
|
||||
void enable_clock_generator(uint8_t gclk, uint8_t source, uint16_t divisor);
|
||||
void disable_clock_generator(uint8_t gclk);
|
||||
|
||||
#endif // MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
#include "py/gc.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "py/runtime.h"
|
||||
#include "common-hal/audiobusio/I2SOut.h"
|
||||
#include "shared-bindings/audiobusio/I2SOut.h"
|
||||
#include "shared-bindings/audioio/RawSample.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
|
||||
#include "atmel_start_pins.h"
|
||||
#include "hal/include/hal_gpio.h"
|
||||
#include "hpl/gclk/hpl_gclk_base.h"
|
||||
#include "peripheral_clk_config.h"
|
||||
|
||||
#ifdef SAMD21
|
||||
#include "hpl/pm/hpl_pm_base.h"
|
||||
#endif
|
||||
|
||||
#include "audio_dma.h"
|
||||
#include "clocks.h"
|
||||
#include "events.h"
|
||||
#include "samd21_pins.h"
|
||||
#include "shared_dma.h"
|
||||
#include "timers.h"
|
||||
|
||||
#ifdef SAMD21
|
||||
#define SERCTRL(name) I2S_SERCTRL_ ## name
|
||||
#endif
|
||||
|
||||
#ifdef SAMD51
|
||||
#define SERCTRL(name) I2S_TXCTRL_ ## name
|
||||
#endif
|
||||
|
||||
void i2sout_reset(void) {
|
||||
// Make sure the I2S peripheral is running so we can see if the resources we need are free.
|
||||
#ifdef SAMD51
|
||||
// Connect the clock units to the 2mhz clock. It can't disable without it.
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_0);
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_1);
|
||||
#endif
|
||||
if (I2S->CTRLA.bit.ENABLE == 1) {
|
||||
I2S->CTRLA.bit.ENABLE = 0;
|
||||
while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
|
||||
}
|
||||
|
||||
// Make sure the I2S peripheral is running so we can see if the resources we need are free.
|
||||
#ifdef SAMD51
|
||||
// Connect the clock units to the 2mhz clock by default. They can't reset without it.
|
||||
disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_0);
|
||||
disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_1);
|
||||
|
||||
hri_mclk_clear_APBDMASK_I2S_bit(MCLK);
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
_pm_disable_bus_clock(PM_BUS_APBC, I2S);
|
||||
#endif
|
||||
}
|
||||
|
||||
void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
|
||||
const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
|
||||
const mcu_pin_obj_t* data, bool left_justified) {
|
||||
uint8_t serializer = 0xff;
|
||||
uint8_t bc_clock_unit = 0xff;
|
||||
uint8_t ws_clock_unit = 0xff;
|
||||
#ifdef SAMD21
|
||||
if (bit_clock == &pin_PA10
|
||||
#ifdef PIN_PA20
|
||||
|| bit_clock == &pin_PA20
|
||||
#endif
|
||||
) { // I2S SCK[0]
|
||||
bc_clock_unit = 0;
|
||||
}
|
||||
#ifdef PIN_PB11
|
||||
else if (bit_clock == &pin_PB11) { // I2S SCK[1]
|
||||
bc_clock_unit = 1;
|
||||
}
|
||||
#endif
|
||||
if (word_select == &pin_PA11
|
||||
#ifdef PIN_PA21
|
||||
|| word_select == &pin_PA21
|
||||
#endif
|
||||
) { // I2S FS[0]
|
||||
ws_clock_unit = 0;
|
||||
}
|
||||
#ifdef PIN_PB12
|
||||
else if (word_select == &pin_PB12) { // I2S FS[1]
|
||||
ws_clock_unit = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (data == &pin_PA07 || data == &pin_PA19) { // I2S SD[0]
|
||||
serializer = 0;
|
||||
} else if (data == &pin_PA08
|
||||
#ifdef PIN_PB16
|
||||
|| data == &pin_PB16
|
||||
#endif
|
||||
) { // I2S SD[1]
|
||||
serializer = 1;
|
||||
}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
// Only clock unit 0 can be used for transmission.
|
||||
if (bit_clock == &pin_PA10 || bit_clock == &pin_PB16) { // I2S SCK[0]
|
||||
bc_clock_unit = 0;
|
||||
}
|
||||
if (word_select == &pin_PA09 || word_select == &pin_PA20) { // I2S FS[0]
|
||||
ws_clock_unit = 0;
|
||||
}
|
||||
if (data == &pin_PA11 || data == &pin_PA21) { // I2S SDO
|
||||
serializer = 0;
|
||||
}
|
||||
#endif
|
||||
if (bc_clock_unit == 0xff) {
|
||||
mp_raise_ValueError("Invalid bit clock pin");
|
||||
}
|
||||
if (ws_clock_unit == 0xff) {
|
||||
mp_raise_ValueError("Invalid bit clock pin");
|
||||
}
|
||||
if (bc_clock_unit != ws_clock_unit) {
|
||||
mp_raise_ValueError("Bit clock and word select must share a clock unit");
|
||||
}
|
||||
if (serializer == 0xff) {
|
||||
mp_raise_ValueError("Invalid data pin");
|
||||
}
|
||||
self->clock_unit = ws_clock_unit;
|
||||
self->serializer = serializer;
|
||||
|
||||
// Make sure the I2S peripheral is running so we can see if the resources we need are free.
|
||||
#ifdef SAMD51
|
||||
hri_mclk_set_APBDMASK_I2S_bit(MCLK);
|
||||
|
||||
// Connect the clock units to the 2mhz clock by default. They can't reset without it.
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_0);
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_1);
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
_pm_enable_bus_clock(PM_BUS_APBC, I2S);
|
||||
#endif
|
||||
|
||||
if (I2S->CTRLA.bit.ENABLE == 0) {
|
||||
I2S->CTRLA.bit.SWRST = 1;
|
||||
while (I2S->CTRLA.bit.SWRST == 1) {}
|
||||
} else {
|
||||
#ifdef SAMD21
|
||||
if ((I2S->CTRLA.vec.SEREN & (1 << serializer)) != 0) {
|
||||
mp_raise_RuntimeError("Serializer in use");
|
||||
}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
if (I2S->CTRLA.bit.TXEN == 1) {
|
||||
mp_raise_RuntimeError("Serializer in use");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SAMD51
|
||||
#define GPIO_I2S_FUNCTION GPIO_PIN_FUNCTION_J
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
#define GPIO_I2S_FUNCTION GPIO_PIN_FUNCTION_G
|
||||
#endif
|
||||
assert_pin_free(bit_clock);
|
||||
assert_pin_free(word_select);
|
||||
assert_pin_free(data);
|
||||
|
||||
self->bit_clock = bit_clock;
|
||||
self->word_select = word_select;
|
||||
self->data = data;
|
||||
|
||||
claim_pin(bit_clock);
|
||||
claim_pin(word_select);
|
||||
claim_pin(data);
|
||||
|
||||
gpio_set_pin_function(self->bit_clock->pin, GPIO_I2S_FUNCTION);
|
||||
gpio_set_pin_function(self->word_select->pin, GPIO_I2S_FUNCTION);
|
||||
gpio_set_pin_function(self->data->pin, GPIO_I2S_FUNCTION);
|
||||
|
||||
self->left_justified = left_justified;
|
||||
self->playing = false;
|
||||
audio_dma_init(&self->dma);
|
||||
}
|
||||
|
||||
bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
|
||||
return self->bit_clock == mp_const_none;
|
||||
}
|
||||
|
||||
void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
|
||||
if (common_hal_audiobusio_i2sout_deinited(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reset_pin(self->bit_clock->pin);
|
||||
self->bit_clock = mp_const_none;
|
||||
reset_pin(self->word_select->pin);
|
||||
self->word_select = mp_const_none;
|
||||
reset_pin(self->data->pin);
|
||||
self->data = mp_const_none;
|
||||
}
|
||||
|
||||
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
|
||||
mp_obj_t sample, bool loop) {
|
||||
if (common_hal_audiobusio_i2sout_get_playing(self)) {
|
||||
common_hal_audiobusio_i2sout_stop(self);
|
||||
}
|
||||
#ifdef SAMD21
|
||||
if ((I2S->CTRLA.vec.CKEN & (1 << self->clock_unit)) == 1) {
|
||||
mp_raise_RuntimeError("Clock unit in use");
|
||||
}
|
||||
#endif
|
||||
uint8_t bits_per_sample = audiosample_bits_per_sample(sample);
|
||||
// We always output stereo so output twice as many bits.
|
||||
uint16_t bits_per_sample_output = bits_per_sample * 2;
|
||||
uint16_t divisor = 48000000 / (bits_per_sample_output * audiosample_sample_rate(sample));
|
||||
// Find a free GCLK to generate the MCLK signal.
|
||||
uint8_t gclk = find_free_gclk(divisor);
|
||||
if (gclk > GCLK_GEN_NUM) {
|
||||
mp_raise_RuntimeError("Unable to find free GCLK");
|
||||
}
|
||||
self->gclk = gclk;
|
||||
|
||||
uint32_t clkctrl = I2S_CLKCTRL_MCKSEL_GCLK |
|
||||
I2S_CLKCTRL_NBSLOTS(1) |
|
||||
I2S_CLKCTRL_FSWIDTH_HALF;
|
||||
if (self->left_justified) {
|
||||
clkctrl |= I2S_CLKCTRL_BITDELAY_LJ;
|
||||
} else {
|
||||
clkctrl |= I2S_CLKCTRL_FSOUTINV | I2S_CLKCTRL_BITDELAY_I2S;
|
||||
}
|
||||
uint8_t channel_count = audiosample_channel_count(sample);
|
||||
if (channel_count > 2) {
|
||||
mp_raise_ValueError("Too many channels in sample.");
|
||||
}
|
||||
#ifdef SAMD21
|
||||
uint32_t serctrl = (self->clock_unit << I2S_SERCTRL_CLKSEL_Pos) | SERCTRL(SERMODE_TX) | I2S_SERCTRL_TXSAME_SAME | I2S_SERCTRL_EXTEND_MSBIT | I2S_SERCTRL_TXDEFAULT_ONE | I2S_SERCTRL_SLOTADJ_LEFT;
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
uint32_t serctrl = (self->clock_unit << I2S_RXCTRL_CLKSEL_Pos) | I2S_TXCTRL_TXSAME_SAME;
|
||||
#endif
|
||||
if (audiosample_channel_count(sample) == 1) {
|
||||
serctrl |= SERCTRL(MONO_MONO);
|
||||
} else {
|
||||
serctrl |= SERCTRL(MONO_STEREO);
|
||||
}
|
||||
if (bits_per_sample == 8) {
|
||||
serctrl |= SERCTRL(DATASIZE_8C);
|
||||
clkctrl |= I2S_CLKCTRL_SLOTSIZE_8;
|
||||
} else if (bits_per_sample == 16) {
|
||||
serctrl |= SERCTRL(DATASIZE_16C);
|
||||
clkctrl |= I2S_CLKCTRL_SLOTSIZE_16;
|
||||
}
|
||||
|
||||
// Configure the I2S peripheral
|
||||
I2S->CTRLA.bit.ENABLE = 0;
|
||||
while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
|
||||
|
||||
I2S->CLKCTRL[self->clock_unit].reg = clkctrl;
|
||||
#ifdef SAMD21
|
||||
I2S->SERCTRL[self->serializer].reg = serctrl;
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
I2S->TXCTRL.reg = serctrl;
|
||||
#endif
|
||||
|
||||
// The DFLL is always a 48mhz clock
|
||||
enable_clock_generator(self->gclk, CLOCK_48MHZ, divisor);
|
||||
connect_gclk_to_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit);
|
||||
|
||||
I2S->CTRLA.bit.ENABLE = 1;
|
||||
while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
|
||||
|
||||
#ifdef SAMD21
|
||||
uint32_t tx_register = (uint32_t) &I2S->DATA[self->serializer].reg;
|
||||
uint8_t dmac_id = I2S_DMAC_ID_TX_0 + self->serializer;
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
uint32_t tx_register = (uint32_t) &I2S->TXDATA.reg;
|
||||
uint8_t dmac_id = I2S_DMAC_ID_TX_0;
|
||||
#endif
|
||||
audio_dma_result result = audio_dma_setup_playback(&self->dma, sample, loop, false, 0,
|
||||
true /* output signed */, tx_register, dmac_id);
|
||||
|
||||
if (result == AUDIO_DMA_DMA_BUSY) {
|
||||
common_hal_audiobusio_i2sout_stop(self);
|
||||
mp_raise_RuntimeError("No DMA channel found");
|
||||
} else if (result == AUDIO_DMA_MEMORY_ERROR) {
|
||||
common_hal_audiobusio_i2sout_stop(self);
|
||||
mp_raise_RuntimeError("Unable to allocate buffers for signed conversion");
|
||||
}
|
||||
|
||||
I2S->INTFLAG.reg = I2S_INTFLAG_TXUR0 | I2S_INTFLAG_TXUR1;
|
||||
|
||||
I2S->CTRLA.vec.CKEN = 1 << self->clock_unit;
|
||||
while ((I2S->SYNCBUSY.vec.CKEN & (1 << self->clock_unit)) != 0) {}
|
||||
|
||||
// Init the serializer after the clock. Otherwise, it will never enable because its unclocked.
|
||||
#ifdef SAMD21
|
||||
I2S->CTRLA.vec.SEREN = 1 << self->serializer;
|
||||
while ((I2S->SYNCBUSY.vec.SEREN & (1 << self->serializer)) != 0) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
I2S->CTRLA.bit.TXEN = 1;
|
||||
while (I2S->SYNCBUSY.bit.TXEN == 1) {}
|
||||
#endif
|
||||
|
||||
self->playing = true;
|
||||
}
|
||||
|
||||
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
|
||||
audio_dma_stop(&self->dma);
|
||||
|
||||
#ifdef SAMD21
|
||||
I2S->CTRLA.vec.SEREN &= ~(1 << self->serializer);
|
||||
while ((I2S->SYNCBUSY.vec.SEREN & (1 << self->serializer)) != 0) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
I2S->CTRLA.bit.TXEN = 0;
|
||||
while (I2S->SYNCBUSY.bit.TXEN == 1) {}
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
if (self->clock_unit == 0) {
|
||||
I2S->CTRLA.bit.CKEN0 = 0;
|
||||
while (I2S->SYNCBUSY.bit.CKEN0 == 1) {}
|
||||
} else {
|
||||
I2S->CTRLA.bit.CKEN1 = 0;
|
||||
while (I2S->SYNCBUSY.bit.CKEN1 == 1) {}
|
||||
}
|
||||
#endif
|
||||
disconnect_gclk_from_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit);
|
||||
disable_clock_generator(self->gclk);
|
||||
|
||||
#ifdef SAMD51
|
||||
connect_gclk_to_peripheral(5, I2S_GCLK_ID_0 + self->clock_unit);
|
||||
#endif
|
||||
|
||||
self->playing = false;
|
||||
}
|
||||
|
||||
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
|
||||
bool still_playing = audio_dma_get_playing(&self->dma);
|
||||
if (self->playing && !still_playing) {
|
||||
common_hal_audiobusio_i2sout_stop(self);
|
||||
}
|
||||
return still_playing;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
|
||||
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
|
||||
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
|
||||
#include "audio_dma.h"
|
||||
#include "py/obj.h"
|
||||
|
||||
// We don't bit pack because we'll only have two at most. Its better to save code size instead.
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
bool left_justified;
|
||||
const mcu_pin_obj_t *bit_clock;
|
||||
const mcu_pin_obj_t *word_select;
|
||||
const mcu_pin_obj_t *data;
|
||||
uint8_t clock_unit;
|
||||
uint8_t serializer;
|
||||
uint8_t gclk;
|
||||
bool playing;
|
||||
audio_dma_t dma;
|
||||
} audiobusio_i2sout_obj_t;
|
||||
|
||||
void i2sout_reset(void);
|
||||
|
||||
#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
|
|
@ -37,7 +37,6 @@
|
|||
#include "shared-bindings/audiobusio/PDMIn.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
|
||||
#include "asf/sam0/drivers/port/port.h"
|
||||
#include "samd21_pins.h"
|
||||
|
||||
#include "shared_dma.h"
|
||||
|
|
|
@ -28,8 +28,6 @@
|
|||
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H
|
||||
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
#include "asf/sam0/drivers/i2s/i2s.h"
|
||||
#include "asf/sam0/drivers/tc/tc.h"
|
||||
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
#include "py/obj.h"
|
||||
|
@ -39,7 +37,6 @@ typedef struct {
|
|||
const mcu_pin_obj_t *clock_pin;
|
||||
const mcu_pin_obj_t *data_pin;
|
||||
uint32_t frequency;
|
||||
struct i2s_module i2s_instance;
|
||||
uint8_t serializer;
|
||||
uint8_t clock_unit;
|
||||
uint8_t bytes_per_sample;
|
||||
|
|
|
@ -35,155 +35,134 @@
|
|||
#include "shared-bindings/audioio/AudioOut.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
|
||||
#include "asf/sam0/drivers/dac/dac.h"
|
||||
#include "asf/sam0/drivers/dma/dma.h"
|
||||
#include "asf/sam0/drivers/events/events.h"
|
||||
#include "asf/sam0/drivers/port/port.h"
|
||||
#include "asf/sam0/drivers/tc/tc.h"
|
||||
#include "atmel_start_pins.h"
|
||||
#include "hal/include/hal_gpio.h"
|
||||
#include "hpl/gclk/hpl_gclk_base.h"
|
||||
#include "peripheral_clk_config.h"
|
||||
|
||||
#ifdef SAMD21
|
||||
#include "hpl/pm/hpl_pm_base.h"
|
||||
#endif
|
||||
|
||||
#include "audio_dma.h"
|
||||
#include "events.h"
|
||||
#include "samd21_pins.h"
|
||||
#include "shared_dma.h"
|
||||
|
||||
#undef ENABLE
|
||||
|
||||
// Shared with PWMOut
|
||||
// TODO(tannewt): Factor these out so audioio can exist without PWMOut.
|
||||
extern uint32_t target_timer_frequencies[TC_INST_NUM + TCC_INST_NUM];
|
||||
extern uint8_t timer_refcount[TC_INST_NUM + TCC_INST_NUM];
|
||||
extern const uint16_t prescaler[8];
|
||||
|
||||
// This timer is shared amongst all AudioOut objects under the assumption that
|
||||
// the code is single threaded. The audioout_sample_timer, audioout_dac_instance,
|
||||
// audioout_sample_event, and audioout_dac_event pointers live in
|
||||
// MICROPY_PORT_ROOT_POINTERS so they don't get garbage collected.
|
||||
|
||||
// The AudioOut object is being currently played. Only it can pause the timer
|
||||
// and change its frequency.
|
||||
static audioio_audioout_obj_t* active_audioout;
|
||||
|
||||
static uint8_t refcount = 0;
|
||||
|
||||
struct wave_format_chunk {
|
||||
uint16_t audio_format;
|
||||
uint16_t num_channels;
|
||||
uint32_t sample_rate;
|
||||
uint32_t byte_rate;
|
||||
uint16_t block_align;
|
||||
uint16_t bits_per_sample;
|
||||
uint16_t extra_params; // Assumed to be zero below.
|
||||
};
|
||||
#include "timers.h"
|
||||
|
||||
void audioout_reset(void) {
|
||||
// Only reset DMA. PWMOut will reset the timer. Other code will reset the DAC.
|
||||
refcount = 0;
|
||||
MP_STATE_VM(audioout_sample_timer) = NULL;
|
||||
MP_STATE_VM(audiodma_block_counter) = NULL;
|
||||
MP_STATE_VM(audioout_dac_instance) = NULL;
|
||||
|
||||
if (MP_STATE_VM(audioout_sample_event) != NULL) {
|
||||
events_detach_user(MP_STATE_VM(audioout_sample_event), EVSYS_ID_USER_DAC_START);
|
||||
events_release(MP_STATE_VM(audioout_sample_event));
|
||||
}
|
||||
MP_STATE_VM(audioout_sample_event) = NULL;
|
||||
|
||||
if (MP_STATE_VM(audiodma_block_event) != NULL) {
|
||||
events_release(MP_STATE_VM(audiodma_block_event));
|
||||
}
|
||||
MP_STATE_VM(audiodma_block_event) = NULL;
|
||||
|
||||
if (MP_STATE_VM(audioout_dac_event) != NULL) {
|
||||
events_detach_user(MP_STATE_VM(audioout_dac_event), EVSYS_ID_USER_DMAC_CH_0);
|
||||
events_release(MP_STATE_VM(audioout_dac_event));
|
||||
}
|
||||
MP_STATE_VM(audioout_dac_event) = NULL;
|
||||
|
||||
dma_abort_job(&audio_dma);
|
||||
}
|
||||
|
||||
// WARN(tannewt): DO NOT print from here. It calls background tasks and causes a
|
||||
// stack overflow.
|
||||
void audioout_background(void) {
|
||||
if (MP_STATE_VM(audiodma_block_counter) != NULL &&
|
||||
active_audioout != NULL &&
|
||||
active_audioout->second_buffer != NULL &&
|
||||
active_audioout->last_loaded_block < tc_get_count_value(MP_STATE_VM(audiodma_block_counter))) {
|
||||
uint8_t* buffer;
|
||||
if (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) % 2 == 1) {
|
||||
buffer = active_audioout->buffer;
|
||||
} else {
|
||||
buffer = active_audioout->second_buffer;
|
||||
}
|
||||
uint16_t num_bytes_to_load = active_audioout->len;
|
||||
if (num_bytes_to_load > active_audioout->bytes_remaining) {
|
||||
num_bytes_to_load = active_audioout->bytes_remaining;
|
||||
}
|
||||
UINT length_read;
|
||||
f_read(&active_audioout->file->fp, buffer, num_bytes_to_load, &length_read);
|
||||
active_audioout->bytes_remaining -= length_read;
|
||||
active_audioout->last_loaded_block += 1;
|
||||
void common_hal_audioio_audioout_construct(audioio_audioout_obj_t* self,
|
||||
const mcu_pin_obj_t* left_channel, const mcu_pin_obj_t* right_channel) {
|
||||
#ifdef SAMD51
|
||||
bool dac_clock_enabled = hri_mclk_get_APBDMASK_DAC_bit(MCLK);
|
||||
#endif
|
||||
|
||||
if (active_audioout->bytes_remaining == 0) {
|
||||
if (active_audioout->loop) {
|
||||
// Loop back to the start of the file.
|
||||
f_lseek(&active_audioout->file->fp, active_audioout->data_start);
|
||||
active_audioout->bytes_remaining = active_audioout->file_length;
|
||||
f_read(&active_audioout->file->fp, buffer, active_audioout->len - num_bytes_to_load, &length_read);
|
||||
active_audioout->bytes_remaining -= length_read;
|
||||
} else {
|
||||
DmacDescriptor* descriptor = audio_dma.descriptor;
|
||||
if (buffer == active_audioout->second_buffer) {
|
||||
descriptor = active_audioout->second_descriptor;
|
||||
}
|
||||
descriptor->BTCNT.reg = length_read / active_audioout->bytes_per_sample;
|
||||
descriptor->SRCADDR.reg = ((uint32_t) buffer) + length_read;
|
||||
descriptor->DESCADDR.reg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (active_audioout->bytes_per_sample == 2) {
|
||||
// Undo twos complement.
|
||||
for (uint16_t i = 0; i < length_read / 2; i++) {
|
||||
buffer[2 * i + 1] ^= 0x80;
|
||||
}
|
||||
}
|
||||
#ifdef SAMD21
|
||||
bool dac_clock_enabled = PM->APBCMASK.bit.DAC_;
|
||||
#endif
|
||||
// Only support exclusive use of the DAC.
|
||||
if (dac_clock_enabled && DAC->CTRLA.bit.ENABLE == 1) {
|
||||
mp_raise_RuntimeError("DAC already in use");
|
||||
}
|
||||
}
|
||||
|
||||
static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t* pin) {
|
||||
assert_pin_free(pin);
|
||||
|
||||
// Configure the DAC to output on input event and to output an empty event
|
||||
// that triggers the DMA to load the next sample.
|
||||
MP_STATE_VM(audioout_dac_instance) = gc_alloc(sizeof(struct dac_module), false);
|
||||
if (MP_STATE_VM(audioout_dac_instance) == NULL) {
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
#ifdef SAMD21
|
||||
if (right_channel != NULL) {
|
||||
mp_raise_ValueError("Right channel unsupported");
|
||||
}
|
||||
struct dac_config config_dac;
|
||||
dac_get_config_defaults(&config_dac);
|
||||
config_dac.left_adjust = true;
|
||||
config_dac.reference = DAC_REFERENCE_AVCC;
|
||||
config_dac.clock_source = GCLK_GENERATOR_0;
|
||||
enum status_code status = dac_init(MP_STATE_VM(audioout_dac_instance), DAC, &config_dac);
|
||||
if (status != STATUS_OK) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_OSError(MP_EIO);
|
||||
return;
|
||||
if (left_channel != &pin_PA02) {
|
||||
mp_raise_ValueError("Invalid pin");
|
||||
}
|
||||
assert_pin_free(left_channel);
|
||||
claim_pin(left_channel);
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
self->right_channel = NULL;
|
||||
if (left_channel != &pin_PA02 && left_channel != &pin_PA05) {
|
||||
mp_raise_ValueError("Invalid pin for left channel");
|
||||
}
|
||||
assert_pin_free(left_channel);
|
||||
if (right_channel != NULL && right_channel != &pin_PA02 && right_channel != &pin_PA05) {
|
||||
mp_raise_ValueError("Invalid pin for right channel");
|
||||
}
|
||||
if (right_channel == left_channel) {
|
||||
mp_raise_ValueError("Cannot output both channels on the same pin");
|
||||
}
|
||||
claim_pin(left_channel);
|
||||
if (right_channel != NULL) {
|
||||
claim_pin(right_channel);
|
||||
self->right_channel = right_channel;
|
||||
gpio_set_pin_function(self->right_channel->pin, GPIO_PIN_FUNCTION_B);
|
||||
audio_dma_init(&self->right_dma);
|
||||
}
|
||||
#endif
|
||||
self->left_channel = left_channel;
|
||||
gpio_set_pin_function(self->left_channel->pin, GPIO_PIN_FUNCTION_B);
|
||||
audio_dma_init(&self->left_dma);
|
||||
|
||||
struct dac_chan_config channel_config;
|
||||
dac_chan_get_config_defaults(&channel_config);
|
||||
dac_chan_set_config(MP_STATE_VM(audioout_dac_instance), DAC_CHANNEL_0, &channel_config);
|
||||
dac_chan_enable(MP_STATE_VM(audioout_dac_instance), DAC_CHANNEL_0);
|
||||
#ifdef SAMD51
|
||||
hri_mclk_set_APBDMASK_DAC_bit(MCLK);
|
||||
#endif
|
||||
|
||||
struct dac_events events_dac = { .generate_event_on_buffer_empty = true,
|
||||
.on_event_start_conversion = true };
|
||||
dac_enable_events(MP_STATE_VM(audioout_dac_instance), &events_dac);
|
||||
#ifdef SAMD21
|
||||
_pm_enable_bus_clock(PM_BUS_APBC, DAC);
|
||||
#endif
|
||||
|
||||
// Figure out which timer we are using.
|
||||
// SAMD21: This clock should be <= 12 MHz, per datasheet section 47.6.3.
|
||||
// SAMD51: This clock should be <= 350kHz, per datasheet table 37-6.
|
||||
_gclk_enable_channel(DAC_GCLK_ID, CONF_GCLK_DAC_SRC);
|
||||
|
||||
|
||||
DAC->CTRLA.bit.SWRST = 1;
|
||||
while (DAC->CTRLA.bit.SWRST == 1) {}
|
||||
|
||||
bool channel0_enabled = true;
|
||||