diff options
| author | Scott Shawcroft <scott@tannewt.org> | 2021-02-25 16:59:15 -0800 |
|---|---|---|
| committer | Scott Shawcroft <scott@tannewt.org> | 2021-02-25 16:59:15 -0800 |
| commit | 3f08cb47b846fff41ef9a45d49758f0cef61e723 (patch) | |
| tree | 66d5093b24d127a1fb60cc70f5087b39e87fbff5 /ports/raspberrypi/common-hal | |
| parent | 52bc935fa7aa9e5c94f75fec8b2fa746c83f0be7 (diff) | |
| parent | b69cb0144ca0d54011be540213d1e25e673a63de (diff) | |
Merge remote-tracking branch 'adafruit/main' into busio-uart-rp
Diffstat (limited to 'ports/raspberrypi/common-hal')
| -rw-r--r-- | ports/raspberrypi/common-hal/audiobusio/I2SOut.c | 224 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/audiobusio/I2SOut.h | 47 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/audiobusio/PDMIn.c | 172 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/audiobusio/PDMIn.h | 50 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/audiobusio/__init__.c | 1 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/neopixel_write/__init__.c | 22 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/rp2pio/StateMachine.c | 206 | ||||
| -rw-r--r-- | ports/raspberrypi/common-hal/rp2pio/StateMachine.h | 7 | ||||
| -rwxr-xr-x | ports/raspberrypi/common-hal/supervisor/Runtime.c | 4 |
9 files changed, 679 insertions, 54 deletions
diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c new file mode 100644 index 000000000..83a443834 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c @@ -0,0 +1,224 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 "mpconfigport.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/microcontroller/Pin.h" +#include "shared-module/audiocore/__init__.h" +#include "bindings/rp2pio/StateMachine.h" +#include "supervisor/shared/translate.h" + +const uint16_t i2s_program[] = { +// ; Load the next set of samples +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull noblock side 0b01 ; Loads OSR with the next FIFO value or X + 0x8880, +// mov x osr side 0b01 ; Save the new value in case we need it again + 0xa827, +// set y 14 side 0b01 + 0xe84e, +// bitloop1: +// out pins 1 side 0b00 [2] + 0x6201, +// jmp y-- bitloop1 side 0b01 [2] + 0x0a83, +// out pins 1 side 0b10 [2] + 0x7201, +// set y 14 side 0b11 [2] + 0xfa4e, +// bitloop0: +// out pins 1 side 0b10 [2] + 0x7201, +// jmp y-- bitloop0 side 0b11 [2] + 0x1a87, +// out pins 1 side 0b00 [2] + 0x6201 +}; + +const uint16_t i2s_program_left_justified[] = { +// ; Load the next set of samples +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull noblock side 0b11 ; Loads OSR with the next FIFO value or X + 0x9880, +// mov x osr side 0b11 ; Save the new value in case we need it again + 0xb827, +// set y 14 side 0b11 + 0xf84e, +// bitloop1: +// out pins 1 side 0b00 [2] + 0x6201, +// jmp y-- bitloop1 side 0b01 [2] + 0x0a83, +// out pins 1 side 0b10 [2] + 0x6201, +// set y 14 side 0b01 [2] + 0xea4e, +// bitloop0: +// out pins 1 side 0b10 [2] + 0x7201, +// jmp y-- bitloop0 side 0b11 [2] + 0x1a87, +// out pins 1 side 0b10 [2] + 0x7201 +}; + +void i2sout_reset(void) { +} + +// Caller validates that pins are free. +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) { + if (bit_clock->number != word_select->number - 1) { + mp_raise_ValueError(translate("Bit clock and word select must be sequential pins")); + } + + const uint16_t* program = i2s_program; + size_t program_len = sizeof(i2s_program) / sizeof(i2s_program[0]); + if (left_justified) { + program = i2s_program_left_justified; + program_len = sizeof(i2s_program_left_justified) / sizeof(i2s_program_left_justified[0]);; + } + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct(&self->state_machine, + program, program_len, + 44100 * 32 * 6, // Clock at 44.1 khz to warm the DAC up. + NULL, 0, + data, 1, 0, 0xffffffff, // out pin + NULL, 0, // in pins + NULL, 0, 0, 0x1f, // set pins + bit_clock, 2, 0, 0x1f, // sideset pins + true, // exclusive pin use + false, 32, false, // shift out left to start with MSB + false, // Wait for txstall + false, 32, false); // in settings + + self->playing = false; + audio_dma_init(&self->dma); +} + +bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) { + if (common_hal_audiobusio_i2sout_deinited(self)) { + return; + } + + common_hal_rp2pio_statemachine_deinit(&self->state_machine); +} + +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); + } + uint8_t bits_per_sample = audiosample_bits_per_sample(sample); + // Make sure we transmit a minimum of 16 bits. + // TODO: Maybe we need an intermediate object to upsample instead. This is + // only needed for some I2S devices that expect at least 8. + if (bits_per_sample < 16) { + bits_per_sample = 16; + } + // We always output stereo so output twice as many bits. + uint16_t bits_per_sample_output = bits_per_sample * 2; + size_t clocks_per_bit = 6; + uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample); + common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency); + common_hal_rp2pio_statemachine_restart(&self->state_machine); + + uint8_t channel_count = audiosample_channel_count(sample); + if (channel_count > 2) { + mp_raise_ValueError(translate("Too many channels in sample.")); + } + + audio_dma_result result = audio_dma_setup_playback( + &self->dma, + sample, + loop, + false, // single channel + 0, // audio channel + true, // output signed + bits_per_sample, + (uint32_t) &self->state_machine.pio->txf[self->state_machine.state_machine], // output register + self->state_machine.tx_dreq); // data request line + + if (result == AUDIO_DMA_DMA_BUSY) { + common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("No DMA channel found")); + } else if (result == AUDIO_DMA_MEMORY_ERROR) { + common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion")); + } + + // Turn on the state machine's clock. + + self->playing = true; +} + +void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) { + audio_dma_pause(&self->dma); +} + +void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) { + // Maybe: Clear any overrun/underrun errors + + audio_dma_resume(&self->dma); +} + +bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) { + return audio_dma_get_paused(&self->dma); +} + +void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) { + audio_dma_stop(&self->dma); + + common_hal_rp2pio_statemachine_stop(&self->state_machine); + + self->playing = false; +} + +bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) { + bool playing = audio_dma_get_playing(&self->dma); + if (!playing && self->playing) { + common_hal_audiobusio_i2sout_stop(self); + } + return playing; +} diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.h b/ports/raspberrypi/common-hal/audiobusio/I2SOut.h new file mode 100644 index 000000000..851e86c8a --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.h @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/rp2pio/StateMachine.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; + rp2pio_statemachine_obj_t state_machine; + bool playing; + audio_dma_t dma; +} audiobusio_i2sout_obj_t; + +void i2sout_reset(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H diff --git a/ports/raspberrypi/common-hal/audiobusio/PDMIn.c b/ports/raspberrypi/common-hal/audiobusio/PDMIn.c new file mode 100644 index 000000000..ffe09326f --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/PDMIn.c @@ -0,0 +1,172 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 <math.h> + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "shared-bindings/audiobusio/PDMIn.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/translate.h" + +#include "audio_dma.h" + +#define OVERSAMPLING 64 +#define SAMPLES_PER_BUFFER 32 + +// MEMS microphones must be clocked at at least 1MHz. +#define MIN_MIC_CLOCK 1000000 + +const uint16_t pdmin[] = { + // in pins 1 side 0b1 + 0x5001, + // push iffull side 0b0 + 0x8040 +}; + +// Caller validates that pins are free. +void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, + const mcu_pin_obj_t* clock_pin, + const mcu_pin_obj_t* data_pin, + uint32_t sample_rate, + uint8_t bit_depth, + bool mono, + uint8_t oversample) { + if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != OVERSAMPLING) { + mp_raise_NotImplementedError(translate("Only 8 or 16 bit mono with " MP_STRINGIFY(OVERSAMPLING) "x oversampling is supported.")); + } + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct(&self->state_machine, + pdmin, sizeof(pdmin) / sizeof(pdmin[0]), + 44100 * 32 * 2, // Clock at 44.1 khz to warm the DAC up. + NULL, 0, + NULL, 1, 0, 0xffffffff, // out pin + data_pin, 1, // in pins + NULL, 0, 0, 0x1f, // set pins + clock_pin, 1, 0, 0x1f, // sideset pins + true, // exclusive pin use + false, 32, false, // out settings + false, // Wait for txstall + false, 32, true); // in settings + + uint32_t actual_frequency = common_hal_rp2pio_statemachine_get_frequency(&self->state_machine); + if (actual_frequency < MIN_MIC_CLOCK) { + mp_raise_ValueError(translate("sampling rate out of range")); + } + + self->sample_rate = actual_frequency / oversample; + self->bit_depth = bit_depth; +} + +bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t* self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* self) { + if (common_hal_audiobusio_pdmin_deinited(self)) { + return; + } +} + +uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t* self) { + return self->bit_depth; +} + +uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t* self) { + return self->sample_rate; +} + +// 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. +const 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_32_TIMES(X) do { X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X } while(0) + +static uint16_t filter_sample(uint32_t pdm_samples[2]) { + uint16_t running_sum = 0; + const uint16_t *filter_ptr = sinc_filter; + for (uint8_t i = 0; i < 2; i++) { + uint32_t pdm_sample = pdm_samples[i]; + REPEAT_32_TIMES( { + if (pdm_sample & 0x1) { + running_sum += *filter_ptr; + } + filter_ptr++; + pdm_sample >>= 1; + } + ); + } + 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 output_buffer_length) { + uint32_t samples[2]; + size_t output_count = 0; + common_hal_rp2pio_statemachine_clear_rxfifo(&self->state_machine); + // Do one read to get the mic going and throw it away. + common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t*) samples, 2 * sizeof(uint32_t), sizeof(uint32_t)); + while (output_count < output_buffer_length && !common_hal_rp2pio_statemachine_get_rxstall(&self->state_machine)) { + common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t*) samples, 2 * sizeof(uint32_t), sizeof(uint32_t)); + // Call filter_sample just one place so it can be inlined. + uint16_t value = filter_sample(samples); + if (self->bit_depth == 8) { + // Truncate to 8 bits. + ((uint8_t*) output_buffer)[output_count] = value >> 8; + } else { + output_buffer[output_count] = value; + } + output_count++; + } + + return output_count; +} + +void common_hal_audiobusio_pdmin_record_to_file(audiobusio_pdmin_obj_t* self, uint8_t* buffer, uint32_t length) { + +} diff --git a/ports/raspberrypi/common-hal/audiobusio/PDMIn.h b/ports/raspberrypi/common-hal/audiobusio/PDMIn.h new file mode 100644 index 000000000..995ffb5d7 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/PDMIn.h @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "bindings/rp2pio/StateMachine.h" + +#include "extmod/vfs_fat.h" +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint32_t sample_rate; + uint8_t serializer; + uint8_t clock_unit; + uint8_t bytes_per_sample; + uint8_t bit_depth; + rp2pio_statemachine_obj_t state_machine; +} audiobusio_pdmin_obj_t; + +void pdmin_reset(void); + +void pdmin_background(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H diff --git a/ports/raspberrypi/common-hal/audiobusio/__init__.c b/ports/raspberrypi/common-hal/audiobusio/__init__.c new file mode 100644 index 000000000..87db40496 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/__init__.c @@ -0,0 +1 @@ +// No audiobusio module functions. diff --git a/ports/raspberrypi/common-hal/neopixel_write/__init__.c b/ports/raspberrypi/common-hal/neopixel_write/__init__.c index 561d438e2..1f0b71ca0 100644 --- a/ports/raspberrypi/common-hal/neopixel_write/__init__.c +++ b/ports/raspberrypi/common-hal/neopixel_write/__init__.c @@ -54,27 +54,25 @@ const uint16_t neopixel_program[] = { 0xa142 }; -const uint16_t init_program[] = { - 0xe081 -}; - void common_hal_neopixel_write(const digitalio_digitalinout_obj_t* digitalinout, uint8_t *pixels, uint32_t num_bytes) { // Set everything up. rp2pio_statemachine_obj_t state_machine; // TODO: Cache the state machine after we create it once. We'll need a way to // change the pins then though. - uint8_t pin_number = digitalinout->pin->number; + uint32_t pins_we_use = 1 << digitalinout->pin->number; bool ok = rp2pio_statemachine_construct(&state_machine, neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]), 800000 * 6, // 800 khz * 6 cycles per bit - init_program, 1, - NULL, 1, - NULL, 1, - digitalinout->pin, 1, - digitalinout->pin, 1, - 1 << pin_number, true, false, + NULL, 0, // init program + NULL, 1, // out + NULL, 1, // in + NULL, 1, // set + digitalinout->pin, 1, // sideset + 0, pins_we_use, // initial pin state + pins_we_use, true, false, true, 8, false, // TX, auto pull every 8 bits. shift left to output msb first + true, // Wait for txstall. If we don't, then we'll deinit too quickly. false, 32, true, // RX setting we don't use false); // claim pins if (!ok) { @@ -86,7 +84,7 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t* digitalinout, // two. while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {} - common_hal_rp2pio_statemachine_write(&state_machine, pixels, num_bytes); + common_hal_rp2pio_statemachine_write(&state_machine, pixels, num_bytes, 1 /* stride in bytes */); // Use a private deinit of the state machine that doesn't reset the pin. rp2pio_statemachine_deinit(&state_machine, true); diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index 90c48130e..5973f47a2 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -54,7 +54,6 @@ STATIC PIO pio_instances[2] = {pio0, pio1}; void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) { uint8_t pio_index = pio_get_index(pio); - pio_sm_unclaim(pio, sm); uint32_t program_id = _current_program_id[pio_index][sm]; if (program_id == 0) { return; @@ -89,6 +88,7 @@ void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) { } } _current_sm_pins[pio_index][sm] = 0; + pio_sm_unclaim(pio, sm); } void reset_rp2pio_statemachine(void) { @@ -130,8 +130,10 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count, const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count, const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count, + uint32_t initial_pin_state, uint32_t initial_pin_direction, uint32_t pins_we_use, bool tx_fifo, bool rx_fifo, bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, bool auto_push, uint8_t push_threshold, bool in_shift_right, bool claim_pins) { // Create a program id that isn't the pointer so we can store it without storing the original object. @@ -199,6 +201,11 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, _current_sm_pins[pio_index][state_machine] = pins_we_use; _current_pins[pio_index] |= pins_we_use; + pio_sm_set_pins_with_mask(self->pio, state_machine, initial_pin_state, pins_we_use); + pio_sm_set_pindirs_with_mask(self->pio, state_machine, initial_pin_direction, pins_we_use); + self->initial_pin_state = initial_pin_state; + self->initial_pin_direction = initial_pin_direction; + for (size_t pin_number = 0; pin_number < TOTAL_GPIO_COUNT; pin_number++) { if ((pins_we_use & (1 << pin_number)) == 0) { continue; @@ -260,27 +267,38 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, self->out = tx_fifo; self->out_shift_right = out_shift_right; self->in_shift_right = in_shift_right; + self->wait_for_txstall = wait_for_txstall; + + self->init = init; + self->init_len = init_len; sm_config_set_fifo_join(&c, join); pio_sm_init(self->pio, self->state_machine, program_offset, &c); + common_hal_rp2pio_statemachine_run(self, init, init_len); + + common_hal_rp2pio_statemachine_set_frequency(self, frequency); pio_sm_set_enabled(self->pio, self->state_machine, true); - for (size_t i = 0; i < init_len; i++) { - pio_sm_exec(self->pio, self->state_machine, init[i]); - } return true; } +static uint32_t mask_and_rotate(const mcu_pin_obj_t* first_pin, uint32_t bit_count, uint32_t value) { + value = value & ((1 << bit_count) - 1); + uint32_t shift = first_pin->number; + return value << shift | value >> (32 - shift); +} + void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, const uint16_t* program, size_t program_len, size_t frequency, const uint16_t* init, size_t init_len, - const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count, + const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count, uint32_t initial_out_pin_state, uint32_t initial_out_pin_direction, const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count, - const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count, - const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count, + const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count, uint32_t initial_set_pin_state, uint32_t initial_set_pin_direction, + const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count, uint32_t initial_sideset_pin_state, uint32_t initial_sideset_pin_direction, bool exclusive_pin_use, bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, bool auto_push, uint8_t push_threshold, bool in_shift_right) { // First, check that all pins are free OR already in use by any PIO if exclusive_pin_use is false. @@ -397,9 +415,26 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, mp_raise_ValueError_varg(translate("Program does OUT without loading OSR")); } - if (in_pin_count > 8 || out_pin_count > 8) { - mp_raise_NotImplementedError(translate("Only IN/OUT of up to 8 supported")); + uint32_t initial_pin_state = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_state); + uint32_t initial_pin_direction = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_direction); + initial_set_pin_state = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_state); + initial_set_pin_direction = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_direction); + uint32_t set_out_overlap = mask_and_rotate(first_out_pin, out_pin_count, 0xffffffff) & + mask_and_rotate(first_set_pin, set_pin_count, 0xffffffff); + // Check that OUT and SET settings agree because we don't have a way of picking one over the other. + if ((initial_pin_state & set_out_overlap) != (initial_set_pin_state & set_out_overlap)) { + mp_raise_ValueError(translate("Initial set pin state conflicts with initial out pin state")); } + if ((initial_pin_direction & set_out_overlap) != (initial_set_pin_direction & set_out_overlap)) { + mp_raise_ValueError(translate("Initial set pin direcion conflicts with initial out pin direction")); + } + initial_pin_state |= initial_set_pin_state; + initial_pin_direction |= initial_set_pin_direction; + + // Sideset overrides OUT or SET so we always use its values. + uint32_t sideset_mask = mask_and_rotate(first_sideset_pin, sideset_pin_count, 0x1f); + initial_pin_state = (initial_pin_state & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_state); + initial_pin_direction = (initial_pin_direction & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_direction); bool ok = rp2pio_statemachine_construct(self, program, program_len, @@ -409,8 +444,10 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, first_in_pin, in_pin_count, first_set_pin, set_pin_count, first_sideset_pin, sideset_pin_count, + initial_pin_state, initial_pin_direction, pins_we_use, tx_fifo, rx_fifo, auto_pull, pull_threshold, out_shift_right, + wait_for_txstall, auto_push, push_threshold, in_shift_right, true /* claim pins */); if (!ok) { @@ -418,10 +455,51 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, } } +void common_hal_rp2pio_statemachine_restart(rp2pio_statemachine_obj_t *self) { + pio_sm_restart(self->pio, self->state_machine); + + uint8_t pio_index = pio_get_index(self->pio); + uint32_t pins_we_use = _current_sm_pins[pio_index][self->state_machine]; + pio_sm_set_pins_with_mask(self->pio, self->state_machine, self->initial_pin_state, pins_we_use); + pio_sm_set_pindirs_with_mask(self->pio, self->state_machine, self->initial_pin_direction, pins_we_use); + common_hal_rp2pio_statemachine_run(self, self->init, self->init_len); + pio_sm_set_enabled(self->pio, self->state_machine, true); +} + +void common_hal_rp2pio_statemachine_stop(rp2pio_statemachine_obj_t *self) { + pio_sm_set_enabled(self->pio, self->state_machine, false); +} + +void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const uint16_t *instructions, size_t len) { + for (size_t i = 0; i < len; i++) { + pio_sm_exec(self->pio, self->state_machine, instructions[i]); + } +} + uint32_t common_hal_rp2pio_statemachine_get_frequency(rp2pio_statemachine_obj_t* self) { return self->actual_frequency; } +void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t* self, uint32_t frequency) { + if (frequency == 0) { + frequency = clock_get_hz(clk_sys); + } + uint64_t frequency256 = ((uint64_t) clock_get_hz(clk_sys)) * 256; + uint64_t div256 = frequency256 / frequency; + if (frequency256 % div256 > 0) { + div256 += 1; + } + // 0 is interpreted as 0x10000 so it's valid. + if (div256 / 256 > 0x10000 || frequency > clock_get_hz(clk_sys)) { + mp_raise_ValueError_varg(translate("%q out of range"), MP_QSTR_frequency); + } + self->actual_frequency = frequency256 / div256; + + pio_sm_set_clkdiv_int_frac(self->pio, self->state_machine, div256 / 256, div256 % 256); + // Reset the clkdiv counter in case our new TOP is lower. + pio_sm_clkdiv_restart(self->pio, self->state_machine); +} + void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins) { uint8_t sm = self->state_machine; uint8_t pio_index = pio_get_index(self->pio); @@ -445,9 +523,21 @@ bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self) { return self->state_machine == NUM_PIO_STATE_MACHINES; } +enum dma_channel_transfer_size _stride_to_dma_size(uint8_t stride) { + switch (stride) { + case 4: + return DMA_SIZE_32; + case 2: + return DMA_SIZE_16; + case 1: + default: + return DMA_SIZE_8; + } +} + static bool _transfer(rp2pio_statemachine_obj_t *self, - const uint8_t *data_out, size_t out_len, - uint8_t *data_in, size_t in_len) { + const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes, + uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes) { // This implementation is based on SPI but varies because the tx and rx buffers // may be different lengths and occur at different times or speeds. @@ -472,42 +562,43 @@ static bool _transfer(rp2pio_statemachine_obj_t *self, if (tx) { tx_destination = (volatile uint8_t*) &self->pio->txf[self->state_machine]; if (!self->out_shift_right) { - tx_destination += 3; + tx_destination += 4 - out_stride_in_bytes; } } if (rx) { rx_source = (const volatile uint8_t*) &self->pio->rxf[self->state_machine]; - if (!self->in_shift_right) { - rx_source += 3; + if (self->in_shift_right) { + rx_source += 4 - in_stride_in_bytes; } } + uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + self->state_machine); bool use_dma = (!rx || chan_rx >= 0) && (!tx || chan_tx >= 0); if (use_dma) { dma_channel_config c; uint32_t channel_mask = 0; if (tx) { c = dma_channel_get_default_config(chan_tx); - channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(out_stride_in_bytes)); channel_config_set_dreq(&c, self->tx_dreq); channel_config_set_read_increment(&c, true); channel_config_set_write_increment(&c, false); dma_channel_configure(chan_tx, &c, tx_destination, data_out, - len, + out_len / out_stride_in_bytes, false); channel_mask |= 1u << chan_tx; } if (rx) { c = dma_channel_get_default_config(chan_rx); - channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(in_stride_in_bytes)); channel_config_set_dreq(&c, self->rx_dreq); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); dma_channel_configure(chan_rx, &c, data_in, rx_source, - len, + in_len / in_stride_in_bytes, false); channel_mask |= 1u << chan_rx; } @@ -528,7 +619,7 @@ static bool _transfer(rp2pio_statemachine_obj_t *self, } } // Clear the stall bit so we can detect when the state machine is done transmitting. - self->pio->fdebug = PIO_FDEBUG_TXSTALL_BITS; + self->pio->fdebug = stall_mask; } // If we have claimed only one channel successfully, we should release immediately. This also @@ -542,27 +633,31 @@ static bool _transfer(rp2pio_statemachine_obj_t *self, if (!use_dma && !mp_hal_is_interrupted()) { // Use software for small transfers, or if couldn't claim two DMA channels - size_t rx_remaining = in_len; - size_t tx_remaining = out_len; + size_t rx_remaining = in_len / in_stride_in_bytes; + size_t tx_remaining = out_len / out_stride_in_bytes; while (rx_remaining || tx_remaining) { - for (int i=0; i<32; i++) { - bool did_transfer = false; - if (tx_remaining && !pio_sm_is_tx_fifo_full(self->pio, self->state_machine)) { + while (tx_remaining && !pio_sm_is_tx_fifo_full(self->pio, self->state_machine)) { + if (out_stride_in_bytes == 1) { *tx_destination = *data_out; - data_out++; - --tx_remaining; - did_transfer = true; + } else if (in_stride_in_bytes == 2) { + *((uint16_t*) tx_destination) = *((uint16_t*) data_out); + } else if (in_stride_in_bytes == 4) { + *((uint32_t*) tx_destination) = *((uint32_t*) data_out); } - if (rx_remaining && !pio_sm_is_rx_fifo_empty(self->pio, self->state_machine)) { + data_out += out_stride_in_bytes; + --tx_remaining; + } + while (rx_remaining && !pio_sm_is_rx_fifo_empty(self->pio, self->state_machine)) { + if (in_stride_in_bytes == 1) { *data_in = (uint8_t) *rx_source; - data_in++; - --rx_remaining; - did_transfer = true; - } - if (!did_transfer) { - break; + } else if (in_stride_in_bytes == 2) { + *((uint16_t*) data_in) = *((uint16_t*) rx_source); + } else if (in_stride_in_bytes == 4) { + *((uint32_t*) data_in) = *((uint32_t*) rx_source); } + data_in += in_stride_in_bytes; + --rx_remaining; } RUN_BACKGROUND_TASKS; if (mp_hal_is_interrupted()) { @@ -570,24 +665,55 @@ static bool _transfer(rp2pio_statemachine_obj_t *self, } } // Clear the stall bit so we can detect when the state machine is done transmitting. - self->pio->fdebug = PIO_FDEBUG_TXSTALL_BITS; + self->pio->fdebug = stall_mask; } // Wait for the state machine to finish transmitting the data we've queued // up. if (tx) { while (!pio_sm_is_tx_fifo_empty(self->pio, self->state_machine) || - (self->pio->fdebug & PIO_FDEBUG_TXSTALL_BITS) == 0) { + (self->wait_for_txstall && (self->pio->fdebug & stall_mask) == 0)) { RUN_BACKGROUND_TASKS; } } return true; } -// Writes out the given data. -bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, - const uint8_t *data, size_t len) { +// TODO: Provide a way around these checks in case someone wants to use the FIFO +// with manually run code. + +bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes) { if (!self->out) { mp_raise_RuntimeError(translate("No out in program")); } - return _transfer(self, data, len, NULL, 0); + return _transfer(self, data, len, stride_in_bytes, NULL, 0, 0); +} + +bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes) { + if (!self->in) { + mp_raise_RuntimeError(translate("No in in program")); + } + return _transfer(self, NULL, 0, 0, data, len, stride_in_bytes); +} + +bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self, + const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes, + uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes) { + if (!self->in || !self->out) { + mp_raise_RuntimeError(translate("No in or out in program")); + } + return _transfer(self, data_out, out_len, out_stride_in_bytes, data_in, in_len, in_stride_in_bytes); +} + +bool common_hal_rp2pio_statemachine_get_rxstall(rp2pio_statemachine_obj_t* self) { + uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine); + return (self->pio->fdebug & stall_mask) != 0; +} + +void common_hal_rp2pio_statemachine_clear_rxfifo(rp2pio_statemachine_obj_t *self) { + uint8_t level = pio_sm_get_rx_fifo_level(self->pio, self->state_machine); + uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine); + for (size_t i = 0; i < level; i++) { + (void) self->pio->rxf[self->state_machine]; + } + self->pio->fdebug = stall_mask; } diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h index 6b70b6b5b..f084e0986 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.h +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.h @@ -36,8 +36,13 @@ typedef struct { uint32_t pins; // Bitmask of what pins this state machine uses. int state_machine; PIO pio; + const uint16_t* init; + size_t init_len; + uint32_t initial_pin_state; + uint32_t initial_pin_direction; bool in; bool out; + bool wait_for_txstall; uint tx_dreq; uint rx_dreq; bool out_shift_right; @@ -56,8 +61,10 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count, const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count, const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count, + uint32_t initial_pin_state, uint32_t initial_pin_direction, uint32_t pins_we_use, bool tx_fifo, bool rx_fifo, bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, bool auto_push, uint8_t push_threshold, bool in_shift_right, bool claim_pins); diff --git a/ports/raspberrypi/common-hal/supervisor/Runtime.c b/ports/raspberrypi/common-hal/supervisor/Runtime.c index 6be38f216..974f26cec 100755 --- a/ports/raspberrypi/common-hal/supervisor/Runtime.c +++ b/ports/raspberrypi/common-hal/supervisor/Runtime.c @@ -28,10 +28,10 @@ #include "shared-bindings/supervisor/Runtime.h" #include "supervisor/serial.h" -bool common_hal_get_serial_connected(void) { +bool common_hal_supervisor_runtime_get_serial_connected(void) { return (bool) serial_connected(); } -bool common_hal_get_serial_bytes_available(void) { +bool common_hal_supervisor_runtime_get_serial_bytes_available(void) { return (bool) serial_bytes_available(); } |
