summaryrefslogtreecommitdiff
path: root/ports/raspberrypi/common-hal
diff options
context:
space:
mode:
authorScott Shawcroft <scott@tannewt.org>2021-02-25 16:59:15 -0800
committerScott Shawcroft <scott@tannewt.org>2021-02-25 16:59:15 -0800
commit3f08cb47b846fff41ef9a45d49758f0cef61e723 (patch)
tree66d5093b24d127a1fb60cc70f5087b39e87fbff5 /ports/raspberrypi/common-hal
parent52bc935fa7aa9e5c94f75fec8b2fa746c83f0be7 (diff)
parentb69cb0144ca0d54011be540213d1e25e673a63de (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.c224
-rw-r--r--ports/raspberrypi/common-hal/audiobusio/I2SOut.h47
-rw-r--r--ports/raspberrypi/common-hal/audiobusio/PDMIn.c172
-rw-r--r--ports/raspberrypi/common-hal/audiobusio/PDMIn.h50
-rw-r--r--ports/raspberrypi/common-hal/audiobusio/__init__.c1
-rw-r--r--ports/raspberrypi/common-hal/neopixel_write/__init__.c22
-rw-r--r--ports/raspberrypi/common-hal/rp2pio/StateMachine.c206
-rw-r--r--ports/raspberrypi/common-hal/rp2pio/StateMachine.h7
-rwxr-xr-xports/raspberrypi/common-hal/supervisor/Runtime.c4
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();
}