2017-04-21 17:43:03 -04:00
|
|
|
/*
|
|
|
|
* 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>
|
2017-05-10 21:21:02 -04:00
|
|
|
#include <string.h>
|
2017-04-21 17:43:03 -04:00
|
|
|
|
2017-06-20 13:56:05 -04:00
|
|
|
#include "extmod/vfs_fat_file.h"
|
2017-04-21 17:43:03 -04:00
|
|
|
#include "py/gc.h"
|
|
|
|
#include "py/mperrno.h"
|
|
|
|
#include "py/runtime.h"
|
|
|
|
#include "common-hal/audioio/AudioOut.h"
|
|
|
|
#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"
|
2017-05-10 21:21:02 -04:00
|
|
|
#include "asf/sam0/drivers/port/port.h"
|
2017-04-21 17:43:03 -04:00
|
|
|
#include "asf/sam0/drivers/tc/tc.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
|
2017-05-10 21:21:02 -04:00
|
|
|
// the code is single threaded. The audioout_sample_timer, audioout_dac_instance,
|
|
|
|
// audioout_sample_event, and audioout_dac_event pointers live in
|
2017-05-04 14:57:51 -04:00
|
|
|
// MICROPY_PORT_ROOT_POINTERS so they don't get garbage collected.
|
2017-04-21 17:43:03 -04:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2017-05-10 21:21:02 -04:00
|
|
|
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.
|
|
|
|
};
|
|
|
|
|
2017-04-21 17:43:03 -04:00
|
|
|
void audioout_reset(void) {
|
2017-05-04 14:57:51 -04:00
|
|
|
// Only reset DMA. PWMOut will reset the timer. Other code will reset the DAC.
|
2017-04-21 17:43:03 -04:00
|
|
|
refcount = 0;
|
2017-05-10 21:21:02 -04:00
|
|
|
MP_STATE_VM(audioout_sample_timer) = NULL;
|
|
|
|
MP_STATE_VM(audioout_block_counter) = NULL;
|
2017-05-04 14:57:51 -04:00
|
|
|
MP_STATE_VM(audioout_dac_instance) = NULL;
|
2017-04-21 17:43:03 -04:00
|
|
|
|
2017-05-10 21:21:02 -04:00
|
|
|
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(audioout_block_event) != NULL) {
|
|
|
|
events_release(MP_STATE_VM(audioout_block_event));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
MP_STATE_VM(audioout_block_event) = NULL;
|
2017-04-21 17:43:03 -04:00
|
|
|
|
2017-05-04 14:57:51 -04:00
|
|
|
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));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-04 14:57:51 -04:00
|
|
|
MP_STATE_VM(audioout_dac_event) = NULL;
|
2017-04-21 17:43:03 -04:00
|
|
|
|
|
|
|
dma_abort_job(&audio_dma);
|
|
|
|
}
|
|
|
|
|
2017-05-10 21:21:02 -04:00
|
|
|
// WARN(tannewt): DO NOT print from here. It calls background tasks and causes a
|
|
|
|
// stack overflow.
|
|
|
|
void audioout_background(void) {
|
|
|
|
if (MP_STATE_VM(audioout_block_counter) != NULL &&
|
|
|
|
active_audioout != NULL &&
|
|
|
|
active_audioout->last_loaded_block < tc_get_count_value(MP_STATE_VM(audioout_block_counter))) {
|
|
|
|
uint8_t* buffer;
|
|
|
|
if (tc_get_count_value(MP_STATE_VM(audioout_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;
|
|
|
|
|
|
|
|
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->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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void allocate_block_counter(audioio_audioout_obj_t* self) {
|
|
|
|
// 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) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_RuntimeError("All timers in use");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
MP_STATE_VM(audioout_block_counter) = gc_alloc(sizeof(struct tc_module), false);
|
|
|
|
if (MP_STATE_VM(audioout_block_counter) == NULL) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_msg(&mp_type_MemoryError, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(audioout_block_counter), 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 = false;
|
|
|
|
events_tc.on_event_perform_action = true;
|
|
|
|
events_tc.event_action = TC_EVENT_ACTION_INCREMENT_COUNTER;
|
|
|
|
tc_enable_events(MP_STATE_VM(audioout_block_counter), &events_tc);
|
|
|
|
|
|
|
|
// Connect the timer overflow event, which happens at the target frequency,
|
|
|
|
// to the DAC conversion trigger.
|
|
|
|
MP_STATE_VM(audioout_block_event) = gc_alloc(sizeof(struct events_resource), false);
|
|
|
|
if (MP_STATE_VM(audioout_block_event) == NULL) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_msg(&mp_type_MemoryError, "");
|
|
|
|
}
|
|
|
|
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(audioout_block_event), &config) != STATUS_OK ||
|
|
|
|
events_attach_user(MP_STATE_VM(audioout_block_event), user) != STATUS_OK) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_OSError(MP_EIO);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tc_enable(MP_STATE_VM(audioout_block_counter));
|
|
|
|
tc_stop_counter(MP_STATE_VM(audioout_block_counter));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t* pin) {
|
2017-04-21 17:43:03 -04:00
|
|
|
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.
|
2017-05-04 14:57:51 -04:00
|
|
|
MP_STATE_VM(audioout_dac_instance) = gc_alloc(sizeof(struct dac_module), false);
|
|
|
|
if (MP_STATE_VM(audioout_dac_instance) == NULL) {
|
2017-04-21 17:43:03 -04:00
|
|
|
mp_raise_msg(&mp_type_MemoryError, "");
|
|
|
|
}
|
|
|
|
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;
|
2017-05-04 14:57:51 -04:00
|
|
|
enum status_code status = dac_init(MP_STATE_VM(audioout_dac_instance), DAC, &config_dac);
|
2017-04-21 17:43:03 -04:00
|
|
|
if (status != STATUS_OK) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_OSError(MP_EIO);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dac_chan_config channel_config;
|
|
|
|
dac_chan_get_config_defaults(&channel_config);
|
2017-05-04 14:57:51 -04:00
|
|
|
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);
|
2017-04-21 17:43:03 -04:00
|
|
|
|
|
|
|
struct dac_events events_dac = { .generate_event_on_buffer_empty = true,
|
|
|
|
.on_event_start_conversion = true };
|
2017-05-04 14:57:51 -04:00
|
|
|
dac_enable_events(MP_STATE_VM(audioout_dac_instance), &events_dac);
|
2017-04-21 17:43:03 -04:00
|
|
|
|
|
|
|
// Figure out which timer we are using.
|
|
|
|
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) {
|
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_RuntimeError("All timers in use");
|
|
|
|
return;
|
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
MP_STATE_VM(audioout_sample_timer) = gc_alloc(sizeof(struct tc_module), false);
|
|
|
|
if (MP_STATE_VM(audioout_sample_timer) == NULL) {
|
2017-04-21 17:43:03 -04:00
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_msg(&mp_type_MemoryError, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2017-05-10 21:21:02 -04:00
|
|
|
if (tc_init(MP_STATE_VM(audioout_sample_timer), t, &config_tc) != STATUS_OK) {
|
2017-04-21 17:43:03 -04:00
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_OSError(MP_EIO);
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct tc_events events_tc;
|
|
|
|
events_tc.generate_event_on_overflow = true;
|
2017-05-04 14:57:51 -04:00
|
|
|
events_tc.on_event_perform_action = false;
|
|
|
|
events_tc.event_action = TC_EVENT_ACTION_OFF;
|
2017-05-10 21:21:02 -04:00
|
|
|
tc_enable_events(MP_STATE_VM(audioout_sample_timer), &events_tc);
|
2017-04-21 17:43:03 -04:00
|
|
|
|
2017-05-10 21:21:02 -04:00
|
|
|
tc_enable(MP_STATE_VM(audioout_sample_timer));
|
|
|
|
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
2017-04-21 17:43:03 -04:00
|
|
|
|
|
|
|
// Connect the timer overflow event, which happens at the target frequency,
|
|
|
|
// to the DAC conversion trigger.
|
2017-05-10 21:21:02 -04:00
|
|
|
MP_STATE_VM(audioout_sample_event) = gc_alloc(sizeof(struct events_resource), false);
|
|
|
|
if (MP_STATE_VM(audioout_sample_event) == NULL) {
|
2017-04-21 17:43:03 -04:00
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_msg(&mp_type_MemoryError, "");
|
|
|
|
}
|
|
|
|
struct events_config config;
|
|
|
|
events_get_config_defaults(&config);
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
config.generator = generator;
|
|
|
|
config.path = EVENTS_PATH_ASYNCHRONOUS;
|
2017-05-10 21:21:02 -04:00
|
|
|
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) {
|
2017-04-21 17:43:03 -04:00
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_OSError(MP_EIO);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect the DAC to DMA
|
2017-05-04 14:57:51 -04:00
|
|
|
MP_STATE_VM(audioout_dac_event) = gc_alloc(sizeof(struct events_resource), false);
|
|
|
|
if (MP_STATE_VM(audioout_dac_event) == NULL) {
|
2017-04-21 17:43:03 -04:00
|
|
|
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;
|
2017-05-04 14:57:51 -04:00
|
|
|
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) {
|
2017-04-21 17:43:03 -04:00
|
|
|
common_hal_audioio_audioout_deinit(self);
|
|
|
|
mp_raise_OSError(MP_EIO);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
self->pin = pin;
|
|
|
|
if (pin != &pin_PA02) {
|
|
|
|
mp_raise_ValueError("Invalid pin");
|
|
|
|
}
|
|
|
|
if (refcount == 0) {
|
|
|
|
refcount++;
|
2017-05-10 21:21:02 -04:00
|
|
|
shared_construct(self, pin);
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
self->buffer = (uint8_t*) buffer;
|
2017-05-10 21:21:02 -04:00
|
|
|
self->second_buffer = NULL;
|
2017-04-21 17:43:03 -04:00
|
|
|
// Input len is a count. Internal len is in bytes.
|
|
|
|
self->len = 2 * 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) {
|
2017-05-10 21:21:02 -04:00
|
|
|
self->pin = pin;
|
|
|
|
if (pin != &pin_PA02) {
|
|
|
|
mp_raise_ValueError("Invalid pin");
|
|
|
|
}
|
|
|
|
if (refcount == 0) {
|
|
|
|
refcount++;
|
|
|
|
shared_construct(self, pin);
|
|
|
|
}
|
|
|
|
if (MP_STATE_VM(audioout_block_counter) == NULL) {
|
|
|
|
allocate_block_counter(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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, "");
|
|
|
|
}
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self) {
|
|
|
|
refcount--;
|
|
|
|
if (refcount == 0) {
|
2017-05-10 21:21:02 -04:00
|
|
|
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;
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-04 14:57:51 -04:00
|
|
|
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;
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
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;
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-04 14:57:51 -04:00
|
|
|
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;
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
reset_pin(self->pin->pin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_timer_frequency(uint32_t frequency) {
|
|
|
|
uint32_t system_clock = system_cpu_clock_get_hz();
|
|
|
|
uint32_t new_top;
|
|
|
|
uint8_t new_divisor;
|
|
|
|
for (new_divisor = 0; new_divisor < 8; new_divisor++) {
|
|
|
|
new_top = (system_clock / prescaler[new_divisor] / frequency) - 1;
|
|
|
|
if (new_top < (1u << 16)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
uint8_t old_divisor = MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER;
|
2017-04-21 17:43:03 -04:00
|
|
|
if (new_divisor != old_divisor) {
|
2017-05-10 21:21:02 -04:00
|
|
|
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));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
|
2017-04-21 17:43:03 -04:00
|
|
|
/* Wait for sync */
|
|
|
|
}
|
2017-05-10 21:21:02 -04:00
|
|
|
MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CC[0].reg = new_top;
|
|
|
|
while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
|
2017-04-21 17:43:03 -04:00
|
|
|
/* Wait for sync */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2017-05-10 21:21:02 -04:00
|
|
|
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
2017-04-21 17:43:03 -04:00
|
|
|
dma_abort_job(&audio_dma);
|
2017-05-04 14:57:51 -04:00
|
|
|
} else {
|
|
|
|
dac_enable(MP_STATE_VM(audioout_dac_instance));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
struct dma_descriptor_config descriptor_config;
|
|
|
|
dma_descriptor_get_config_defaults(&descriptor_config);
|
2017-05-10 21:21:02 -04:00
|
|
|
if (self->bytes_per_sample == 2) {
|
|
|
|
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
|
|
|
|
} else {
|
|
|
|
descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
|
|
|
|
}
|
|
|
|
|
2017-04-21 17:43:03 -04:00
|
|
|
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.
|
2017-05-10 21:21:02 -04:00
|
|
|
descriptor_config.block_transfer_count = self->len / self->bytes_per_sample;
|
2017-04-21 17:43:03 -04:00
|
|
|
descriptor_config.source_address = ((uint32_t)self->buffer + self->len);
|
2017-05-10 21:21:02 -04:00
|
|
|
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);
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
dma_descriptor_create(audio_dma.descriptor, &descriptor_config);
|
2017-05-10 21:21:02 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-21 17:43:03 -04:00
|
|
|
active_audioout = self;
|
|
|
|
dma_start_transfer_job(&audio_dma);
|
|
|
|
|
2017-05-10 21:21:02 -04:00
|
|
|
if (MP_STATE_VM(audioout_block_counter) != NULL) {
|
|
|
|
tc_start_counter(MP_STATE_VM(audioout_block_counter));
|
|
|
|
}
|
2017-04-21 17:43:03 -04:00
|
|
|
set_timer_frequency(self->frequency);
|
2017-05-10 21:21:02 -04:00
|
|
|
tc_start_counter(MP_STATE_VM(audioout_sample_timer));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
|
2017-05-10 21:21:02 -04:00
|
|
|
if (active_audioout == self) {
|
|
|
|
if (MP_STATE_VM(audioout_block_counter) != NULL) {
|
|
|
|
tc_stop_counter(MP_STATE_VM(audioout_block_counter));
|
|
|
|
}
|
|
|
|
tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
|
2017-04-21 17:43:03 -04:00
|
|
|
dma_abort_job(&audio_dma);
|
|
|
|
active_audioout = NULL;
|
2017-05-04 14:57:51 -04:00
|
|
|
dac_disable(MP_STATE_VM(audioout_dac_instance));
|
2017-04-21 17:43:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self) {
|
|
|
|
if (!dma_is_busy(&audio_dma)) {
|
2017-05-10 21:21:02 -04:00
|
|
|
if (active_audioout != NULL) {
|
|
|
|
common_hal_audioio_audioout_stop(active_audioout);
|
|
|
|
}
|
2017-04-21 17:43:03 -04:00
|
|
|
active_audioout = NULL;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|