diff options
| author | Dan Halbert <halbert@halwitz.org> | 2018-01-02 21:25:41 -0500 |
|---|---|---|
| committer | Dan Halbert <halbert@halwitz.org> | 2018-01-02 21:25:41 -0500 |
| commit | 065e82015f0d2bd6732c62b7375fcc7af87fa0cf (patch) | |
| tree | e48fa8caa5675d529d335a2b154e69eba8199cff /ports/atmel-samd/common-hal | |
| parent | ce81c8dda9686d3cd78d9c5383b6982c148ac7ec (diff) | |
merge from 2.2.0 + fix up board defs
Diffstat (limited to 'ports/atmel-samd/common-hal')
| -rw-r--r-- | ports/atmel-samd/common-hal/analogio/AnalogIn.c | 9 | ||||
| -rw-r--r-- | ports/atmel-samd/common-hal/audiobusio/PDMIn.c | 185 | ||||
| -rw-r--r-- | ports/atmel-samd/common-hal/busio/SPI.c | 17 | ||||
| -rw-r--r-- | ports/atmel-samd/common-hal/digitalio/DigitalInOut.c | 14 | ||||
| -rw-r--r-- | ports/atmel-samd/common-hal/microcontroller/__init__.c | 30 |
5 files changed, 178 insertions, 77 deletions
diff --git a/ports/atmel-samd/common-hal/analogio/AnalogIn.c b/ports/atmel-samd/common-hal/analogio/AnalogIn.c index 03c502dff..cbb9f5de5 100644 --- a/ports/atmel-samd/common-hal/analogio/AnalogIn.c +++ b/ports/atmel-samd/common-hal/analogio/AnalogIn.c @@ -74,8 +74,10 @@ uint16_t common_hal_analogio_analogin_get_value(analogio_analogin_obj_t *self) { config_adc.reference = ADC_REFERENCE_INTVCC1; config_adc.gain_factor = ADC_GAIN_FACTOR_DIV2; config_adc.positive_input = self->pin->adc_input; - config_adc.resolution = ADC_RESOLUTION_16BIT; - config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV128; + config_adc.resolution = ADC_RESOLUTION_12BIT; + // Default input clock is GCLK0 (48 MHz) + // 48Mhz / 32 = 1.5MHz. Max ADC clock is 2.1MHz + config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV32; struct adc_module adc_instance; // ADC must have been disabled before adc_init() is called. @@ -108,7 +110,8 @@ uint16_t common_hal_analogio_analogin_get_value(analogio_analogin_obj_t *self) { } adc_disable(&adc_instance); - return data; + // Scale to 16 bits. In the future we might make this be this be under API control. + return data * 16; } float common_hal_analogio_analogin_get_reference_voltage(analogio_analogin_obj_t *self) { diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c index db671f213..50d3b180c 100644 --- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c +++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c @@ -26,6 +26,7 @@ #include <stdint.h> #include <string.h> +#include <math.h> #include "py/gc.h" #include "py/mperrno.h" @@ -42,6 +43,12 @@ #include "shared_dma.h" #include "tick.h" +#define OVERSAMPLING 64 +#define SAMPLES_PER_BUFFER 32 + +// MEMS microphones must be clocked at at least 1MHz. +#define MIN_MIC_CLOCK 1000000 + void pdmin_reset(void) { while (I2S->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE) {} I2S->INTENCLR.reg = I2S_INTENCLR_MASK; @@ -96,8 +103,8 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, mp_raise_RuntimeError("Unable to allocate audio DMA block counter."); } - if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != 64) { - mp_raise_NotImplementedError("Only 8 or 16 bit mono with 64 oversample is supported."); + if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != OVERSAMPLING) { + mp_raise_NotImplementedError("Only 8 or 16 bit mono with " MP_STRINGIFY(OVERSAMPLING) "x oversampling is supported."); } // TODO(tannewt): Use the DPLL to get a more precise sampling rate. @@ -112,12 +119,17 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, config_clock_unit.clock.mck_out_enable = false; config_clock_unit.clock.sck_src = I2S_SERIAL_CLOCK_SOURCE_MCKDIV; - config_clock_unit.clock.sck_div = 8000000 / frequency / oversample; - self->frequency = 8000000 / config_clock_unit.clock.sck_div / oversample; + uint32_t clock_divisor = (uint32_t) roundf( 8000000.0f / frequency / oversample); + config_clock_unit.clock.sck_div = clock_divisor; + float mic_clock_freq = 8000000.0f / clock_divisor; + self->frequency = mic_clock_freq / oversample; + if (mic_clock_freq < MIN_MIC_CLOCK || clock_divisor == 0 || clock_divisor > 255) { + mp_raise_ValueError("sampling frequency out of range"); + } config_clock_unit.frame.number_slots = 2; config_clock_unit.frame.slot_size = I2S_SLOT_SIZE_16_BIT; - config_clock_unit.frame.data_delay = I2S_DATA_DELAY_1; + config_clock_unit.frame.data_delay = I2S_DATA_DELAY_0; config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_SLOT; @@ -141,6 +153,10 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, i2s_serializer_set_config(&self->i2s_instance, self->serializer, &config_serializer); i2s_enable(&self->i2s_instance); + // Run the serializer all the time. This eliminates startup delay for the microphone. + i2s_clock_unit_enable(&self->i2s_instance, self->clock_unit); + i2s_serializer_enable(&self->i2s_instance, self->serializer); + self->bytes_per_sample = oversample >> 3; self->bit_depth = bit_depth; } @@ -154,6 +170,8 @@ void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* self) { return; } i2s_disable(&self->i2s_instance); + i2s_serializer_disable(&self->i2s_instance, self->serializer); + i2s_clock_unit_disable(&self->i2s_instance, self->clock_unit); i2s_reset(&self->i2s_instance); reset_pin(self->clock_pin->pin); reset_pin(self->data_pin->pin); @@ -195,11 +213,15 @@ static void setup_dma(audiobusio_pdmin_obj_t* self, uint32_t length, } dma_descriptor_create(audio_dma.descriptor, &descriptor_config); + // Do we need more values than will fit in the first buffer? + // If so, set up a second buffer chained to be filled after the first buffer. if (length * words_per_sample > words_per_buffer) { block_transfer_count = words_per_buffer; descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor); if (length * words_per_sample < 2 * words_per_buffer) { - block_transfer_count = 2 * words_per_buffer - length * words_per_sample; + // Length needed is more than one buffer but less than two. + // Subtract off the size of the first buffer, and what remains is the count we need. + block_transfer_count = length * words_per_sample - words_per_buffer; descriptor_config.next_descriptor_address = 0; } descriptor_config.block_transfer_count = block_transfer_count; @@ -213,117 +235,150 @@ static void setup_dma(audiobusio_pdmin_obj_t* self, uint32_t length, void start_dma(audiobusio_pdmin_obj_t* self) { dma_start_transfer_job(&audio_dma); tc_start_counter(MP_STATE_VM(audiodma_block_counter)); - i2s_clock_unit_enable(&self->i2s_instance, self->clock_unit); - i2s_serializer_enable(&self->i2s_instance, self->serializer); I2S->DATA[1].reg = I2S->DATA[1].reg; } void stop_dma(audiobusio_pdmin_obj_t* self) { - // Turn off the I2S clock and serializer. Peripheral is still enabled. - i2s_serializer_disable(&self->i2s_instance, self->serializer); - i2s_clock_unit_disable(&self->i2s_instance, self->clock_unit); - - // Shutdown the DMA + // Shutdown the DMA: serializer keeps running. tc_stop_counter(MP_STATE_VM(audiodma_block_counter)); dma_abort_job(&audio_dma); } -static const uint16_t sinc_filter[64] = { - 0, 1, 6, 16, 29, 49, 75, 108, - 149, 200, 261, 334, 418, 514, 622, 742, - 872, 1012, 1161, 1315, 1472, 1631, 1787, 1938, - 2081, 2212, 2329, 2429, 2509, 2568, 2604, 2616, - 2604, 2568, 2509, 2429, 2329, 2212, 2081, 1938, - 1787, 1631, 1472, 1315, 1161, 1012, 872, 742, - 622, 514, 418, 334, 261, 200, 149, 108, - 75, 49, 29, 16, 6, 1, 0, 0 +// a windowed sinc filter for 44 khz, 64 samples +// +// This filter is good enough to use for lower sample rates as +// well. It does not increase the noise enough to be a problem. +// +// In the long run we could use a fast filter like this to do the +// decimation and initial filtering in real time, filtering to a +// higher sample rate than specified. Then after the audio is +// recorded, a more expensive filter non-real-time filter could be +// used to down-sample and low-pass. +uint16_t sinc_filter [OVERSAMPLING] = { + 0, 2, 9, 21, 39, 63, 94, 132, + 179, 236, 302, 379, 467, 565, 674, 792, + 920, 1055, 1196, 1341, 1487, 1633, 1776, 1913, + 2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516, + 2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913, + 1776, 1633, 1487, 1341, 1196, 1055, 920, 792, + 674, 565, 467, 379, 302, 236, 179, 132, + 94, 63, 39, 21, 9, 2, 0, 0 }; +#define REPEAT_16_TIMES(X) X X X X X X X X X X X X X X X X + static uint16_t filter_sample(uint32_t pdm_samples[4]) { - uint16_t sample = 0; - for (uint8_t i = 0; i < 4; i++) { - uint16_t pdm = pdm_samples[i] & 0xffff; - for (uint8_t j = 0; j < 16; j++) { - if ((pdm & 0x8000) != 0) { - sample += sinc_filter[i * 16 + j]; + uint16_t running_sum = 0; + const uint16_t *filter_ptr = sinc_filter; + for (uint8_t i = 0; i < OVERSAMPLING/16; i++) { + // The sample is 16-bits right channel in the upper two bytes and 16-bits left channel + // in the lower two bytes. + // We just ignore the upper bits + uint32_t pdm_sample = pdm_samples[i]; + REPEAT_16_TIMES( { + if (pdm_sample & 0x8000) { + running_sum += *filter_ptr; + } + filter_ptr++; + pdm_sample <<= 1; } - pdm <<= 1; - } + ) } - return sample; + return running_sum; } +// output_buffer may be a byte buffer or a halfword buffer. +// output_buffer_length is the number of slots, not the number of bytes. uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* self, - uint16_t* output_buffer, uint32_t length) { - // Write the wave file header. - - // We allocate two 256 byte buffers on the stack to use for double buffering. - // Our oversample rate is 64 (bits) so each buffer produces 32 samples. - // TODO(tannewt): Can the compiler optimize better if we fix the size of - // these buffers? - uint8_t samples_per_buffer = 32; + uint16_t* output_buffer, uint32_t output_buffer_length) { + // We allocate two buffers on the stack to use for double buffering. + const uint8_t samples_per_buffer = SAMPLES_PER_BUFFER; // For every word we record, we throw away 2 bytes of a phantom second channel. - uint8_t words_per_sample = self->bytes_per_sample / 2; - uint8_t words_per_buffer = samples_per_buffer * words_per_sample; + const uint8_t words_per_sample = self->bytes_per_sample / 2; + const uint8_t words_per_buffer = samples_per_buffer * words_per_sample; uint32_t first_buffer[words_per_buffer]; uint32_t second_buffer[words_per_buffer]; COMPILER_ALIGNED(16) DmacDescriptor second_descriptor; - setup_dma(self, length, &second_descriptor, words_per_buffer, + setup_dma(self, output_buffer_length, &second_descriptor, words_per_buffer, words_per_sample, first_buffer, second_buffer); start_dma(self); // Record uint32_t buffers_processed = 0; - uint32_t total_bytes = 0; + uint32_t values_output = 0; - uint64_t start_ticks = ticks_ms; - while (total_bytes < length) { + uint32_t remaining_samples_needed = output_buffer_length; + while (values_output < output_buffer_length) { // Wait for the next buffer to fill - while (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) == buffers_processed) { + uint32_t block_counter; + while ((block_counter = tc_get_count_value(MP_STATE_VM(audiodma_block_counter))) == buffers_processed) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP #endif } - if (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) != (buffers_processed + 1)) { + if (block_counter != (buffers_processed + 1)) { + // Looks like we aren't keeping up. We shouldn't skip a buffer. break; } - // Throw away the first ~10ms of data because thats during mic start up. - if (ticks_ms - start_ticks < 10) { - buffers_processed++; - continue; - } - uint32_t* buffer = first_buffer; + + // The mic is running all the time, so we don't need to wait the usual 10msec or 100msec + // for it to start up. + + // Flip back and forth between processing the first and second buffers. + uint32_t *buffer = first_buffer; DmacDescriptor* descriptor = audio_dma.descriptor; if (buffers_processed % 2 == 1) { buffer = second_buffer; descriptor = &second_descriptor; } - // Decimate and filter the last buffer - int32_t samples_gathered = descriptor->BTCNT.reg / words_per_sample; - for (uint16_t i = 0; i < samples_gathered; i++) { + // Decimate and filter the buffer that was just filled. + uint32_t samples_gathered = descriptor->BTCNT.reg / words_per_sample; + // Don't run off the end of output buffer. Process only as many as needed. + uint32_t samples_to_process = min(remaining_samples_needed, samples_gathered); + for (uint32_t i = 0; i < samples_to_process; i++) { + // Call filter_sample just one place so it can be inlined. + uint16_t value = filter_sample(buffer + i * words_per_sample); if (self->bit_depth == 8) { - ((uint8_t*) output_buffer)[total_bytes] = filter_sample(buffer + i * words_per_sample) >> 8; - total_bytes += 1; - } else if (self->bit_depth == 16) { - output_buffer[total_bytes / 2] = filter_sample(buffer + i * words_per_sample); - total_bytes += 2; + // Truncate to 8 bits. + ((uint8_t*) output_buffer)[values_output] = value >> 8; + } else { + output_buffer[values_output] = value; } + values_output++; } + buffers_processed++; - if (length - total_bytes < samples_per_buffer) { - descriptor->BTCNT.reg = (length - total_bytes) * words_per_sample; - descriptor->DSTADDR.reg = ((uint32_t) buffer) + (length - total_bytes) * self->bytes_per_sample; + // Compute how many more samples we need, and if the last buffer is the last + // set of samples needed, adjust the DMA count to only fetch as necessary. + remaining_samples_needed = output_buffer_length - values_output; + if (remaining_samples_needed <= samples_per_buffer*2 && + remaining_samples_needed > samples_per_buffer) { + // Adjust the DMA settings for the current buffer, which will be processed + // after the other buffer, which is now receiving samples via DMA. + // We don't adjust the DMA in progress, but the one after that. + // Timeline: + // 1. current buffer (already processed) + // 2. alternate buffer (DMA in progress) + // 3. current buffer (last set of samples needed) + + // Set up to receive the last set of samples (don't include the alternate buffer, now in use). + uint32_t samples_needed_for_last_buffer = remaining_samples_needed - samples_per_buffer; + descriptor->BTCNT.reg = samples_needed_for_last_buffer * words_per_sample; + descriptor->DSTADDR.reg = ((uint32_t) buffer) + + samples_needed_for_last_buffer * words_per_sample * sizeof(buffer[0]); + + // Break chain to alternate buffer. descriptor->DESCADDR.reg = 0; } } stop_dma(self); - return total_bytes; + return values_output; } void common_hal_audiobusio_pdmin_record_to_file(audiobusio_pdmin_obj_t* self, uint8_t* buffer, uint32_t length) { diff --git a/ports/atmel-samd/common-hal/busio/SPI.c b/ports/atmel-samd/common-hal/busio/SPI.c index cb6a165de..4414d7ca9 100644 --- a/ports/atmel-samd/common-hal/busio/SPI.c +++ b/ports/atmel-samd/common-hal/busio/SPI.c @@ -262,3 +262,20 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self, // } return status >= 0; // Status is number of chars read or an error code < 0. } + +bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, uint8_t *data_out, uint8_t *data_in, size_t len) { + if (len == 0) { + return true; + } + int32_t status; +// if (len >= 16) { +// status = shared_dma_transfer(self->spi_master_instance.hw, data_out, data_in, len, 0 /*ignored*/); +// } else { + struct spi_xfer xfer; + xfer.txbuf = data_out; + xfer.rxbuf = data_in; + xfer.size = len; + status = spi_m_sync_transfer(&self->spi_desc, &xfer); +// } + return status >= 0; // Status is number of chars read or an error code < 0. +} diff --git a/ports/atmel-samd/common-hal/digitalio/DigitalInOut.c b/ports/atmel-samd/common-hal/digitalio/DigitalInOut.c index c5fff4ba2..109f14b04 100644 --- a/ports/atmel-samd/common-hal/digitalio/DigitalInOut.c +++ b/ports/atmel-samd/common-hal/digitalio/DigitalInOut.c @@ -58,7 +58,7 @@ void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t* self } void common_hal_digitalio_digitalinout_switch_to_input( - digitalio_digitalinout_obj_t* self, enum digitalio_pull_t pull) { + digitalio_digitalinout_obj_t* self, digitalio_pull_t pull) { self->output = false; common_hal_digitalio_digitalinout_set_pull(self, pull); @@ -66,7 +66,7 @@ void common_hal_digitalio_digitalinout_switch_to_input( void common_hal_digitalio_digitalinout_switch_to_output( digitalio_digitalinout_obj_t* self, bool value, - enum digitalio_drive_mode_t drive_mode) { + digitalio_drive_mode_t drive_mode) { const uint8_t pin = self->pin->pin; gpio_set_pin_pull_mode(pin, GPIO_PULL_OFF); gpio_set_pin_direction(pin, GPIO_DIRECTION_OUT); @@ -79,7 +79,7 @@ void common_hal_digitalio_digitalinout_switch_to_output( common_hal_digitalio_digitalinout_set_value(self, value); } -enum digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( +digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( digitalio_digitalinout_obj_t* self) { return self->output? DIRECTION_OUTPUT : DIRECTION_INPUT; } @@ -115,7 +115,7 @@ bool common_hal_digitalio_digitalinout_get_value( void common_hal_digitalio_digitalinout_set_drive_mode( digitalio_digitalinout_obj_t* self, - enum digitalio_drive_mode_t drive_mode) { + digitalio_drive_mode_t drive_mode) { bool value = common_hal_digitalio_digitalinout_get_value(self); self->open_drain = drive_mode == DRIVE_MODE_OPEN_DRAIN; // True is implemented differently between modes so reset the value to make @@ -125,7 +125,7 @@ void common_hal_digitalio_digitalinout_set_drive_mode( } } -enum digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( +digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( digitalio_digitalinout_obj_t* self) { if (self->open_drain) { return DRIVE_MODE_OPEN_DRAIN; @@ -135,7 +135,7 @@ enum digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( } void common_hal_digitalio_digitalinout_set_pull( - digitalio_digitalinout_obj_t* self, enum digitalio_pull_t pull) { + digitalio_digitalinout_obj_t* self, digitalio_pull_t pull) { enum gpio_pull_mode asf_pull = GPIO_PULL_OFF; switch (pull) { case PULL_UP: @@ -151,7 +151,7 @@ void common_hal_digitalio_digitalinout_set_pull( gpio_set_pin_pull_mode(self->pin->pin, asf_pull); } -enum digitalio_pull_t common_hal_digitalio_digitalinout_get_pull( +digitalio_pull_t common_hal_digitalio_digitalinout_get_pull( digitalio_digitalinout_obj_t* self) { uint32_t pin = self->pin->pin; if (self->output) { diff --git a/ports/atmel-samd/common-hal/microcontroller/__init__.c b/ports/atmel-samd/common-hal/microcontroller/__init__.c index 5c119c439..614fac870 100644 --- a/ports/atmel-samd/common-hal/microcontroller/__init__.c +++ b/ports/atmel-samd/common-hal/microcontroller/__init__.c @@ -27,10 +27,13 @@ #include "py/mphal.h" #include "py/obj.h" #include "hal/include/hal_atomic.h" +#include "py/runtime.h" +#include "reset.h" #include "samd21_pins.h" #include "shared-bindings/nvm/ByteArray.h" +#include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/microcontroller/Processor.h" void common_hal_mcu_delay_us(uint32_t delay) { @@ -48,9 +51,32 @@ void common_hal_mcu_enable_interrupts(void) { atomic_leave_critical(&flags); } +extern uint32_t _ezero; + +void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { + // Set up the defaults. + _bootloader_dbl_tap = DBL_TAP_MAGIC; + _ezero = CIRCUITPY_CANARY_WORD; + + if (runmode == RUNMODE_BOOTLOADER) { + if (!bootloader_available()) { + mp_raise_ValueError("Cannot reset into bootloader because no bootloader is present."); + } + // Pretend to be the first of the two reset presses needed to enter the + // bootloader. That way one reset will end in the bootloader. + _bootloader_dbl_tap = DBL_TAP_MAGIC; + } else if (runmode == RUNMODE_SAFE_MODE) { + _ezero = CIRCUITPY_SOFTWARE_SAFE_MODE; + } +} + +void common_hal_mcu_reset(void) { + reset(); +} + // The singleton microcontroller.Processor object, bound to microcontroller.cpu // It currently only has properties, and no state. -mcu_processor_obj_t common_hal_mcu_processor_obj = { +const mcu_processor_obj_t common_hal_mcu_processor_obj = { .base = { .type = &mcu_processor_type, }, @@ -59,7 +85,7 @@ mcu_processor_obj_t common_hal_mcu_processor_obj = { // NVM is only available on Express boards for now. #if CIRCUITPY_INTERNAL_NVM_SIZE > 0 // The singleton nvm.ByteArray object. -// nvm_bytearray_obj_t common_hal_mcu_nvm_obj = { +// const nvm_bytearray_obj_t common_hal_mcu_nvm_obj = { // .base = { // .type = &nvm_bytearray_type, // }, |
