Merge pull request #756 from tannewt/audio3

Add audio output support!
This commit is contained in:
Dan Halbert 2018-04-13 14:59:10 -04:00 committed by GitHub
commit 10eabf6bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2951 additions and 720 deletions

View File

@ -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))

View File

@ -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);
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -6,5 +6,5 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
QSPI_FLASH_FILESYSTEM = 1
CHIP_VARIANT = SAMD51J19A
CHIP_VARIANT = SAMD51G19A
CHIP_FAMILY = samd51

165
ports/atmel-samd/clocks.c Normal file
View File

@ -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
}

55
ports/atmel-samd/clocks.h Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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"

View File

@ -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;

View File

@ -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;