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;
|
||||
#ifdef SAMD51
|
||||
channel0_enabled = self->left_channel == &pin_PA02 || self->right_channel == &pin_PA02;
|
||||
bool channel1_enabled = self->left_channel == &pin_PA05 || self->right_channel == &pin_PA05;
|
||||
#endif
|
||||
|
||||
if (channel0_enabled) {
|
||||
#ifdef SAMD21
|
||||
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI;
|
||||
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_AVCC |
|
||||
DAC_CTRLB_LEFTADJ |
|
||||
DAC_CTRLB_EOEN;
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI0;
|
||||
DAC->DACCTRL[0].reg = DAC_DACCTRL_CCTRL_CC1M |
|
||||
DAC_DACCTRL_ENABLE |
|
||||
DAC_DACCTRL_LEFTADJ;
|
||||
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
|
||||
#endif
|
||||
}
|
||||
#ifdef SAMD51
|
||||
if (channel1_enabled) {
|
||||
DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI1;
|
||||
DAC->DACCTRL[1].reg = DAC_DACCTRL_CCTRL_CC1M |
|
||||
DAC_DACCTRL_ENABLE |
|
||||
DAC_DACCTRL_LEFTADJ;
|
||||
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Re-enable the DAC
|
||||
DAC->CTRLA.bit.ENABLE = 1;
|
||||
#ifdef SAMD21
|
||||
while (DAC->STATUS.bit.SYNCBUSY == 1) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
while (DAC->SYNCBUSY.bit.ENABLE == 1) {}
|
||||
#endif
|
||||
|
||||
// Use a timer to coordinate when DAC conversions occur.
|
||||
Tc *t = NULL;
|
||||
Tc *tcs[TC_INST_NUM] = TC_INSTS;
|
||||
uint8_t tc_index = TC_INST_NUM;
|
||||
for (uint8_t i = TC_INST_NUM; i > 0; i--) {
|
||||
if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
|
||||
t = tcs[i - 1];
|
||||
if (tc_insts[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
|
||||
t = tc_insts[i - 1];
|
||||
tc_index = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -192,234 +171,85 @@ static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t*
|
|||
mp_raise_RuntimeError("All timers in use");
|
||||
return;
|
||||
}
|
||||
MP_STATE_VM(audioout_sample_timer) = gc_alloc(sizeof(struct tc_module), false);
|
||||
if (MP_STATE_VM(audioout_sample_timer) == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
self->tc_index = tc_index;
|
||||
|
||||
// Use the 48mhz clocks on both the SAMD21 and 51 because we will be going much slower.
|
||||
uint8_t tc_gclk = 0;
|
||||
#ifdef SAMD51
|
||||
tc_gclk = 1;
|
||||
#endif
|
||||
|
||||
turn_on_clocks(true, tc_index, tc_gclk);
|
||||
|
||||
// Don't bother setting the period. We set it before you playback anything.
|
||||
struct tc_config config_tc;
|
||||
tc_get_config_defaults(&config_tc);
|
||||
config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
|
||||
config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
|
||||
config_tc.wave_generation = TC_WAVE_GENERATION_MATCH_FREQ;
|
||||
if (tc_init(MP_STATE_VM(audioout_sample_timer), t, &config_tc) != STATUS_OK) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_OSError(MP_EIO);
|
||||
return;
|
||||
};
|
||||
|
||||
struct tc_events events_tc;
|
||||
events_tc.generate_event_on_overflow = true;
|
||||
events_tc.on_event_perform_action = false;
|
||||
events_tc.event_action = TC_EVENT_ACTION_OFF;
|
||||
tc_enable_events(MP_STATE_VM(audioout_sample_timer), &events_tc);
|
||||
|
||||
tc_enable(MP_STATE_VM(audioout_sample_timer));
|
||||
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
||||
tc_set_enable(t, false);
|
||||
tc_reset(t);
|
||||
#ifdef SAMD51
|
||||
t->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
t->COUNT16.CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_MFRQ_Val;
|
||||
#endif
|
||||
t->COUNT16.EVCTRL.reg = TC_EVCTRL_OVFEO;
|
||||
tc_set_enable(t, true);
|
||||
t->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;
|
||||
|
||||
// Connect the timer overflow event, which happens at the target frequency,
|
||||
// to the DAC conversion trigger.
|
||||
MP_STATE_VM(audioout_sample_event) = gc_alloc(sizeof(struct events_resource), false);
|
||||
if (MP_STATE_VM(audioout_sample_event) == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
struct events_config config;
|
||||
events_get_config_defaults(&config);
|
||||
// to the DAC conversion trigger(s).
|
||||
#ifdef SAMD21
|
||||
#define FIRST_TC_GEN_ID EVSYS_ID_GEN_TC3_OVF
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
#define FIRST_TC_GEN_ID EVSYS_ID_GEN_TC0_OVF
|
||||
#endif
|
||||
uint8_t tc_gen_id = FIRST_TC_GEN_ID + 3 * tc_index;
|
||||
|
||||
uint8_t generator = EVSYS_ID_GEN_TC3_OVF;
|
||||
if (t == TC4) {
|
||||
generator = EVSYS_ID_GEN_TC4_OVF;
|
||||
} else if (t == TC5) {
|
||||
generator = EVSYS_ID_GEN_TC5_OVF;
|
||||
#ifdef TC6
|
||||
} else if (t == TC6) {
|
||||
generator = EVSYS_ID_GEN_TC6_OVF;
|
||||
#endif
|
||||
#ifdef TC7
|
||||
} else if (t == TC7) {
|
||||
generator = EVSYS_ID_GEN_TC7_OVF;
|
||||
#endif
|
||||
}
|
||||
turn_on_event_system();
|
||||
|
||||
config.generator = generator;
|
||||
config.path = EVENTS_PATH_ASYNCHRONOUS;
|
||||
if (events_allocate(MP_STATE_VM(audioout_sample_event), &config) != STATUS_OK ||
|
||||
events_attach_user(MP_STATE_VM(audioout_sample_event), EVSYS_ID_USER_DAC_START) != STATUS_OK) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_OSError(MP_EIO);
|
||||
return;
|
||||
}
|
||||
// Find a free event channel. We start at the highest channels because we only need and async
|
||||
// path.
|
||||
uint8_t channel = find_async_event_channel();
|
||||
#ifdef SAMD51
|
||||
connect_event_user_to_channel(EVSYS_ID_USER_DAC_START_1, channel);
|
||||
#define EVSYS_ID_USER_DAC_START EVSYS_ID_USER_DAC_START_0
|
||||
#endif
|
||||
connect_event_user_to_channel(EVSYS_ID_USER_DAC_START, channel);
|
||||
init_async_event_channel(channel, tc_gen_id);
|
||||
|
||||
// Connect the DAC to DMA
|
||||
MP_STATE_VM(audioout_dac_event) = gc_alloc(sizeof(struct events_resource), false);
|
||||
if (MP_STATE_VM(audioout_dac_event) == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
events_get_config_defaults(&config);
|
||||
config.generator = EVSYS_ID_GEN_DAC_EMPTY;
|
||||
config.path = EVENTS_PATH_ASYNCHRONOUS;
|
||||
if (events_allocate(MP_STATE_VM(audioout_dac_event), &config) != STATUS_OK ||
|
||||
events_attach_user(MP_STATE_VM(audioout_dac_event), EVSYS_ID_USER_DMAC_CH_0) != STATUS_OK) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_OSError(MP_EIO);
|
||||
return;
|
||||
}
|
||||
self->tc_to_dac_event_channel = channel;
|
||||
|
||||
// Leave the DMA setup to the specific constructor.
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* self,
|
||||
const mcu_pin_obj_t* pin,
|
||||
uint16_t* buffer,
|
||||
uint32_t len,
|
||||
uint8_t bytes_per_sample) {
|
||||
self->pin = pin;
|
||||
if (pin != &pin_PA02) {
|
||||
mp_raise_ValueError("Invalid pin");
|
||||
}
|
||||
if (refcount == 0) {
|
||||
refcount++;
|
||||
shared_construct(self, pin);
|
||||
}
|
||||
|
||||
self->buffer = (uint8_t*) buffer;
|
||||
self->second_buffer = NULL;
|
||||
self->bytes_per_sample = bytes_per_sample;
|
||||
self->len = len;
|
||||
self->frequency = 8000;
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_construct_from_file(audioio_audioout_obj_t* self,
|
||||
const mcu_pin_obj_t* pin,
|
||||
pyb_file_obj_t* file) {
|
||||
self->pin = pin;
|
||||
if (pin != &pin_PA02) {
|
||||
mp_raise_ValueError("Invalid pin");
|
||||
}
|
||||
if (refcount == 0) {
|
||||
refcount++;
|
||||
shared_construct(self, pin);
|
||||
}
|
||||
if (MP_STATE_VM(audiodma_block_counter) == NULL && !allocate_block_counter()) {
|
||||
mp_raise_RuntimeError("Unable to allocate audio DMA block counter.");
|
||||
}
|
||||
|
||||
// Load the wave
|
||||
self->file = file;
|
||||
uint8_t chunk_header[16];
|
||||
f_rewind(&self->file->fp);
|
||||
UINT bytes_read;
|
||||
f_read(&self->file->fp, chunk_header, 16, &bytes_read);
|
||||
if (bytes_read != 16 ||
|
||||
memcmp(chunk_header, "RIFF", 4) != 0 ||
|
||||
memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) {
|
||||
mp_raise_ValueError("Invalid wave file");
|
||||
}
|
||||
uint32_t format_size;
|
||||
f_read(&self->file->fp, &format_size, 4, &bytes_read);
|
||||
if (bytes_read != 4 ||
|
||||
format_size > sizeof(struct wave_format_chunk)) {
|
||||
mp_raise_ValueError("Invalid format chunk size");
|
||||
}
|
||||
struct wave_format_chunk format;
|
||||
f_read(&self->file->fp, &format, format_size, &bytes_read);
|
||||
if (bytes_read != format_size) {
|
||||
}
|
||||
|
||||
if (format.audio_format != 1 ||
|
||||
format.num_channels > 1 ||
|
||||
format.bits_per_sample > 16 ||
|
||||
(format_size == 18 &&
|
||||
format.extra_params != 0)) {
|
||||
mp_raise_ValueError("Unsupported format");
|
||||
}
|
||||
// Get the frequency
|
||||
self->frequency = format.sample_rate;
|
||||
self->len = 512;
|
||||
|
||||
self->bytes_per_sample = format.bits_per_sample / 8;
|
||||
|
||||
// TODO(tannewt): Skip any extra chunks that occur before the data section.
|
||||
|
||||
uint8_t data_tag[4];
|
||||
f_read(&self->file->fp, &data_tag, 4, &bytes_read);
|
||||
if (bytes_read != 4 ||
|
||||
memcmp((uint8_t *) data_tag, "data", 4) != 0) {
|
||||
mp_raise_ValueError("Data chunk must follow fmt chunk");
|
||||
}
|
||||
|
||||
uint32_t data_length;
|
||||
f_read(&self->file->fp, &data_length, 4, &bytes_read);
|
||||
if (bytes_read != 4) {
|
||||
mp_raise_ValueError("Invalid file");
|
||||
}
|
||||
self->file_length = data_length;
|
||||
self->data_start = self->file->fp.fptr;
|
||||
|
||||
// Try to allocate two buffers, one will be loaded from file and the other
|
||||
// DMAed to DAC.
|
||||
self->buffer = gc_alloc(self->len, false);
|
||||
if (self->buffer == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
|
||||
self->second_buffer = gc_alloc(self->len, false);
|
||||
if (self->second_buffer == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
|
||||
self->second_descriptor = gc_alloc(sizeof(DmacDescriptor), false);
|
||||
if (self->second_descriptor == NULL) {
|
||||
common_hal_audioio_audioout_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
// Leave the DMA setup to playback.
|
||||
}
|
||||
|
||||
bool common_hal_audioio_audioout_deinited(audioio_audioout_obj_t* self) {
|
||||
return self->pin == mp_const_none;
|
||||
return self->left_channel == mp_const_none;
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self) {
|
||||
if (common_hal_audioio_audioout_deinited(self)) {
|
||||
return;
|
||||
}
|
||||
refcount--;
|
||||
if (refcount == 0) {
|
||||
if (MP_STATE_VM(audioout_sample_timer) != NULL) {
|
||||
tc_reset(MP_STATE_VM(audioout_sample_timer));
|
||||
gc_free(MP_STATE_VM(audioout_sample_timer));
|
||||
MP_STATE_VM(audioout_sample_timer) = NULL;
|
||||
}
|
||||
if (MP_STATE_VM(audioout_dac_instance) != NULL) {
|
||||
dac_reset(MP_STATE_VM(audioout_dac_instance));
|
||||
gc_free(MP_STATE_VM(audioout_dac_instance));
|
||||
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));
|
||||
gc_free(MP_STATE_VM(audioout_sample_event));
|
||||
MP_STATE_VM(audioout_sample_event) = NULL;
|
||||
}
|
||||
if (MP_STATE_VM(audioout_dac_event) != NULL) {
|
||||
events_release(MP_STATE_VM(audioout_dac_event));
|
||||
gc_free(MP_STATE_VM(audioout_dac_event));
|
||||
MP_STATE_VM(audioout_dac_event) = NULL;
|
||||
}
|
||||
reset_pin(self->pin->pin);
|
||||
}
|
||||
|
||||
self->pin = mp_const_none;
|
||||
DAC->CTRLA.bit.ENABLE = 0;
|
||||
#ifdef SAMD21
|
||||
while (DAC->STATUS.bit.SYNCBUSY == 1) {}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
while (DAC->SYNCBUSY.bit.ENABLE == 1) {}
|
||||
#endif
|
||||
|
||||
disable_event_channel(self->tc_to_dac_event_channel);
|
||||
|
||||
reset_pin(self->left_channel->pin);
|
||||
self->left_channel = mp_const_none;
|
||||
#ifdef SAMD51
|
||||
reset_pin(self->right_channel->pin);
|
||||
self->right_channel = mp_const_none;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void set_timer_frequency(uint32_t frequency) {
|
||||
uint32_t system_clock = system_cpu_clock_get_hz();
|
||||
static void set_timer_frequency(Tc* timer, uint32_t frequency) {
|
||||
uint32_t system_clock = 48000000;
|
||||
uint32_t new_top;
|
||||
uint8_t new_divisor;
|
||||
for (new_divisor = 0; new_divisor < 8; new_divisor++) {
|
||||
|
@ -428,133 +258,88 @@ static void set_timer_frequency(uint32_t frequency) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
uint8_t old_divisor = MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER;
|
||||
uint8_t old_divisor = timer->COUNT16.CTRLA.bit.PRESCALER;
|
||||
if (new_divisor != old_divisor) {
|
||||
tc_disable(MP_STATE_VM(audioout_sample_timer));
|
||||
MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER = new_divisor;
|
||||
tc_enable(MP_STATE_VM(audioout_sample_timer));
|
||||
}
|
||||
while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
|
||||
/* Wait for sync */
|
||||
}
|
||||
MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CC[0].reg = new_top;
|
||||
while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
|
||||
/* Wait for sync */
|
||||
tc_set_enable(timer, false);
|
||||
timer->COUNT16.CTRLA.bit.PRESCALER = new_divisor;
|
||||
tc_set_enable(timer, true);
|
||||
}
|
||||
tc_wait_for_sync(timer);
|
||||
timer->COUNT16.CC[0].reg = new_top;
|
||||
tc_wait_for_sync(timer);
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, bool loop) {
|
||||
common_hal_audioio_audioout_get_playing(self);
|
||||
// Shut down any active playback.
|
||||
if (active_audioout != NULL) {
|
||||
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
||||
dma_abort_job(&audio_dma);
|
||||
} else {
|
||||
dac_enable(MP_STATE_VM(audioout_dac_instance));
|
||||
void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self,
|
||||
mp_obj_t sample, bool loop) {
|
||||
if (common_hal_audioio_audioout_get_playing(self)) {
|
||||
common_hal_audioio_audioout_stop(self);
|
||||
}
|
||||
switch_audiodma_trigger(DAC_DMAC_ID_EMPTY);
|
||||
struct dma_descriptor_config descriptor_config;
|
||||
dma_descriptor_get_config_defaults(&descriptor_config);
|
||||
if (self->bytes_per_sample == 2) {
|
||||
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
|
||||
} else {
|
||||
descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
|
||||
audio_dma_result result = AUDIO_DMA_OK;
|
||||
#ifdef SAMD21
|
||||
result = audio_dma_setup_playback(&self->left_dma, sample, loop, true, 0,
|
||||
false /* output unsigned */,
|
||||
(uint32_t) &DAC->DATABUF.reg,
|
||||
DAC_DMAC_ID_EMPTY);
|
||||
#endif
|
||||
|
||||
#ifdef SAMD51
|
||||
uint32_t left_channel_reg = (uint32_t) &DAC->DATABUF[0].reg;
|
||||
uint8_t left_channel_trigger = DAC_DMAC_ID_EMPTY_0;
|
||||
uint32_t right_channel_reg = 0;
|
||||
uint8_t right_channel_trigger = 0;
|
||||
if (self->left_channel == &pin_PA05) {
|
||||
left_channel_reg = (uint32_t) &DAC->DATABUF[1].reg;
|
||||
left_channel_trigger = DAC_DMAC_ID_EMPTY_1;
|
||||
} else if (self->right_channel == &pin_PA05) {
|
||||
right_channel_reg = (uint32_t) &DAC->DATABUF[1].reg;
|
||||
right_channel_trigger = DAC_DMAC_ID_EMPTY_1;
|
||||
}
|
||||
|
||||
descriptor_config.dst_increment_enable = false;
|
||||
// Block transfer count is the number of beats per block (aka descriptor).
|
||||
// In this case there are two bytes per beat so divide the length by two.
|
||||
descriptor_config.block_transfer_count = self->len / self->bytes_per_sample;
|
||||
descriptor_config.source_address = ((uint32_t)self->buffer + self->len);
|
||||
descriptor_config.destination_address = ((uint32_t)&DAC->DATABUF.reg + 1);
|
||||
descriptor_config.event_output_selection = DMA_EVENT_OUTPUT_BLOCK;
|
||||
self->loop = loop;
|
||||
if (self->second_buffer == NULL) {
|
||||
if (loop) {
|
||||
descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
|
||||
} else {
|
||||
descriptor_config.next_descriptor_address = 0;
|
||||
}
|
||||
} else {
|
||||
descriptor_config.next_descriptor_address = ((uint32_t)self->second_descriptor);
|
||||
if (self->right_channel == &pin_PA02) {
|
||||
right_channel_reg = (uint32_t) &DAC->DATABUF[0].reg;
|
||||
right_channel_trigger = DAC_DMAC_ID_EMPTY_0;
|
||||
}
|
||||
dma_descriptor_create(audio_dma.descriptor, &descriptor_config);
|
||||
|
||||
if (self->second_buffer != NULL) {
|
||||
// TODO(tannewt): Correctly set the end of this.
|
||||
descriptor_config.block_transfer_count = self->len / self->bytes_per_sample;
|
||||
descriptor_config.source_address = ((uint32_t)self->second_buffer + self->len);
|
||||
descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
|
||||
dma_descriptor_create(self->second_descriptor, &descriptor_config);
|
||||
|
||||
self->last_loaded_block = 0;
|
||||
self->bytes_remaining = self->file_length;
|
||||
|
||||
f_lseek(&self->file->fp, self->data_start);
|
||||
// Seek to the start of the PCM.
|
||||
UINT length_read;
|
||||
f_read(&self->file->fp, self->buffer, self->len, &length_read);
|
||||
self->bytes_remaining -= length_read;
|
||||
if (self->bytes_per_sample == 2) {
|
||||
// Undo twos complement.
|
||||
for (uint16_t i = 0; i < length_read / 2; i++) {
|
||||
self->buffer[2 * i + 1] ^= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
f_read(&self->file->fp, self->second_buffer, self->len, &length_read);
|
||||
self->bytes_remaining -= length_read;
|
||||
if (self->bytes_per_sample == 2) {
|
||||
// Undo twos complement.
|
||||
for (uint16_t i = 0; i < length_read / 2; i++) {
|
||||
self->second_buffer[2 * i + 1] ^= 0x80;
|
||||
}
|
||||
result = audio_dma_setup_playback(&self->left_dma, sample, loop, true, 0,
|
||||
false /* output unsigned */,
|
||||
left_channel_reg,
|
||||
left_channel_trigger);
|
||||
if (right_channel_reg != 0 && result == AUDIO_DMA_OK) {
|
||||
result = audio_dma_setup_playback(&self->right_dma, sample, loop, true, 1,
|
||||
false /* output unsigned */,
|
||||
right_channel_reg,
|
||||
right_channel_trigger);
|
||||
}
|
||||
#endif
|
||||
if (result != AUDIO_DMA_OK) {
|
||||
audio_dma_stop(&self->left_dma);
|
||||
#ifdef SAMD51
|
||||
audio_dma_stop(&self->right_dma);
|
||||
#endif
|
||||
if (result == AUDIO_DMA_DMA_BUSY) {
|
||||
mp_raise_RuntimeError("No DMA channel found");
|
||||
} else if (result == AUDIO_DMA_MEMORY_ERROR) {
|
||||
mp_raise_RuntimeError("Unable to allocate buffers for signed conversion");
|
||||
}
|
||||
}
|
||||
active_audioout = self;
|
||||
dma_start_transfer_job(&audio_dma);
|
||||
|
||||
if (MP_STATE_VM(audiodma_block_counter) != NULL) {
|
||||
tc_start_counter(MP_STATE_VM(audiodma_block_counter));
|
||||
}
|
||||
set_timer_frequency(self->frequency);
|
||||
tc_start_counter(MP_STATE_VM(audioout_sample_timer));
|
||||
Tc* timer = tc_insts[self->tc_index];
|
||||
set_timer_frequency(timer, audiosample_sample_rate(sample));
|
||||
timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;
|
||||
while (timer->COUNT16.STATUS.bit.STOP == 1) {}
|
||||
self->playing = true;
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
|
||||
if (active_audioout == self) {
|
||||
if (MP_STATE_VM(audiodma_block_counter) != NULL) {
|
||||
tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
|
||||
}
|
||||
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
||||
dma_abort_job(&audio_dma);
|
||||
active_audioout = NULL;
|
||||
dac_disable(MP_STATE_VM(audioout_dac_instance));
|
||||
}
|
||||
Tc* timer = tc_insts[self->tc_index];
|
||||
timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;
|
||||
audio_dma_stop(&self->left_dma);
|
||||
#ifdef SAMD51
|
||||
audio_dma_stop(&self->right_dma);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self) {
|
||||
if (!dma_is_busy(&audio_dma)) {
|
||||
if (active_audioout != NULL) {
|
||||
common_hal_audioio_audioout_stop(active_audioout);
|
||||
}
|
||||
active_audioout = NULL;
|
||||
bool now_playing = audio_dma_get_playing(&self->left_dma);
|
||||
if (self->playing && !now_playing) {
|
||||
common_hal_audioio_audioout_stop(self);
|
||||
}
|
||||
return active_audioout == self;
|
||||
}
|
||||
|
||||
void common_hal_audioio_audioout_set_frequency(audioio_audioout_obj_t* self,
|
||||
uint32_t frequency) {
|
||||
if (frequency == 0 || frequency > 350000) {
|
||||
mp_raise_ValueError("Unsupported playback frequency");
|
||||
}
|
||||
self->frequency = frequency;
|
||||
|
||||
if (common_hal_audioio_audioout_get_playing(self)) {
|
||||
set_timer_frequency(frequency);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t common_hal_audioio_audioout_get_frequency(audioio_audioout_obj_t* self) {
|
||||
return self->frequency;
|
||||
return now_playing;
|
||||
}
|
||||
|
|
|
@ -29,28 +29,21 @@
|
|||
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
#include "audio_dma.h"
|
||||
#include "py/obj.h"
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
const mcu_pin_obj_t *pin;
|
||||
uint32_t frequency;
|
||||
uint8_t* buffer;
|
||||
const mcu_pin_obj_t *left_channel;
|
||||
audio_dma_t left_dma;
|
||||
#ifdef SAMD51
|
||||
const mcu_pin_obj_t *right_channel;
|
||||
audio_dma_t right_dma;
|
||||
#endif
|
||||
uint8_t tc_index;
|
||||
|
||||
// File playback specific:
|
||||
uint8_t* second_buffer;
|
||||
DmacDescriptor* second_descriptor;
|
||||
uint32_t file_length; // In bytes
|
||||
uint16_t data_start; // Where the data values start
|
||||
uint8_t bytes_per_sample;
|
||||
bool signed_samples;
|
||||
uint16_t last_loaded_block;
|
||||
uint32_t bytes_remaining;
|
||||
|
||||
bool loop;
|
||||
uint32_t len;
|
||||
pyb_file_obj_t* file;
|
||||
uint8_t tc_to_dac_event_channel;
|
||||
bool playing;
|
||||
} audioio_audioout_obj_t;
|
||||
|
||||
void audioout_reset(void);
|
||||
|
|
|
@ -48,7 +48,6 @@ static uint32_t tc_periods[TC_INST_NUM];
|
|||
|
||||
uint32_t target_tcc_frequencies[TCC_INST_NUM];
|
||||
uint8_t tcc_refcount[TCC_INST_NUM];
|
||||
const uint16_t prescaler[8] = {1, 2, 4, 8, 16, 64, 256, 1024};
|
||||
|
||||
// This bitmask keeps track of which channels of a TCC are currently claimed.
|
||||
#ifdef SAMD21
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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 "events.h"
|
||||
|
||||
#include "clocks.h"
|
||||
|
||||
#include "py/runtime.h"
|
||||
|
||||
#ifdef SAMD21
|
||||
#include "hpl/pm/hpl_pm_base.h"
|
||||
#endif
|
||||
|
||||
#ifdef SAMD51
|
||||
#include "hri/hri_mclk_d51.h"
|
||||
#endif
|
||||
|
||||
void turn_on_event_system(void) {
|
||||
#ifdef SAMD51
|
||||
hri_mclk_set_APBBMASK_EVSYS_bit(MCLK);
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
_pm_enable_bus_clock(PM_BUS_APBC, EVSYS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_event_system(void) {
|
||||
#ifdef SAMD51
|
||||
EVSYS->CTRLA.bit.SWRST = true;
|
||||
hri_mclk_clear_APBBMASK_EVSYS_bit(MCLK);
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
EVSYS->CTRL.bit.SWRST = true;
|
||||
_pm_disable_bus_clock(PM_BUS_APBC, EVSYS);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool channel_free(int8_t channel) {
|
||||
uint8_t generator;
|
||||
#ifdef SAMD51
|
||||
generator = EVSYS->Channel[channel].CHANNEL.bit.EVGEN;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
// Explicitly do a byte write so the peripheral knows we're just wanting to read the channel
|
||||
// rather than write to it.
|
||||
*((uint8_t*) &EVSYS->CHANNEL.reg) = channel;
|
||||
generator = (EVSYS->CHANNEL.reg & EVSYS_CHANNEL_EVGEN_Msk) >> EVSYS_CHANNEL_EVGEN_Pos;
|
||||
#endif
|
||||
return generator == 0;
|
||||
}
|
||||
|
||||
uint8_t find_async_event_channel(void) {
|
||||
int8_t channel;
|
||||
for (channel = EVSYS_CHANNELS - 1; channel > 0; channel--) {
|
||||
if (channel_free(channel)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (channel < 0) {
|
||||
mp_raise_RuntimeError("All event channels in use");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
#ifdef SAMD21
|
||||
#define EVSYS_SYNCH_NUM EVSYS_CHANNELS
|
||||
#endif
|
||||
uint8_t find_sync_event_channel(void) {
|
||||
int8_t channel;
|
||||
for (channel = 0; channel < EVSYS_SYNCH_NUM; channel++) {
|
||||
if (channel_free(channel)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (channel >= EVSYS_SYNCH_NUM) {
|
||||
mp_raise_RuntimeError("All sync event channels in use");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
void disable_event_channel(uint8_t channel_number) {
|
||||
#ifdef SAMD51
|
||||
EVSYS->Channel[channel_number].CHANNEL.reg = 0;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel_number);
|
||||
#endif
|
||||
}
|
||||
|
||||
void disable_event_user(uint8_t user_number) {
|
||||
#ifdef SAMD51
|
||||
EVSYS->USER[user_number].reg = 0;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
EVSYS->USER.reg = EVSYS_USER_USER(user_number);
|
||||
#endif
|
||||
}
|
||||
|
||||
void connect_event_user_to_channel(uint8_t user, uint8_t channel) {
|
||||
#ifdef SAMD51
|
||||
EVSYS->USER[user].reg = channel + 1;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
EVSYS->USER.reg = EVSYS_USER_USER(user) | EVSYS_USER_CHANNEL(channel + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_async_event_channel(uint8_t channel, uint8_t generator) {
|
||||
#ifdef SAMD51
|
||||
EVSYS->Channel[channel].CHANNEL.reg = EVSYS_CHANNEL_EVGEN(generator) | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel) | EVSYS_CHANNEL_EVGEN(generator) | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_event_channel_interrupt(uint8_t channel, uint8_t gclk, uint8_t generator) {
|
||||
connect_gclk_to_peripheral(gclk, EVSYS_GCLK_ID_0 + channel);
|
||||
#ifdef SAMD51
|
||||
EVSYS->Channel[channel].CHANNEL.reg = EVSYS_CHANNEL_EVGEN(generator) |
|
||||
EVSYS_CHANNEL_PATH_SYNCHRONOUS |
|
||||
EVSYS_CHANNEL_EDGSEL_RISING_EDGE;
|
||||
#endif
|
||||
#ifdef SAMD21
|
||||
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel) |
|
||||
EVSYS_CHANNEL_EVGEN(generator) |
|
||||
EVSYS_CHANNEL_PATH_RESYNCHRONIZED |
|
||||
EVSYS_CHANNEL_EDGSEL_RISING_EDGE;
|
||||
if (channel > 7) {
|
||||
uint8_t value = 1 << (channel - 7);
|
||||
EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVDp8(value) | EVSYS_INTFLAG_OVRp8(value);
|
||||
EVSYS->INTENSET.reg = EVSYS_INTENSET_EVDp8(value) | EVSYS_INTENSET_OVRp8(value);
|
||||
} else {
|
||||
uint8_t value = 1 << channel;
|
||||
EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD(value) | EVSYS_INTFLAG_OVR(value);
|
||||
EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD(value) | EVSYS_INTENSET_OVR(value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool event_interrupt_active(uint8_t channel) {
|
||||
bool active = false;
|
||||
#ifdef SAMD21
|
||||
if (channel > 7) {
|
||||
uint8_t value = 1 << (channel - 7);
|
||||
active = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_EVDp8(value)) != 0;
|
||||
// Only clear if we know its active, otherwise there is the possibility it becomes active
|
||||
// after we check but before we clear.
|
||||
if (active) {
|
||||
EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVDp8(value) | EVSYS_INTFLAG_OVRp8(value);
|
||||
}
|
||||
} else {
|
||||
uint8_t value = 1 << channel;
|
||||
active = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_EVD(value)) != 0;
|
||||
if (active) {
|
||||
EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD(value) | EVSYS_INTFLAG_OVR(value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef SAMD51
|
||||
active = EVSYS->Channel[channel].CHINTFLAG.bit.EVD;
|
||||
// Only clear if we know its active, otherwise there is the possibility it becomes after we
|
||||
// check but before we clear.
|
||||
if (active) {
|
||||
EVSYS->Channel[channel].CHINTFLAG.reg = EVSYS_CHINTFLAG_EVD;
|
||||
}
|
||||
#endif
|
||||
return active;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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_EVENTS_H
|
||||
#define MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "include/sam.h"
|
||||
|
||||
void turn_on_event_system(void);
|
||||
void reset_event_system(void);
|
||||
uint8_t find_async_event_channel(void);
|
||||
uint8_t find_sync_event_channel(void);
|
||||
void disable_event_channel(uint8_t channel_number);
|
||||
void disable_event_user(uint8_t user_number);
|
||||
void connect_event_user_to_channel(uint8_t user, uint8_t channel);
|
||||
void init_async_event_channel(uint8_t channel, uint8_t generator);
|
||||
void init_event_channel_interrupt(uint8_t channel, uint8_t gclk, uint8_t generator);
|
||||
bool event_interrupt_active(uint8_t channel);
|
||||
|
||||
#endif // MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H
|
|
@ -195,12 +195,19 @@ extern const struct _mp_obj_module_t usb_hid_module;
|
|||
// Scan gamepad every 32ms
|
||||
#define CIRCUITPY_GAMEPAD_TICKS 0x1f
|
||||
|
||||
#if defined(__SAMD51G19A__) || defined(__SAMD51G18A__)
|
||||
#define AUDIOBUSIO_MODULE
|
||||
#else
|
||||
#define AUDIOBUSIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module },
|
||||
#endif
|
||||
|
||||
#define EXTRA_BUILTIN_MODULES \
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_audioio), (mp_obj_t)&audioio_module }, \
|
||||
AUDIOBUSIO_MODULE \
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_bitbangio), (mp_obj_t)&bitbangio_module }, \
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_gamepad),(mp_obj_t)&gamepad_module }
|
||||
// { MP_OBJ_NEW_QSTR(MP_QSTR_audioio), (mp_obj_t)&audioio_module },
|
||||
// { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module },
|
||||
#define EXPRESS_BOARD
|
||||
|
||||
#else
|
||||
#define MICROPY_PY_BUILTINS_REVERSED (0)
|
||||
#define MICROPY_PY_MICROPYTHON_MEM_INFO (0)
|
||||
|
@ -253,15 +260,12 @@ extern const struct _mp_obj_module_t usb_hid_module;
|
|||
|
||||
#define MP_STATE_PORT MP_STATE_VM
|
||||
|
||||
#include "shared_dma.h"
|
||||
|
||||
#define MICROPY_PORT_ROOT_POINTERS \
|
||||
const char *readline_hist[8]; \
|
||||
vstr_t *repl_line; \
|
||||
struct tc_module* audiodma_block_counter; \
|
||||
struct events_resource* audiodma_block_event; \
|
||||
struct tc_module* audioout_sample_timer; \
|
||||
struct dac_module* audioout_dac_instance; \
|
||||
struct events_resource* audioout_sample_event; \
|
||||
struct events_resource* audioout_dac_event; \
|
||||
mp_obj_t playing_audio[AUDIO_DMA_CHANNEL_COUNT]; \
|
||||
FLASH_ROOT_POINTERS \
|
||||
|
||||
void run_background_tasks(void);
|
||||
|
|
|
@ -34,18 +34,10 @@
|
|||
|
||||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
|
||||
// We allocate three DMA resources for the entire lifecycle of the board (not the
|
||||
// vm) because the general_dma resource will be shared between the REPL and SPI
|
||||
// flash. Both uses must block each other in order to prevent conflict.
|
||||
COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[3];
|
||||
COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT];
|
||||
|
||||
// Don't use these directly. They are used by the DMA engine itself.
|
||||
COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[3];
|
||||
|
||||
#define AUDIO_DMA_CHANNEL 0
|
||||
#define SHARED_TX_CHANNEL 1
|
||||
#define SHARED_RX_CHANNEL 2
|
||||
|
||||
COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT];
|
||||
|
||||
#ifdef SAMD21
|
||||
#define FIRST_SERCOM_RX_TRIGSRC 0x01
|
||||
|
@ -56,51 +48,6 @@ COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[3];
|
|||
#define FIRST_SERCOM_TX_TRIGSRC 0x05
|
||||
#endif
|
||||
|
||||
// static void dma_configure_audio(uint8_t channel) {
|
||||
// system_interrupt_enter_critical_section();
|
||||
// /** Select the DMA channel and clear software trigger */
|
||||
// DMAC->CHID.reg = DMAC_CHID_ID(channel);
|
||||
// DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
|
||||
// DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
|
||||
// DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel));
|
||||
// uint32_t event_output_enable = 0;
|
||||
// if (output_event) {
|
||||
// event_output_enable = DMAC_CHCTRLB_EVOE;
|
||||
// }
|
||||
// DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) |
|
||||
// DMAC_CHCTRLB_TRIGSRC(trigsrc) |
|
||||
// DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT) |
|
||||
// event_output_enable;
|
||||
// // config.peripheral_trigger = DAC_DMAC_ID_EMPTY;
|
||||
// // config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
|
||||
// // config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
|
||||
// // config.event_config.event_output_enable = true;
|
||||
// system_interrupt_leave_critical_section();
|
||||
// }
|
||||
|
||||
void init_shared_dma(void) {
|
||||
// Turn on the clocks
|
||||
#ifdef SAMD51
|
||||
MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
|
||||
PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
|
||||
#endif
|
||||
|
||||
DMAC->CTRL.reg = DMAC_CTRL_SWRST;
|
||||
|
||||
DMAC->BASEADDR.reg = (uint32_t) dma_descriptors;
|
||||
DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors;
|
||||
|
||||
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0;
|
||||
|
||||
// This allocates the lowest channel first so make sure the audio is first
|
||||
// so it gets the highest priority.
|
||||
// dma_configure_audio(0);
|
||||
}
|
||||
|
||||
static uint8_t sercom_index(Sercom* sercom) {
|
||||
#ifdef SAMD21
|
||||
return ((uint32_t) sercom - (uint32_t) SERCOM0) / 0x400;
|
||||
|
@ -115,7 +62,7 @@ static uint8_t sercom_index(Sercom* sercom) {
|
|||
#endif
|
||||
}
|
||||
|
||||
static void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event) {
|
||||
void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
|
@ -147,7 +94,7 @@ static void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_e
|
|||
#endif
|
||||
}
|
||||
|
||||
static void enable_channel(uint8_t channel_number) {
|
||||
void dma_enable_channel(uint8_t channel_number) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
|
@ -162,7 +109,38 @@ static void enable_channel(uint8_t channel_number) {
|
|||
#endif
|
||||
}
|
||||
|
||||
static uint8_t transfer_status(uint8_t channel_number) {
|
||||
void dma_disable_channel(uint8_t channel_number) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
|
||||
DMAC->CHCTRLA.bit.ENABLE = false;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
#endif
|
||||
|
||||
#ifdef SAMD51
|
||||
DmacChannel* channel = &DMAC->Channel[channel_number];
|
||||
channel->CHCTRLA.bit.ENABLE = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool dma_channel_enabled(uint8_t channel_number) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
|
||||
bool enabled = DMAC->CHCTRLA.bit.ENABLE;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
return enabled;
|
||||
#endif
|
||||
|
||||
#ifdef SAMD51
|
||||
DmacChannel* channel = &DMAC->Channel[channel_number];
|
||||
return channel->CHCTRLA.bit.ENABLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t dma_transfer_status(uint8_t channel_number) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
|
@ -181,7 +159,6 @@ static uint8_t transfer_status(uint8_t channel_number) {
|
|||
static bool channel_free(uint8_t channel_number) {
|
||||
#ifdef SAMD21
|
||||
common_hal_mcu_disable_interrupts();
|
||||
/** Select the DMA channel and clear software trigger */
|
||||
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
|
||||
bool channel_free = DMAC->CHSTATUS.reg == 0;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
|
@ -194,6 +171,29 @@ static bool channel_free(uint8_t channel_number) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void init_shared_dma(void) {
|
||||
// Turn on the clocks
|
||||
#ifdef SAMD51
|
||||
MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
|
||||
#endif
|
||||
|
||||
#ifdef SAMD21
|
||||
PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
|
||||
PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
|
||||
#endif
|
||||
|
||||
DMAC->CTRL.reg = DMAC_CTRL_SWRST;
|
||||
|
||||
DMAC->BASEADDR.reg = (uint32_t) dma_descriptors;
|
||||
DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors;
|
||||
|
||||
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0;
|
||||
|
||||
for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
|
||||
dma_configure(i, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Do write and read simultaneously. If buffer_out is NULL, write the tx byte over and over.
|
||||
// If buffer_out is a real buffer, ignore tx.
|
||||
// DMAs buffer_out -> dest
|
||||
|
@ -284,10 +284,10 @@ static int32_t shared_dma_transfer(void* peripheral,
|
|||
// Start the RX job first so we don't miss the first byte. The TX job clocks
|
||||
// the output.
|
||||
if (rx_active) {
|
||||
enable_channel(SHARED_RX_CHANNEL);
|
||||
dma_enable_channel(SHARED_RX_CHANNEL);
|
||||
}
|
||||
if (tx_active) {
|
||||
enable_channel(SHARED_TX_CHANNEL);
|
||||
dma_enable_channel(SHARED_TX_CHANNEL);
|
||||
}
|
||||
|
||||
|
||||
|
@ -309,10 +309,10 @@ static int32_t shared_dma_transfer(void* peripheral,
|
|||
// Channels cycle between Suspend -> Pending -> Busy and back while transfering. So, we check
|
||||
// the channels transfer status for an error or completion.
|
||||
if (rx_active) {
|
||||
while ((transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {}
|
||||
while ((dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {}
|
||||
}
|
||||
if (tx_active) {
|
||||
while ((transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {}
|
||||
while ((dma_transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {}
|
||||
}
|
||||
|
||||
if (sercom) {
|
||||
|
@ -331,8 +331,8 @@ static int32_t shared_dma_transfer(void* peripheral,
|
|||
}
|
||||
}
|
||||
|
||||
if ((!rx_active || transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) &&
|
||||
(!tx_active || transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) {
|
||||
if ((!rx_active || dma_transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) &&
|
||||
(!tx_active || dma_transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) {
|
||||
return length;
|
||||
}
|
||||
return -2;
|
||||
|
@ -362,75 +362,6 @@ int32_t qspi_dma_read(uint32_t address, uint8_t* buffer, uint32_t length) {
|
|||
}
|
||||
#endif
|
||||
|
||||
bool allocate_block_counter() {
|
||||
// // Find a timer to count DMA block completions.
|
||||
// Tc *t = NULL;
|
||||
// Tc *tcs[TC_INST_NUM] = TC_INSTS;
|
||||
// for (uint8_t i = TC_INST_NUM; i > 0; i--) {
|
||||
// if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
|
||||
// t = tcs[i - 1];
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (t == NULL) {
|
||||
// return false;
|
||||
// }
|
||||
// MP_STATE_VM(audiodma_block_counter) = gc_alloc(sizeof(struct tc_module), false);
|
||||
// if (MP_STATE_VM(audiodma_block_counter) == NULL) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // Don't bother setting the period. We set it before you playback anything.
|
||||
// struct tc_config config_tc;
|
||||
// tc_get_config_defaults(&config_tc);
|
||||
// config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
|
||||
// config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
|
||||
// if (tc_init(MP_STATE_VM(audiodma_block_counter), t, &config_tc) != STATUS_OK) {
|
||||
// return false;
|
||||
// };
|
||||
//
|
||||
// struct tc_events events_tc;
|
||||
// events_tc.generate_event_on_overflow = false;
|
||||
// events_tc.on_event_perform_action = true;
|
||||
// events_tc.event_action = TC_EVENT_ACTION_INCREMENT_COUNTER;
|
||||
// tc_enable_events(MP_STATE_VM(audiodma_block_counter), &events_tc);
|
||||
//
|
||||
// // Connect the timer overflow event, which happens at the target frequency,
|
||||
// // to the DAC conversion trigger.
|
||||
// MP_STATE_VM(audiodma_block_event) = gc_alloc(sizeof(struct events_resource), false);
|
||||
// if (MP_STATE_VM(audiodma_block_event) == NULL) {
|
||||
// return false;
|
||||
// }
|
||||
// struct events_config config;
|
||||
// events_get_config_defaults(&config);
|
||||
//
|
||||
// uint8_t user = EVSYS_ID_USER_TC3_EVU;
|
||||
// if (t == TC4) {
|
||||
// user = EVSYS_ID_USER_TC4_EVU;
|
||||
// } else if (t == TC5) {
|
||||
// user = EVSYS_ID_USER_TC5_EVU;
|
||||
// #ifdef TC6
|
||||
// } else if (t == TC6) {
|
||||
// user = EVSYS_ID_USER_TC6_EVU;
|
||||
// #endif
|
||||
// #ifdef TC7
|
||||
// } else if (t == TC7) {
|
||||
// user = EVSYS_ID_USER_TC7_EVU;
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// config.generator = EVSYS_ID_GEN_DMAC_CH_0;
|
||||
// config.path = EVENTS_PATH_ASYNCHRONOUS;
|
||||
// if (events_allocate(MP_STATE_VM(audiodma_block_event), &config) != STATUS_OK ||
|
||||
// events_attach_user(MP_STATE_VM(audiodma_block_event), user) != STATUS_OK) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// tc_enable(MP_STATE_VM(audiodma_block_counter));
|
||||
// tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
|
||||
return true;
|
||||
}
|
||||
|
||||
void switch_audiodma_trigger(uint8_t trigger_dmac_id) {
|
||||
//dma_configure(audio_dma.channel_id, trigger_dmac_id, true);
|
||||
DmacDescriptor* dma_descriptor(uint8_t channel_number) {
|
||||
return &dma_descriptors[channel_number];
|
||||
}
|
||||
|
|
|
@ -32,6 +32,14 @@
|
|||
|
||||
#include "include/sam.h"
|
||||
|
||||
// We allocate DMA resources for the entire lifecycle of the board (not the
|
||||
// vm) because the general_dma resource will be shared between the REPL and SPI
|
||||
// flash. Both uses must block each other in order to prevent conflict.
|
||||
#define AUDIO_DMA_CHANNEL_COUNT 3
|
||||
#define DMA_CHANNEL_COUNT (AUDIO_DMA_CHANNEL_COUNT + 2)
|
||||
#define SHARED_TX_CHANNEL (DMA_CHANNEL_COUNT - 2)
|
||||
#define SHARED_RX_CHANNEL (DMA_CHANNEL_COUNT - 1)
|
||||
|
||||
volatile bool audio_dma_in_use;
|
||||
|
||||
void init_shared_dma(void);
|
||||
|
@ -45,8 +53,11 @@ int32_t sercom_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length)
|
|||
int32_t sercom_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx);
|
||||
int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length);
|
||||
|
||||
// Allocate a counter to track how far along we are in a DMA double buffer.
|
||||
bool allocate_block_counter(void);
|
||||
void switch_audiodma_trigger(uint8_t trigger_dmac_id);
|
||||
void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event);
|
||||
void dma_enable_channel(uint8_t channel_number);
|
||||
void dma_disable_channel(uint8_t channel_number);
|
||||
bool dma_channel_enabled(uint8_t channel_number);
|
||||
uint8_t dma_transfer_status(uint8_t channel_number);
|
||||
DmacDescriptor* dma_descriptor(uint8_t channel_number);
|
||||
|
||||
#endif // MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H
|
||||
|
|
|
@ -45,11 +45,16 @@
|
|||
|
||||
#include "common-hal/analogio/AnalogIn.h"
|
||||
#include "common-hal/analogio/AnalogOut.h"
|
||||
#include "common-hal/audiobusio/PDMIn.h"
|
||||
#include "common-hal/audiobusio/I2SOut.h"
|
||||
#include "common-hal/audioio/AudioOut.h"
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
#include "common-hal/pulseio/PulseIn.h"
|
||||
#include "common-hal/pulseio/PulseOut.h"
|
||||
#include "common-hal/pulseio/PWMOut.h"
|
||||
#include "common-hal/usb_hid/Device.h"
|
||||
#include "clocks.h"
|
||||
#include "events.h"
|
||||
#include "shared_dma.h"
|
||||
#include "tick.h"
|
||||
|
||||
|
@ -192,15 +197,21 @@ void reset_port(void) {
|
|||
sercom_instances[i]->SPI.CTRLA.bit.SWRST = 1;
|
||||
}
|
||||
|
||||
// #ifdef EXPRESS_BOARD
|
||||
// audioout_reset();
|
||||
#ifdef EXPRESS_BOARD
|
||||
audioout_reset();
|
||||
#if !defined(__SAMD51G19A__) && !defined(__SAMD51G18A__)
|
||||
i2sout_reset();
|
||||
#endif
|
||||
audio_dma_reset();
|
||||
// touchin_reset();
|
||||
// pdmin_reset();
|
||||
// #endif
|
||||
//pdmin_reset();
|
||||
#endif
|
||||
pulsein_reset();
|
||||
pulseout_reset();
|
||||
pwmout_reset();
|
||||
|
||||
reset_gclks();
|
||||
|
||||
analogin_reset();
|
||||
|
||||
#ifdef CIRCUITPY_GAMEPAD_TICKS
|
||||
|
@ -209,6 +220,8 @@ void reset_port(void) {
|
|||
|
||||
analogout_reset();
|
||||
|
||||
reset_event_system();
|
||||
|
||||
reset_all_pins();
|
||||
|
||||
// Set up debugging pins after reset_all_pins().
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
#include "hri/hri_gclk_d51.h"
|
||||
#endif
|
||||
|
||||
const uint16_t prescaler[8] = {1, 2, 4, 8, 16, 64, 256, 1024};
|
||||
|
||||
// This bitmask keeps track of which channels of a TCC are currently claimed.
|
||||
#ifdef SAMD21
|
||||
const uint8_t tcc_cc_num[3] = {4, 2, 2};
|
||||
|
@ -73,8 +75,8 @@ const uint8_t tc_gclk_ids[TC_INST_NUM] = {TC0_GCLK_ID,
|
|||
TC7_GCLK_ID,
|
||||
#endif
|
||||
};
|
||||
const uint8_t tcc_gclk_ids[TCC_INST_NUM] = {TCC0_GCLK_ID,
|
||||
TCC1_GCLK_ID,
|
||||
const uint8_t tcc_gclk_ids[TCC_INST_NUM] = {TCC0_GCLK_ID,
|
||||
TCC1_GCLK_ID,
|
||||
TCC2_GCLK_ID,
|
||||
#ifdef TCC3_GCLK_ID
|
||||
TCC3_GCLK_ID,
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#include "include/sam.h"
|
||||
|
||||
const uint16_t prescaler[8];
|
||||
|
||||
#ifdef SAMD21
|
||||
const uint8_t tcc_cc_num[3];
|
||||
const uint8_t tc_gclk_ids[TC_INST_NUM];
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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 "lib/utils/context_manager_helpers.h"
|
||||
#include "py/binary.h"
|
||||
#include "py/objproperty.h"
|
||||
#include "py/runtime.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audiobusio/I2SOut.h"
|
||||
#include "shared-bindings/util.h"
|
||||
|
||||
//| .. currentmodule:: audiobusio
|
||||
//|
|
||||
//| :class:`I2SOut` -- Output an I2S audio signal
|
||||
//| ========================================================
|
||||
//|
|
||||
//| I2S is used to output an audio signal on an I2S bus.
|
||||
//|
|
||||
//| .. class:: I2SOut(bit_clock, word_select, data, *, left_justified)
|
||||
//|
|
||||
//| Create a I2SOut object associated with the given pins.
|
||||
//|
|
||||
//| :param ~microcontroller.Pin bit_clock: The bit clock (or serial clock) pin
|
||||
//| :param ~microcontroller.Pin word_select: The word select (or left/right clock) pin
|
||||
//| :param ~microcontroller.Pin data: The data pin
|
||||
//| :param bool left_justified: True when data bits are aligned with the word select clock. False
|
||||
//| when they are shifted by one to match classic I2S protocol.
|
||||
//|
|
||||
//| Simple 8ksps 440 Hz sine wave on `Metro M0 Express <https://www.adafruit.com/product/3505>`_
|
||||
//| using `UDA1334 Breakout <https://www.adafruit.com/product/3678>`_::
|
||||
//|
|
||||
//| import audiobusio
|
||||
//| import audioio
|
||||
//| import board
|
||||
//| import array
|
||||
//| import time
|
||||
//| import math
|
||||
//|
|
||||
//| # Generate one period of sine wav.
|
||||
//| length = 8000 // 440
|
||||
//| sine_wave = array.array("H", [0] * length)
|
||||
//| for i in range(length):
|
||||
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
|
||||
//|
|
||||
//| sine_wave = audiobusio.RawSample(sine_wave, sample_rate=8000)
|
||||
//| i2s = audiobusio.I2SOut(board.D1, board.D0, board.D9)
|
||||
//| i2s.play(sine_wave, loop=True)
|
||||
//| time.sleep(1)
|
||||
//| i2s.stop()
|
||||
//|
|
||||
//| Playing a wave file from flash::
|
||||
//|
|
||||
//| import board
|
||||
//| import audioio
|
||||
//| import audiobusio
|
||||
//| import digitalio
|
||||
//|
|
||||
//|
|
||||
//| f = open("cplay-5.1-16bit-16khz.wav", "rb")
|
||||
//| wav = audioio.WaveFile(f)
|
||||
//|
|
||||
//| a = audiobusio.I2SOut(board.D1, board.D0, board.D9)
|
||||
//|
|
||||
//| print("playing")
|
||||
//| a.play(wav)
|
||||
//| while a.playing:
|
||||
//| pass
|
||||
//| print("stopped")
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
|
||||
mp_arg_check_num(n_args, n_kw, 3, 4, true);
|
||||
mp_map_t kw_args;
|
||||
mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
|
||||
enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_left_justified };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_bit_clock, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_word_select, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_left_justified, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_bool = false} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
mp_obj_t bit_clock_obj = args[ARG_bit_clock].u_obj;
|
||||
assert_pin(bit_clock_obj, false);
|
||||
const mcu_pin_obj_t *bit_clock = MP_OBJ_TO_PTR(bit_clock_obj);
|
||||
|
||||
mp_obj_t word_select_obj = args[ARG_word_select].u_obj;
|
||||
assert_pin(word_select_obj, false);
|
||||
const mcu_pin_obj_t *word_select = MP_OBJ_TO_PTR(word_select_obj);
|
||||
|
||||
mp_obj_t data_obj = args[ARG_data].u_obj;
|
||||
assert_pin(data_obj, false);
|
||||
const mcu_pin_obj_t *data = MP_OBJ_TO_PTR(data_obj);
|
||||
|
||||
audiobusio_i2sout_obj_t *self = m_new_obj(audiobusio_i2sout_obj_t);
|
||||
self->base.type = &audiobusio_i2sout_type;
|
||||
common_hal_audiobusio_i2sout_construct(self, bit_clock, word_select, data, args[ARG_left_justified].u_bool);
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
//| .. method:: deinit()
|
||||
//|
|
||||
//| Deinitialises the I2SOut and releases any hardware resources for reuse.
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_deinit(mp_obj_t self_in) {
|
||||
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
common_hal_audiobusio_i2sout_deinit(self);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_deinit_obj, audiobusio_i2sout_deinit);
|
||||
|
||||
//| .. method:: __enter__()
|
||||
//|
|
||||
//| No-op used by Context Managers.
|
||||
//|
|
||||
// Provided by context manager helper.
|
||||
|
||||
//| .. method:: __exit__()
|
||||
//|
|
||||
//| Automatically deinitializes the hardware when exiting a context. See
|
||||
//| :ref:`lifetime-and-contextmanagers` for more info.
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_obj___exit__(size_t n_args, const mp_obj_t *args) {
|
||||
(void)n_args;
|
||||
common_hal_audiobusio_i2sout_deinit(args[0]);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2sout___exit___obj, 4, 4, audiobusio_i2sout_obj___exit__);
|
||||
|
||||
|
||||
//| .. method:: play(sample, *, loop=False)
|
||||
//|
|
||||
//| Plays the sample once when loop=False and continuously when loop=True.
|
||||
//| Does not block. Use `playing` to block.
|
||||
//|
|
||||
//| Sample must be an `audioio.WaveFile` or `audioio.RawSample`.
|
||||
//|
|
||||
//| The sample itself should consist of 8 bit or 16 bit samples.
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_sample, ARG_loop };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
|
||||
};
|
||||
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
|
||||
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
mp_obj_t sample = args[ARG_sample].u_obj;
|
||||
common_hal_audiobusio_i2sout_play(self, sample, args[ARG_loop].u_bool);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(audiobusio_i2sout_play_obj, 1, audiobusio_i2sout_obj_play);
|
||||
|
||||
//| .. method:: stop()
|
||||
//|
|
||||
//| Stops playback.
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_obj_stop(mp_obj_t self_in) {
|
||||
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
|
||||
common_hal_audiobusio_i2sout_stop(self);
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_stop_obj, audiobusio_i2sout_obj_stop);
|
||||
|
||||
//| .. attribute:: playing
|
||||
//|
|
||||
//| True when the audio sample is being output. (read-only)
|
||||
//|
|
||||
STATIC mp_obj_t audiobusio_i2sout_obj_get_playing(mp_obj_t self_in) {
|
||||
audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
|
||||
return mp_obj_new_bool(common_hal_audiobusio_i2sout_get_playing(self));
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_get_playing_obj, audiobusio_i2sout_obj_get_playing);
|
||||
|
||||
const mp_obj_property_t audiobusio_i2sout_playing_obj = {
|
||||
.base.type = &mp_type_property,
|
||||
.proxy = {(mp_obj_t)&audiobusio_i2sout_get_playing_obj,
|
||||
(mp_obj_t)&mp_const_none_obj,
|
||||
(mp_obj_t)&mp_const_none_obj},
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t audiobusio_i2sout_locals_dict_table[] = {
|
||||
// Methods
|
||||
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2sout_deinit_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_i2sout___exit___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiobusio_i2sout_play_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiobusio_i2sout_stop_obj) },
|
||||
|
||||
// Properties
|
||||
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiobusio_i2sout_playing_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(audiobusio_i2sout_locals_dict, audiobusio_i2sout_locals_dict_table);
|
||||
|
||||
const mp_obj_type_t audiobusio_i2sout_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_I2SOut,
|
||||
.make_new = audiobusio_i2sout_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&audiobusio_i2sout_locals_dict,
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of the Micro Python project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017, 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_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
|
||||
|
||||
#include "common-hal/audiobusio/I2SOut.h"
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
|
||||
extern const mp_obj_type_t audiobusio_i2sout_type;
|
||||
|
||||
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);
|
||||
|
||||
void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self);
|
||||
bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self);
|
||||
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, mp_obj_t sample, bool loop);
|
||||
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self);
|
||||
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audiobusio/__init__.h"
|
||||
#include "shared-bindings/audiobusio/I2SOut.h"
|
||||
#include "shared-bindings/audiobusio/PDMIn.h"
|
||||
|
||||
//| :mod:`audiobusio` --- Support for audio input and output over digital bus
|
||||
|
@ -50,6 +51,7 @@
|
|||
//| .. toctree::
|
||||
//| :maxdepth: 3
|
||||
//|
|
||||
//| I2SOut
|
||||
//| PDMIn
|
||||
//|
|
||||
//| All libraries change hardware state and should be deinitialized when they
|
||||
|
@ -59,7 +61,8 @@
|
|||
|
||||
STATIC const mp_rom_map_elem_t audiobusio_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiobusio) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_I2SOut), MP_ROM_PTR(&audiobusio_i2sout_type) },
|
||||
//{ MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_type) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(audiobusio_module_globals, audiobusio_module_globals_table);
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "py/runtime.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audioio/AudioOut.h"
|
||||
#include "shared-bindings/audioio/RawSample.h"
|
||||
#include "shared-bindings/util.h"
|
||||
|
||||
//| .. currentmodule:: audioio
|
||||
|
@ -41,18 +42,13 @@
|
|||
//|
|
||||
//| AudioOut can be used to output an analog audio signal on a given pin.
|
||||
//|
|
||||
//| .. class:: AudioOut(pin, sample_source)
|
||||
//| .. class:: AudioOut(left_channel, right_channel=None)
|
||||
//|
|
||||
//| Create a AudioOut object associated with the given pin. This allows you to
|
||||
//| play audio signals out on the given pin. Sample_source must be a `bytes-like object <https://docs.python.org/3/glossary.html#term-bytes-like-object>`_.
|
||||
//| Create a AudioOut object associated with the given pin(s). This allows you to
|
||||
//| play audio signals out on the given pin(s).
|
||||
//|
|
||||
//| The sample itself should consist of 16 bit samples and be mono.
|
||||
//| Microcontrollers with a lower output resolution will use the highest order
|
||||
//| bits to output. For example, the SAMD21 has a 10 bit DAC that ignores the
|
||||
//| lowest 6 bits when playing 16 bit samples.
|
||||
//|
|
||||
//| :param ~microcontroller.Pin pin: The pin to output to
|
||||
//| :param bytes-like sample_source: The source of the sample
|
||||
//| :param ~microcontroller.Pin left_channel: The pin to output the left channel to
|
||||
//| :param ~microcontroller.Pin right_channel: The pin to output the right channel to
|
||||
//|
|
||||
//| Simple 8ksps 440 Hz sin wave::
|
||||
//|
|
||||
|
@ -68,8 +64,9 @@
|
|||
//| for i in range(length):
|
||||
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
|
||||
//|
|
||||
//| sample = audioio.AudioOut(board.SPEAKER, sine_wave)
|
||||
//| sample.play(loop=True)
|
||||
//| dac = audioio.AudioOut(board.SPEAKER)
|
||||
//| sine_wave = audioio.RawSample(sine_wave, mono=True, sample_rate=8000)
|
||||
//| dac.play(sine_wave, loop=True)
|
||||
//| time.sleep(1)
|
||||
//| sample.stop()
|
||||
//|
|
||||
|
@ -83,41 +80,42 @@
|
|||
//| speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
|
||||
//| speaker_enable.switch_to_output(value=True)
|
||||
//|
|
||||
//| f = open("cplay-5.1-16bit-16khz.wav", "rb")
|
||||
//
|
||||
//| a = audioio.AudioOut(board.A0, f)
|
||||
//| wav = audioio.WaveFile("cplay-5.1-16bit-16khz.wav")
|
||||
//| a = audioio.AudioOut(board.A0)
|
||||
//|
|
||||
//| print("playing")
|
||||
//| a.play()
|
||||
//| a.play(wav)
|
||||
//| while a.playing:
|
||||
//| pass
|
||||
//| print("stopped")
|
||||
//|
|
||||
STATIC mp_obj_t audioio_audioout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
mp_arg_check_num(n_args, n_kw, 2, 2, true);
|
||||
mp_obj_t pin_obj = args[0];
|
||||
assert_pin(pin_obj, false);
|
||||
const mcu_pin_obj_t *pin = MP_OBJ_TO_PTR(pin_obj);
|
||||
// We explicitly don't check whether the pin is free because multiple
|
||||
// AudioOuts may share it.
|
||||
STATIC mp_obj_t audioio_audioout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
|
||||
mp_arg_check_num(n_args, n_kw, 1, 2, true);
|
||||
mp_map_t kw_args;
|
||||
mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
|
||||
enum { ARG_left_channel, ARG_right_channel };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_left_channel, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_right_channel, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = mp_const_none} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
mp_obj_t left_channel_obj = args[ARG_left_channel].u_obj;
|
||||
assert_pin(left_channel_obj, false);
|
||||
const mcu_pin_obj_t *left_channel_pin = MP_OBJ_TO_PTR(left_channel_obj);
|
||||
|
||||
mp_obj_t right_channel_obj = args[ARG_right_channel].u_obj;
|
||||
const mcu_pin_obj_t *right_channel_pin = NULL;
|
||||
if (right_channel_obj != mp_const_none) {
|
||||
assert_pin(right_channel_obj, false);
|
||||
right_channel_pin = MP_OBJ_TO_PTR(right_channel_obj);
|
||||
}
|
||||
|
||||
// create AudioOut object from the given pin
|
||||
audioio_audioout_obj_t *self = m_new_obj(audioio_audioout_obj_t);
|
||||
self->base.type = &audioio_audioout_type;
|
||||
mp_buffer_info_t bufinfo;
|
||||
if (MP_OBJ_IS_TYPE(args[1], &fatfs_type_fileio)) {
|
||||
common_hal_audioio_audioout_construct_from_file(self, pin, MP_OBJ_TO_PTR(args[1]));
|
||||
} else if (mp_get_buffer(args[1], &bufinfo, MP_BUFFER_READ)) {
|
||||
uint8_t bytes_per_sample = 1;
|
||||
if (bufinfo.typecode == 'H') {
|
||||
bytes_per_sample = 2;
|
||||
} else if (bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
|
||||
mp_raise_ValueError("sample_source buffer must be a bytearray or array of type 'H' or 'B'");
|
||||
}
|
||||
common_hal_audioio_audioout_construct_from_buffer(self, pin, ((uint16_t*)bufinfo.buf), bufinfo.len, bytes_per_sample);
|
||||
} else {
|
||||
mp_raise_TypeError("sample_source must be a file or bytes-like object");
|
||||
}
|
||||
common_hal_audioio_audioout_construct(self, left_channel_pin, right_channel_pin);
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
@ -152,30 +150,38 @@ STATIC mp_obj_t audioio_audioout_obj___exit__(size_t n_args, const mp_obj_t *arg
|
|||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_audioout___exit___obj, 4, 4, audioio_audioout_obj___exit__);
|
||||
|
||||
|
||||
//| .. method:: play(loop=False)
|
||||
//| .. method:: play(sample, *, loop=False)
|
||||
//|
|
||||
//| Plays the sample once when loop=False and continuously when loop=True.
|
||||
//| Does not block. Use `playing` to block.
|
||||
//|
|
||||
//| Sample must be an `audioio.WaveFile` or `audioio.RawSample`.
|
||||
//|
|
||||
//| The sample itself should consist of 16 bit samples. Microcontrollers with a lower output
|
||||
//| resolution will use the highest order bits to output. For example, the SAMD21 has a 10 bit
|
||||
//| DAC that ignores the lowest 6 bits when playing 16 bit samples.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_audioout_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_loop };
|
||||
enum { ARG_sample, ARG_loop };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_loop, MP_ARG_BOOL, {.u_bool = false} },
|
||||
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
|
||||
};
|
||||
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
|
||||
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
common_hal_audioio_audioout_play(self, args[ARG_loop].u_bool);
|
||||
mp_obj_t sample = args[ARG_sample].u_obj;
|
||||
common_hal_audioio_audioout_play(self, sample, args[ARG_loop].u_bool);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(audioio_audioout_play_obj, 1, audioio_audioout_obj_play);
|
||||
|
||||
//| .. method:: stop()
|
||||
//|
|
||||
//| Stops playback of this sample. If another sample is playing instead, it
|
||||
//| won't be stopped.
|
||||
//| Stops playback.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_audioout_obj_stop(mp_obj_t self_in) {
|
||||
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
@ -187,7 +193,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_stop_obj, audioio_audioout_obj_stop);
|
|||
|
||||
//| .. attribute:: playing
|
||||
//|
|
||||
//| True when the audio sample is being output. (read-only)
|
||||
//| True when an audio sample is being output. (read-only)
|
||||
//|
|
||||
STATIC mp_obj_t audioio_audioout_obj_get_playing(mp_obj_t self_in) {
|
||||
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
@ -203,34 +209,6 @@ const mp_obj_property_t audioio_audioout_playing_obj = {
|
|||
(mp_obj_t)&mp_const_none_obj},
|
||||
};
|
||||
|
||||
//| .. attribute:: frequency
|
||||
//|
|
||||
//| 32 bit value that dictates how quickly samples are loaded into the DAC
|
||||
//| in Hertz (cycles per second). When the sample is looped, this can change
|
||||
//| the pitch output without changing the underlying sample.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_audioout_obj_get_frequency(mp_obj_t self_in) {
|
||||
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
|
||||
return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_audioout_get_frequency(self));
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_get_frequency_obj, audioio_audioout_obj_get_frequency);
|
||||
|
||||
STATIC mp_obj_t audioio_audioout_obj_set_frequency(mp_obj_t self_in, mp_obj_t frequency) {
|
||||
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
|
||||
common_hal_audioio_audioout_set_frequency(self, mp_obj_get_int(frequency));
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(audioio_audioout_set_frequency_obj, audioio_audioout_obj_set_frequency);
|
||||
|
||||
const mp_obj_property_t audioio_audioout_frequency_obj = {
|
||||
.base.type = &mp_type_property,
|
||||
.proxy = {(mp_obj_t)&audioio_audioout_get_frequency_obj,
|
||||
(mp_obj_t)&audioio_audioout_set_frequency_obj,
|
||||
(mp_obj_t)&mp_const_none_obj},
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
|
||||
// Methods
|
||||
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_audioout_deinit_obj) },
|
||||
|
@ -241,7 +219,6 @@ STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
|
|||
|
||||
// Properties
|
||||
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioio_audioout_playing_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&audioio_audioout_frequency_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(audioio_audioout_locals_dict, audioio_audioout_locals_dict_table);
|
||||
|
||||
|
|
|
@ -29,21 +29,18 @@
|
|||
|
||||
#include "common-hal/audioio/AudioOut.h"
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
#include "shared-bindings/audioio/RawSample.h"
|
||||
|
||||
extern const mp_obj_type_t audioio_audioout_type;
|
||||
|
||||
void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* self,
|
||||
const mcu_pin_obj_t* pin, uint16_t* buffer, uint32_t len, uint8_t bytes_per_sample);
|
||||
void common_hal_audioio_audioout_construct_from_file(audioio_audioout_obj_t* self,
|
||||
const mcu_pin_obj_t* pin, pyb_file_obj_t* file);
|
||||
// left_channel will always be non-NULL but right_channel may be for mono output.
|
||||
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);
|
||||
|
||||
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self);
|
||||
bool common_hal_audioio_audioout_deinited(audioio_audioout_obj_t* self);
|
||||
void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, bool loop);
|
||||
void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, mp_obj_t sample, bool loop);
|
||||
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self);
|
||||
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self);
|
||||
uint32_t common_hal_audioio_audioout_get_frequency(audioio_audioout_obj_t* self);
|
||||
void common_hal_audioio_audioout_set_frequency(audioio_audioout_obj_t* self, uint32_t frequency);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_AUDIOOUT_H
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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 "lib/utils/context_manager_helpers.h"
|
||||
#include "py/binary.h"
|
||||
#include "py/objproperty.h"
|
||||
#include "py/runtime.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audioio/AudioOut.h"
|
||||
#include "shared-bindings/util.h"
|
||||
|
||||
//| .. currentmodule:: audioio
|
||||
//|
|
||||
//| :class:`RawSample` -- A raw audio sample buffer
|
||||
//| ========================================================
|
||||
//|
|
||||
//| An in-memory sound sample
|
||||
//|
|
||||
//| .. class:: RawSample(buffer, *, channel_count=1, sample_rate=8000)
|
||||
//|
|
||||
//| Create a RawSample based on the given buffer of signed values. If channel_count is more than
|
||||
//| 1 then each channel's samples should alternate. In other words, for a two channel buffer, the
|
||||
//| first sample will be for channel 1, the second sample will be for channel two, the third for
|
||||
//| channel 1 and so on.
|
||||
//|
|
||||
//| :param array buffer: An `array.array` with samples
|
||||
//| :param int channel_count: The number of channels in the buffer
|
||||
//| :param int sample_rate: The desired playback sample rate
|
||||
//|
|
||||
//| Simple 8ksps 440 Hz sin wave::
|
||||
//|
|
||||
//| import audioio
|
||||
//| import board
|
||||
//| import array
|
||||
//| import time
|
||||
//| import math
|
||||
//|
|
||||
//| # Generate one period of sine wav.
|
||||
//| length = 8000 // 440
|
||||
//| sine_wave = array.array("h", [0] * length)
|
||||
//| for i in range(length):
|
||||
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15))
|
||||
//|
|
||||
//| dac = audioio.AudioOut(board.SPEAKER)
|
||||
//| sine_wave = audioio.RawSample(sine_wave)
|
||||
//| dac.play(sine_wave, loop=True)
|
||||
//| time.sleep(1)
|
||||
//| sample.stop()
|
||||
//|
|
||||
STATIC mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
|
||||
mp_arg_check_num(n_args, n_kw, 1, 2, true);
|
||||
mp_map_t kw_args;
|
||||
mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
|
||||
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED },
|
||||
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
|
||||
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
audioio_rawsample_obj_t *self = m_new_obj(audioio_rawsample_obj_t);
|
||||
self->base.type = &audioio_rawsample_type;
|
||||
mp_buffer_info_t bufinfo;
|
||||
if (mp_get_buffer(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ)) {
|
||||
uint8_t bytes_per_sample = 1;
|
||||
bool signed_samples = bufinfo.typecode == 'b' || bufinfo.typecode == 'h';
|
||||
if (bufinfo.typecode == 'h' || bufinfo.typecode == 'H') {
|
||||
bytes_per_sample = 2;
|
||||
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
|
||||
mp_raise_ValueError("sample_source buffer must be a bytearray or array of type 'h', 'H', 'b' or 'B'");
|
||||
}
|
||||
common_hal_audioio_rawsample_construct(self, ((uint8_t*)bufinfo.buf), bufinfo.len,
|
||||
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
|
||||
args[ARG_sample_rate].u_int);
|
||||
} else {
|
||||
mp_raise_TypeError("buffer must be a bytes-like object");
|
||||
}
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
//| .. method:: deinit()
|
||||
//|
|
||||
//| Deinitialises the AudioOut and releases any hardware resources for reuse.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_rawsample_deinit(mp_obj_t self_in) {
|
||||
audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
common_hal_audioio_rawsample_deinit(self);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(audioio_rawsample_deinit_obj, audioio_rawsample_deinit);
|
||||
|
||||
//| .. method:: __enter__()
|
||||
//|
|
||||
//| No-op used by Context Managers.
|
||||
//|
|
||||
// Provided by context manager helper.
|
||||
|
||||
//| .. method:: __exit__()
|
||||
//|
|
||||
//| Automatically deinitializes the hardware when exiting a context. See
|
||||
//| :ref:`lifetime-and-contextmanagers` for more info.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_rawsample_obj___exit__(size_t n_args, const mp_obj_t *args) {
|
||||
(void)n_args;
|
||||
common_hal_audioio_rawsample_deinit(args[0]);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_rawsample___exit___obj, 4, 4, audioio_rawsample_obj___exit__);
|
||||
|
||||
//| .. attribute:: sample_rate
|
||||
//|
|
||||
//| 32 bit value that dictates how quickly samples are played in Hertz (cycles per second).
|
||||
//| When the sample is looped, this can change the pitch output without changing the underlying
|
||||
//| sample. This will not change the sample rate of any active playback. Call ``play`` again to
|
||||
//| change it.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_rawsample_obj_get_sample_rate(mp_obj_t self_in) {
|
||||
audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_rawsample_deinited(self));
|
||||
return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_rawsample_get_sample_rate(self));
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(audioio_rawsample_get_sample_rate_obj, audioio_rawsample_obj_get_sample_rate);
|
||||
|
||||
STATIC mp_obj_t audioio_rawsample_obj_set_sample_rate(mp_obj_t self_in, mp_obj_t sample_rate) {
|
||||
audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_rawsample_deinited(self));
|
||||
common_hal_audioio_rawsample_set_sample_rate(self, mp_obj_get_int(sample_rate));
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(audioio_rawsample_set_sample_rate_obj, audioio_rawsample_obj_set_sample_rate);
|
||||
|
||||
const mp_obj_property_t audioio_rawsample_sample_rate_obj = {
|
||||
.base.type = &mp_type_property,
|
||||
.proxy = {(mp_obj_t)&audioio_rawsample_get_sample_rate_obj,
|
||||
(mp_obj_t)&audioio_rawsample_set_sample_rate_obj,
|
||||
(mp_obj_t)&mp_const_none_obj},
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t audioio_rawsample_locals_dict_table[] = {
|
||||
// Methods
|
||||
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_rawsample_deinit_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_rawsample___exit___obj) },
|
||||
|
||||
// Properties
|
||||
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audioio_rawsample_sample_rate_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(audioio_rawsample_locals_dict, audioio_rawsample_locals_dict_table);
|
||||
|
||||
const mp_obj_type_t audioio_rawsample_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_RawSample,
|
||||
.make_new = audioio_rawsample_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&audioio_rawsample_locals_dict,
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
|
||||
|
||||
#include "common-hal/audioio/AudioOut.h"
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
#include "shared-module/audioio/RawSample.h"
|
||||
|
||||
extern const mp_obj_type_t audioio_rawsample_type;
|
||||
|
||||
void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t* self,
|
||||
uint8_t* buffer, uint32_t len, uint8_t bytes_per_sample, bool samples_signed,
|
||||
uint8_t channel_count, uint32_t sample_rate);
|
||||
|
||||
void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t* self);
|
||||
bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t* self);
|
||||
uint32_t common_hal_audioio_rawsample_get_sample_rate(audioio_rawsample_obj_t* self);
|
||||
void common_hal_audioio_rawsample_set_sample_rate(audioio_rawsample_obj_t* self, uint32_t sample_rate);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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 <stdint.h>
|
||||
|
||||
#include "lib/utils/context_manager_helpers.h"
|
||||
// #include "py/binary.h"
|
||||
#include "py/objproperty.h"
|
||||
#include "py/runtime.h"
|
||||
// #include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audioio/WaveFile.h"
|
||||
#include "shared-bindings/util.h"
|
||||
|
||||
//| .. currentmodule:: audioio
|
||||
//|
|
||||
//| :class:`WaveFile` -- Load a wave file for audio playback
|
||||
//| ========================================================
|
||||
//|
|
||||
//| A .wav file prepped for audio playback. Only mono and stereo files are supported. Samples must
|
||||
//| be 8 bit unsigned or 16 bit signed.
|
||||
//|
|
||||
//| .. class:: WaveFile(filename)
|
||||
//|
|
||||
//| Load a .wav file for playback with `audioio.AudioOut` or `audiobusio.I2SOut`.
|
||||
//|
|
||||
//| :param bytes-like file: Already opened wave file
|
||||
//|
|
||||
//| Playing a wave file from flash::
|
||||
//|
|
||||
//| import board
|
||||
//| import audioio
|
||||
//| import digitalio
|
||||
//|
|
||||
//| # Required for CircuitPlayground Express
|
||||
//| speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
|
||||
//| speaker_enable.switch_to_output(value=True)
|
||||
//|
|
||||
//| data = open("cplay-5.1-16bit-16khz.wav", "rb")
|
||||
//| wav = audioio.WaveFile(data)
|
||||
//| a = audioio.AudioOut(board.A0)
|
||||
//|
|
||||
//| print("playing")
|
||||
//| a.play(wav)
|
||||
//| while a.playing:
|
||||
//| pass
|
||||
//| print("stopped")
|
||||
//|
|
||||
STATIC mp_obj_t audioio_wavefile_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
mp_arg_check_num(n_args, n_kw, 1, 1, true);
|
||||
|
||||
audioio_wavefile_obj_t *self = m_new_obj(audioio_wavefile_obj_t);
|
||||
self->base.type = &audioio_wavefile_type;
|
||||
if (MP_OBJ_IS_TYPE(args[0], &fatfs_type_fileio)) {
|
||||
common_hal_audioio_wavefile_construct(self, MP_OBJ_TO_PTR(args[0]));
|
||||
} else {
|
||||
mp_raise_TypeError("file must be a file opened in byte mode");
|
||||
}
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
//| .. method:: deinit()
|
||||
//|
|
||||
//| Deinitialises the WaveFile and releases all memory resources for reuse.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_wavefile_deinit(mp_obj_t self_in) {
|
||||
audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
common_hal_audioio_wavefile_deinit(self);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(audioio_wavefile_deinit_obj, audioio_wavefile_deinit);
|
||||
|
||||
//| .. method:: __enter__()
|
||||
//|
|
||||
//| No-op used by Context Managers.
|
||||
//|
|
||||
// Provided by context manager helper.
|
||||
|
||||
//| .. method:: __exit__()
|
||||
//|
|
||||
//| Automatically deinitializes the hardware when exiting a context. See
|
||||
//| :ref:`lifetime-and-contextmanagers` for more info.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_wavefile_obj___exit__(size_t n_args, const mp_obj_t *args) {
|
||||
(void)n_args;
|
||||
common_hal_audioio_wavefile_deinit(args[0]);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_wavefile___exit___obj, 4, 4, audioio_wavefile_obj___exit__);
|
||||
|
||||
//| .. attribute:: sample_rate
|
||||
//|
|
||||
//| 32 bit value that dictates how quickly samples are loaded into the DAC
|
||||
//| in Hertz (cycles per second). When the sample is looped, this can change
|
||||
//| the pitch output without changing the underlying sample.
|
||||
//|
|
||||
STATIC mp_obj_t audioio_wavefile_obj_get_sample_rate(mp_obj_t self_in) {
|
||||
audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_wavefile_deinited(self));
|
||||
return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_wavefile_get_sample_rate(self));
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(audioio_wavefile_get_sample_rate_obj, audioio_wavefile_obj_get_sample_rate);
|
||||
|
||||
STATIC mp_obj_t audioio_wavefile_obj_set_sample_rate(mp_obj_t self_in, mp_obj_t sample_rate) {
|
||||
audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
raise_error_if_deinited(common_hal_audioio_wavefile_deinited(self));
|
||||
common_hal_audioio_wavefile_set_sample_rate(self, mp_obj_get_int(sample_rate));
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(audioio_wavefile_set_sample_rate_obj, audioio_wavefile_obj_set_sample_rate);
|
||||
|
||||
const mp_obj_property_t audioio_wavefile_sample_rate_obj = {
|
||||
.base.type = &mp_type_property,
|
||||
.proxy = {(mp_obj_t)&audioio_wavefile_get_sample_rate_obj,
|
||||
(mp_obj_t)&audioio_wavefile_set_sample_rate_obj,
|
||||
(mp_obj_t)&mp_const_none_obj},
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t audioio_wavefile_locals_dict_table[] = {
|
||||
// Methods
|
||||
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_wavefile_deinit_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_wavefile___exit___obj) },
|
||||
|
||||
// Properties
|
||||
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audioio_wavefile_sample_rate_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(audioio_wavefile_locals_dict, audioio_wavefile_locals_dict_table);
|
||||
|
||||
const mp_obj_type_t audioio_wavefile_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_WaveFile,
|
||||
.make_new = audioio_wavefile_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&audioio_wavefile_locals_dict,
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
|
||||
#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
|
||||
|
||||
#include "common-hal/audioio/AudioOut.h"
|
||||
#include "common-hal/microcontroller/Pin.h"
|
||||
#include "extmod/vfs_fat_file.h"
|
||||
|
||||
extern const mp_obj_type_t audioio_wavefile_type;
|
||||
|
||||
void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self,
|
||||
pyb_file_obj_t* file);
|
||||
|
||||
void common_hal_audioio_wavefile_deinit(audioio_wavefile_obj_t* self);
|
||||
bool common_hal_audioio_wavefile_deinited(audioio_wavefile_obj_t* self);
|
||||
uint32_t common_hal_audioio_wavefile_get_sample_rate(audioio_wavefile_obj_t* self);
|
||||
void common_hal_audioio_wavefile_set_sample_rate(audioio_wavefile_obj_t* self, uint32_t sample_rate);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
|
|
@ -32,6 +32,7 @@
|
|||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "shared-bindings/audioio/__init__.h"
|
||||
#include "shared-bindings/audioio/AudioOut.h"
|
||||
#include "shared-bindings/audioio/WaveFile.h"
|
||||
|
||||
//| :mod:`audioio` --- Support for audio input and output
|
||||
//| ======================================================
|
||||
|
@ -48,6 +49,8 @@
|
|||
//| :maxdepth: 3
|
||||
//|
|
||||
//| AudioOut
|
||||
//| RawSample
|
||||
//| WaveFile
|
||||
//|
|
||||
//| All classes change hardware state and should be deinitialized when they
|
||||
//| are no longer needed if the program continues after use. To do so, either
|
||||
|
@ -58,6 +61,8 @@
|
|||
STATIC const mp_rom_map_elem_t audioio_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audioio) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_AudioOut), MP_ROM_PTR(&audioio_audioout_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_RawSample), MP_ROM_PTR(&audioio_rawsample_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_WaveFile), MP_ROM_PTR(&audioio_wavefile_type) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(audioio_module_globals, audioio_module_globals_table);
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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 "shared-bindings/audioio/RawSample.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "shared-module/audioio/RawSample.h"
|
||||
|
||||
void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t* self,
|
||||
uint8_t* buffer,
|
||||
uint32_t len,
|
||||
uint8_t bytes_per_sample,
|
||||
bool samples_signed,
|
||||
uint8_t channel_count,
|
||||
uint32_t sample_rate) {
|
||||
self->buffer = buffer;
|
||||
self->bits_per_sample = bytes_per_sample * 8;
|
||||
self->samples_signed = samples_signed;
|
||||
self->len = len;
|
||||
self->channel_count = channel_count;
|
||||
self->sample_rate = sample_rate;
|
||||
self->buffer_read = false;
|
||||
}
|
||||
|
||||
void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t* self) {
|
||||
self->buffer = NULL;
|
||||
}
|
||||
bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t* self) {
|
||||
return self->buffer == NULL;
|
||||
}
|
||||
|
||||
uint32_t common_hal_audioio_rawsample_get_sample_rate(audioio_rawsample_obj_t* self) {
|
||||
return self->sample_rate;
|
||||
}
|
||||
void common_hal_audioio_rawsample_set_sample_rate(audioio_rawsample_obj_t* self,
|
||||
uint32_t sample_rate) {
|
||||
self->sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel) {
|
||||
}
|
||||
|
||||
bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel,
|
||||
uint8_t** buffer,
|
||||
uint32_t* buffer_length) {
|
||||
*buffer_length = self->len;
|
||||
if (single_channel) {
|
||||
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
|
||||
} else {
|
||||
*buffer = self->buffer;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel,
|
||||
bool* single_buffer, bool* samples_signed,
|
||||
uint32_t* max_buffer_length, uint8_t* spacing) {
|
||||
*single_buffer = true;
|
||||
*samples_signed = self->samples_signed;
|
||||
*max_buffer_length = self->len;
|
||||
if (single_channel) {
|
||||
*spacing = self->channel_count;
|
||||
} else {
|
||||
*spacing = 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft
|
||||
*
|
||||
* 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_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
|
||||
#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
|
||||
|
||||
#include "py/obj.h"
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
uint8_t* buffer;
|
||||
uint32_t len;
|
||||
uint8_t bits_per_sample;
|
||||
bool samples_signed;
|
||||
uint8_t channel_count;
|
||||
uint32_t sample_rate;
|
||||
bool buffer_read;
|
||||
} audioio_rawsample_obj_t;
|
||||
|
||||
|
||||
// These are not available from Python because it may be called in an interrupt.
|
||||
void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel);
|
||||
bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel,
|
||||
uint8_t** buffer,
|
||||
uint32_t* buffer_length); // length in bytes
|
||||
void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel,
|
||||
bool* single_buffer, bool* samples_signed,
|
||||
uint32_t* max_buffer_length, uint8_t* spacing);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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 "shared-bindings/audioio/WaveFile.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "py/runtime.h"
|
||||
|
||||
#include "shared-module/audioio/WaveFile.h"
|
||||
|
||||
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.
|
||||
};
|
||||
|
||||
void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self,
|
||||
pyb_file_obj_t* file) {
|
||||
// Load the wave
|
||||
self->file = file;
|
||||
uint8_t chunk_header[16];
|
||||
f_rewind(&self->file->fp);
|
||||
UINT bytes_read;
|
||||
f_read(&self->file->fp, chunk_header, 16, &bytes_read);
|
||||
if (bytes_read != 16 ||
|
||||
memcmp(chunk_header, "RIFF", 4) != 0 ||
|
||||
memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) {
|
||||
mp_raise_ValueError("Invalid wave file");
|
||||
}
|
||||
uint32_t format_size;
|
||||
f_read(&self->file->fp, &format_size, 4, &bytes_read);
|
||||
if (bytes_read != 4 ||
|
||||
format_size > sizeof(struct wave_format_chunk)) {
|
||||
mp_raise_ValueError("Invalid format chunk size");
|
||||
}
|
||||
struct wave_format_chunk format;
|
||||
f_read(&self->file->fp, &format, format_size, &bytes_read);
|
||||
if (bytes_read != format_size) {
|
||||
}
|
||||
|
||||
if (format.audio_format != 1 ||
|
||||
format.num_channels > 2 ||
|
||||
format.bits_per_sample > 16 ||
|
||||
(format_size == 18 &&
|
||||
format.extra_params != 0)) {
|
||||
mp_raise_ValueError("Unsupported format");
|
||||
}
|
||||
// Get the sample_rate
|
||||
self->sample_rate = format.sample_rate;
|
||||
self->len = 512;
|
||||
self->channel_count = format.num_channels;
|
||||
self->bits_per_sample = format.bits_per_sample;
|
||||
|
||||
// TODO(tannewt): Skip any extra chunks that occur before the data section.
|
||||
|
||||
uint8_t data_tag[4];
|
||||
f_read(&self->file->fp, &data_tag, 4, &bytes_read);
|
||||
if (bytes_read != 4 ||
|
||||
memcmp((uint8_t *) data_tag, "data", 4) != 0) {
|
||||
mp_raise_ValueError("Data chunk must follow fmt chunk");
|
||||
}
|
||||
|
||||
uint32_t data_length;
|
||||
f_read(&self->file->fp, &data_length, 4, &bytes_read);
|
||||
if (bytes_read != 4) {
|
||||
mp_raise_ValueError("Invalid file");
|
||||
}
|
||||
self->file_length = data_length;
|
||||
self->data_start = self->file->fp.fptr;
|
||||
|
||||
// Try to allocate two buffers, one will be loaded from file and the other
|
||||
// DMAed to DAC.
|
||||
self->buffer = m_malloc(self->len, false);
|
||||
if (self->buffer == NULL) {
|
||||
common_hal_audioio_wavefile_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
|
||||
self->second_buffer = m_malloc(self->len, false);
|
||||
if (self->second_buffer == NULL) {
|
||||
common_hal_audioio_wavefile_deinit(self);
|
||||
mp_raise_msg(&mp_type_MemoryError, "");
|
||||
}
|
||||
}
|
||||
|
||||
void common_hal_audioio_wavefile_deinit(audioio_wavefile_obj_t* self) {
|
||||
self->buffer = NULL;
|
||||
}
|
||||
|
||||
bool common_hal_audioio_wavefile_deinited(audioio_wavefile_obj_t* self) {
|
||||
return self->buffer == NULL;
|
||||
}
|
||||
|
||||
uint32_t common_hal_audioio_wavefile_get_sample_rate(audioio_wavefile_obj_t* self) {
|
||||
return self->sample_rate;
|
||||
}
|
||||
|
||||
void common_hal_audioio_wavefile_set_sample_rate(audioio_wavefile_obj_t* self,
|
||||
uint32_t sample_rate) {
|
||||
self->sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
bool audioio_wavefile_samples_signed(audioio_wavefile_obj_t* self) {
|
||||
return self->bits_per_sample > 8;
|
||||
}
|
||||
|
||||
uint32_t audioio_wavefile_max_buffer_length(audioio_wavefile_obj_t* self) {
|
||||
return 512;
|
||||
}
|
||||
|
||||
void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel) {
|
||||
if (single_channel && channel == 1) {
|
||||
return;
|
||||
}
|
||||
// We don't reset the buffer index in case we're looping and we have an odd number of buffer
|
||||
// loads
|
||||
self->bytes_remaining = self->file_length;
|
||||
f_lseek(&self->file->fp, self->data_start);
|
||||
self->read_count = 0;
|
||||
self->left_read_count = 0;
|
||||
self->right_read_count = 0;
|
||||
}
|
||||
|
||||
bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel,
|
||||
uint8_t** buffer,
|
||||
uint32_t* buffer_length) {
|
||||
if (!single_channel) {
|
||||
channel = 0;
|
||||
}
|
||||
|
||||
uint32_t channel_read_count = self->left_read_count;
|
||||
if (channel == 1) {
|
||||
channel_read_count = self->right_read_count;
|
||||
}
|
||||
|
||||
bool need_more_data = self->read_count == channel_read_count;
|
||||
|
||||
if (self->bytes_remaining == 0 && need_more_data) {
|
||||
*buffer = NULL;
|
||||
*buffer_length = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (need_more_data) {
|
||||
uint16_t num_bytes_to_load = self->len;
|
||||
if (num_bytes_to_load > self->bytes_remaining) {
|
||||
num_bytes_to_load = self->bytes_remaining;
|
||||
}
|
||||
UINT length_read;
|
||||
if (self->buffer_index % 2 == 1) {
|
||||
*buffer = self->second_buffer;
|
||||
} else {
|
||||
*buffer = self->buffer;
|
||||
}
|
||||
f_read(&self->file->fp, *buffer, num_bytes_to_load, &length_read);
|
||||
*buffer_length = length_read;
|
||||
if (self->buffer_index % 2 == 1) {
|
||||
self->second_buffer_length = length_read;
|
||||
} else {
|
||||
self->buffer_length = length_read;
|
||||
}
|
||||
self->bytes_remaining -= length_read;
|
||||
self->buffer_index += 1;
|
||||
self->read_count += 1;
|
||||
}
|
||||
|
||||
uint32_t buffers_back = self->read_count - 1 - channel_read_count;
|
||||
if ((self->buffer_index - buffers_back) % 2 == 0) {
|
||||
*buffer = self->second_buffer;
|
||||
*buffer_length = self->second_buffer_length;
|
||||
} else {
|
||||
*buffer = self->buffer;
|
||||
*buffer_length = self->buffer_length;
|
||||
}
|
||||
|
||||
if (channel == 0) {
|
||||
self->left_read_count += 1;
|
||||
} else if (channel == 1) {
|
||||
self->right_read_count += 1;
|
||||
*buffer = *buffer + self->bits_per_sample / 8;
|
||||
}
|
||||
|
||||
return self->bytes_remaining == 0;
|
||||
}
|
||||
|
||||
void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel,
|
||||
bool* single_buffer, bool* samples_signed,
|
||||
uint32_t* max_buffer_length, uint8_t* spacing) {
|
||||
*single_buffer = false;
|
||||
*samples_signed = self->bits_per_sample > 8;
|
||||
*max_buffer_length = 512;
|
||||
if (single_channel) {
|
||||
*spacing = self->channel_count;
|
||||
} else {
|
||||
*spacing = 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Scott Shawcroft
|
||||
*
|
||||
* 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_SHARED_MODULE_AUDIOIO_WAVEFILE_H
|
||||
#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_WAVEFILE_H
|
||||
|
||||
#include "py/obj.h"
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
uint8_t* buffer;
|
||||
uint32_t buffer_length;
|
||||
uint8_t* second_buffer;
|
||||
uint32_t second_buffer_length;
|
||||
uint32_t file_length; // In bytes
|
||||
uint16_t data_start; // Where the data values start
|
||||
uint8_t bits_per_sample;
|
||||
uint16_t buffer_index;
|
||||
uint32_t bytes_remaining;
|
||||
|
||||
uint8_t channel_count;
|
||||
uint16_t sample_rate;
|
||||
|
||||
uint32_t len;
|
||||
pyb_file_obj_t* file;
|
||||
|
||||
uint32_t read_count;
|
||||
uint32_t left_read_count;
|
||||
uint32_t right_read_count;
|
||||
} audioio_wavefile_obj_t;
|
||||
|
||||
// These are not available from Python because it may be called in an interrupt.
|
||||
void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel);
|
||||
bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self,
|
||||
bool single_channel,
|
||||
uint8_t channel,
|
||||
uint8_t** buffer,
|
||||
uint32_t* buffer_length); // length in bytes
|
||||
void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel,
|
||||
bool* single_buffer, bool* samples_signed,
|
||||
uint32_t* max_buffer_length, uint8_t* spacing);
|
||||
|
||||
#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_WAVEFILE_H
|
Loading…
Reference in New Issue