diff --git a/docs/porting.rst b/docs/porting.rst index f4ed2ab4cc86e..9cb28b7e5d4ac 100644 --- a/docs/porting.rst +++ b/docs/porting.rst @@ -72,6 +72,7 @@ as a natural "TODO" list. An example minimal build list is shown below: CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_I2CTARGET = 0 + CIRCUITPY_SPITARGET = 0 # Requires SPI, PulseIO (stub ok): CIRCUITPY_DISPLAYIO = 0 diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 456f5e698dd11..7b139ac88f4c1 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -594,6 +594,10 @@ msgstr "" msgid "Array values should be single bytes." msgstr "" +#: ports/atmel-samd/common-hal/spitarget/SPITarget.c +msgid "Async SPI transfer in progress on this bus, keep awaiting." +msgstr "" + #: shared-module/memorymonitor/AllocationAlarm.c #, c-format msgid "Attempt to allocate %d blocks" @@ -1725,6 +1729,10 @@ msgstr "" msgid "PWM slice channel A already in use" msgstr "" +#: shared-bindings/spitarget/SPITarget.c +msgid "Packet buffers for an SPI transfer must have the same length." +msgstr "" + #: shared-module/jpegio/JpegDecoder.c msgid "Parameter error" msgstr "" @@ -2017,8 +2025,8 @@ msgstr "" msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" msgstr "" -#: shared-module/audiodelays/Echo.c shared-module/audiofilters/Filter.c -#: shared-module/audiomixer/MixerVoice.c +#: shared-module/audiodelays/Echo.c shared-module/audiofilters/Distortion.c +#: shared-module/audiofilters/Filter.c shared-module/audiomixer/MixerVoice.c msgid "The sample's %q does not match" msgstr "" @@ -2570,8 +2578,8 @@ msgstr "" msgid "bits must be 32 or less" msgstr "" -#: shared-bindings/audiodelays/Echo.c shared-bindings/audiofilters/Filter.c -#: shared-bindings/audiomixer/Mixer.c +#: shared-bindings/audiodelays/Echo.c shared-bindings/audiofilters/Distortion.c +#: shared-bindings/audiofilters/Filter.c shared-bindings/audiomixer/Mixer.c msgid "bits_per_sample must be 8 or 16" msgstr "" diff --git a/ports/atmel-samd/audio_dma.c b/ports/atmel-samd/audio_dma.c index fbf12674c87e9..db2a26ac4f778 100644 --- a/ports/atmel-samd/audio_dma.c +++ b/ports/atmel-samd/audio_dma.c @@ -30,8 +30,6 @@ static audio_dma_t *audio_dma_state[AUDIO_DMA_CHANNEL_COUNT]; // This cannot be in audio_dma_state because it's volatile. static volatile bool audio_dma_pending[AUDIO_DMA_CHANNEL_COUNT]; -static bool audio_dma_allocated[AUDIO_DMA_CHANNEL_COUNT]; - uint8_t find_sync_event_channel_raise() { uint8_t event_channel = find_sync_event_channel(); if (event_channel >= EVSYS_SYNCH_NUM) { @@ -40,24 +38,6 @@ uint8_t find_sync_event_channel_raise() { return event_channel; } -uint8_t dma_allocate_channel(void) { - uint8_t channel; - for (channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) { - if (!audio_dma_allocated[channel]) { - audio_dma_allocated[channel] = true; - return channel; - } - } - return channel; // i.e., return failure -} - -void dma_free_channel(uint8_t channel) { - assert(channel < AUDIO_DMA_CHANNEL_COUNT); - assert(audio_dma_allocated[channel]); - audio_dma_disable_channel(channel); - audio_dma_allocated[channel] = false; -} - void audio_dma_disable_channel(uint8_t channel) { if (channel >= AUDIO_DMA_CHANNEL_COUNT) { return; @@ -191,7 +171,7 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma, bool output_signed, uint32_t output_register_address, uint8_t dma_trigger_source) { - uint8_t dma_channel = dma_allocate_channel(); + uint8_t dma_channel = dma_allocate_channel(true); if (dma_channel >= AUDIO_DMA_CHANNEL_COUNT) { return AUDIO_DMA_DMA_BUSY; } @@ -342,8 +322,7 @@ void audio_dma_reset(void) { for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { audio_dma_state[i] = NULL; audio_dma_pending[i] = false; - audio_dma_allocated[i] = false; - audio_dma_disable_channel(i); + dma_free_channel(i); dma_descriptor(i)->BTCTRL.bit.VALID = false; MP_STATE_PORT(playing_audio)[i] = NULL; } diff --git a/ports/atmel-samd/audio_dma.h b/ports/atmel-samd/audio_dma.h index 3b294492b0f11..7b42a7b6114dd 100644 --- a/ports/atmel-samd/audio_dma.h +++ b/ports/atmel-samd/audio_dma.h @@ -44,9 +44,6 @@ typedef enum { void audio_dma_init(audio_dma_t *dma); void audio_dma_reset(void); -uint8_t dma_allocate_channel(void); -void dma_free_channel(uint8_t channel); - // This sets everything up but doesn't start the timer. // Sample is the python object for the sample to play. // loop is true if we should loop the sample. diff --git a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk index b470a2860e6a5..46aa0b7dc659f 100755 --- a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk @@ -12,4 +12,5 @@ LONGINT_IMPL = MPZ CIRCUITPY_PS2IO = 1 CIRCUITPY_JPEGIO = 0 +CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 diff --git a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk index 95123ae5b85da..8f06d8d1e5168 100644 --- a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk @@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "GD25Q16C, W25Q16JVxQ, W25Q16JVxM" LONGINT_IMPL = MPZ CIRCUITPY_SYNTHIO = 0 CIRCUITPY_JPEGIO = 0 +CIRCUITPY_SPITARGET = 0 diff --git a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk index 8005146114a47..5f4f8cb30cd6e 100644 --- a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk @@ -18,6 +18,7 @@ CIRCUITPY_I2CDISPLAYBUS = 0 CIRCUITPY_JPEGIO = 0 CIRCUITPY_KEYPAD = 1 CIRCUITPY_PARALLELDISPLAYBUS= 0 +CIRCUITPY_SPITARGET = 0 CIRCUITPY_STAGE = 1 FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pybadge diff --git a/ports/atmel-samd/boards/pygamer/mpconfigboard.mk b/ports/atmel-samd/boards/pygamer/mpconfigboard.mk index 5149dda508fb4..6f6dee2e1c42b 100644 --- a/ports/atmel-samd/boards/pygamer/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pygamer/mpconfigboard.mk @@ -18,6 +18,7 @@ CIRCUITPY_I2CDISPLAYBUS = 0 CIRCUITPY_JPEGIO = 0 CIRCUITPY_KEYPAD = 1 CIRCUITPY_PARALLELDISPLAYBUS= 0 +CIRCUITPY_SPITARGET = 0 CIRCUITPY_STAGE = 1 FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pygamer diff --git a/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk b/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk index 7c576a0376bdf..16e439d3ef91f 100644 --- a/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk +++ b/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk @@ -11,4 +11,5 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C LONGINT_IMPL = MPZ CIRCUITPY_JPEGIO = 0 +CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 diff --git a/ports/atmel-samd/boards/uartlogger2/mpconfigboard.mk b/ports/atmel-samd/boards/uartlogger2/mpconfigboard.mk index 1181abd9640dc..4b23566f71fd0 100644 --- a/ports/atmel-samd/boards/uartlogger2/mpconfigboard.mk +++ b/ports/atmel-samd/boards/uartlogger2/mpconfigboard.mk @@ -9,5 +9,6 @@ CHIP_FAMILY = samd51 QSPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ" LONGINT_IMPL = MPZ +CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 CIRCUITPY_JPEGIO = 0 diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c index 3244d06979efb..a1a67d0408247 100644 --- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c +++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c @@ -364,7 +364,7 @@ static uint16_t filter_sample(uint32_t pdm_samples[4]) { // 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) { - uint8_t dma_channel = dma_allocate_channel(); + uint8_t dma_channel = dma_allocate_channel(true); pdmin_event_channel = find_sync_event_channel_raise(); pdmin_dma_block_done = false; diff --git a/ports/atmel-samd/common-hal/busio/SPI.c b/ports/atmel-samd/common-hal/busio/SPI.c index 1da42aaad094e..5d7633be7f8d3 100644 --- a/ports/atmel-samd/common-hal/busio/SPI.c +++ b/ports/atmel-samd/common-hal/busio/SPI.c @@ -17,11 +17,12 @@ #include "hal/include/hal_gpio.h" #include "hal/include/hal_spi_m_sync.h" -#include "hal/include/hpl_spi_m_sync.h" #include "samd/dma.h" #include "samd/sercom.h" +void setup_pin(const mcu_pin_obj_t *pin, uint32_t pinmux); + void common_hal_busio_spi_construct(busio_spi_obj_t *self, const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, const mcu_pin_obj_t *miso, bool half_duplex) { @@ -76,6 +77,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, if (!samd_peripherals_valid_spi_clock_pad(clock_pad)) { continue; } + // find mosi_pad first, since it corresponds to dopo which takes limited values for (int j = 0; j < NUM_SERCOMS_PER_PIN; j++) { if (!mosi_none) { if (sercom_index == mosi->sercom[j].index) { @@ -125,6 +127,8 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, // Pads must be set after spi_m_sync_init(), which uses default values from // the prototypical SERCOM. + + hri_sercomspi_write_CTRLA_MODE_bf(sercom, 3); hri_sercomspi_write_CTRLA_DOPO_bf(sercom, dopo); hri_sercomspi_write_CTRLA_DIPO_bf(sercom, miso_pad); @@ -137,30 +141,21 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, mp_raise_OSError(MP_EIO); } - gpio_set_pin_direction(clock->number, GPIO_DIRECTION_OUT); - gpio_set_pin_pull_mode(clock->number, GPIO_PULL_OFF); - gpio_set_pin_function(clock->number, clock_pinmux); - claim_pin(clock); + setup_pin(clock, clock_pinmux); self->clock_pin = clock->number; if (mosi_none) { self->MOSI_pin = NO_PIN; } else { - gpio_set_pin_direction(mosi->number, GPIO_DIRECTION_OUT); - gpio_set_pin_pull_mode(mosi->number, GPIO_PULL_OFF); - gpio_set_pin_function(mosi->number, mosi_pinmux); + setup_pin(mosi, mosi_pinmux); self->MOSI_pin = mosi->number; - claim_pin(mosi); } if (miso_none) { self->MISO_pin = NO_PIN; } else { - gpio_set_pin_direction(miso->number, GPIO_DIRECTION_IN); - gpio_set_pin_pull_mode(miso->number, GPIO_PULL_OFF); - gpio_set_pin_function(miso->number, miso_pinmux); + setup_pin(miso, miso_pinmux); self->MISO_pin = miso->number; - claim_pin(miso); } spi_m_sync_enable(&self->spi_desc); @@ -322,3 +317,11 @@ uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { void *hw = self->spi_desc.dev.prvt; return hri_sercomspi_get_CTRLA_CPOL_bit(hw); } + +void setup_pin(const mcu_pin_obj_t *pin, uint32_t pinmux) { + gpio_set_pin_direction(pin->number, GPIO_DIRECTION_OUT); + gpio_set_pin_pull_mode(pin->number, GPIO_PULL_OFF); + gpio_set_pin_function(pin->number, pinmux); + claim_pin(pin); + hri_port_set_PINCFG_DRVSTR_bit(PORT, (enum gpio_port)GPIO_PORT(pin->number), GPIO_PIN(pin->number)); +} diff --git a/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c b/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c index 05d5075e57862..a6d8fef0f394b 100644 --- a/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c +++ b/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c @@ -135,7 +135,7 @@ void common_hal_imagecapture_parallelimagecapture_singleshot_capture(imagecaptur mp_buffer_info_t bufinfo; mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_RW); - uint8_t dma_channel = dma_allocate_channel(); + uint8_t dma_channel = dma_allocate_channel(true); uint32_t *dest = bufinfo.buf; size_t count = bufinfo.len / 4; // PCC receives 4 bytes (2 pixels) at a time diff --git a/ports/atmel-samd/common-hal/spitarget/SPITarget.c b/ports/atmel-samd/common-hal/spitarget/SPITarget.c new file mode 100644 index 0000000000000..b9062911cc353 --- /dev/null +++ b/ports/atmel-samd/common-hal/spitarget/SPITarget.c @@ -0,0 +1,245 @@ +#include "common-hal/spitarget/SPITarget.h" +#include "common-hal/busio/__init__.h" + +#include "shared-bindings/spitarget/SPITarget.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "py/mperrno.h" +#include "py/runtime.h" + +#include "hpl_sercom_config.h" +#include "peripheral_clk_config.h" + +#include "hal/include/hal_gpio.h" +#include "hal/include/hal_spi_m_sync.h" + +#include "hpl_sercom_config.h" +#include "samd/sercom.h" + +void common_hal_spitarget_spi_target_construct(spitarget_spi_target_obj_t *self, + const mcu_pin_obj_t *sck, const mcu_pin_obj_t *mosi, + const mcu_pin_obj_t *miso, const mcu_pin_obj_t *ss) { + Sercom *sercom = NULL; + uint8_t sercom_index; + uint32_t clock_pinmux = 0; + uint32_t mosi_pinmux = 0; + uint32_t miso_pinmux = 0; + uint32_t ss_pinmux = 0; + uint8_t clock_pad = 0; + uint8_t mosi_pad = 0; + uint8_t miso_pad = 0; + uint8_t dopo = 255; + + // Ensure the object starts in its deinit state. + self->clock_pin = NO_PIN; + + // Special case for SAMR21 boards. (feather_radiofruit_zigbee) + #if defined(PIN_PC19F_SERCOM4_PAD0) + if (miso == &pin_PC19) { + if (mosi == &pin_PB30 && sck == &pin_PC18) { + sercom = SERCOM4; + sercom_index = 4; + clock_pinmux = MUX_F; + mosi_pinmux = MUX_F; + miso_pinmux = MUX_F; + clock_pad = 3; + mosi_pad = 2; + miso_pad = 0; + dopo = samd_peripherals_get_spi_dopo(clock_pad, mosi_pad); + } + // Error, leave SERCOM unset to throw an exception later. + } else + #endif + { + for (int i = 0; i < NUM_SERCOMS_PER_PIN; i++) { + sercom_index = sck->sercom[i].index; // 2 for SERCOM2, etc. + if (sercom_index >= SERCOM_INST_NUM) { + continue; + } + Sercom *potential_sercom = sercom_insts[sercom_index]; + if (potential_sercom->SPI.CTRLA.bit.ENABLE != 0) { + continue; + } + clock_pinmux = PINMUX(sck->number, (i == 0) ? MUX_C : MUX_D); + clock_pad = sck->sercom[i].pad; + if (!samd_peripherals_valid_spi_clock_pad(clock_pad)) { + continue; + } + // find miso_pad first, since it corresponds to dopo which takes limited values + for (int j = 0; j < NUM_SERCOMS_PER_PIN; j++) { + if (sercom_index == miso->sercom[j].index) { + miso_pinmux = PINMUX(miso->number, (j == 0) ? MUX_C : MUX_D); + miso_pad = miso->sercom[j].pad; + dopo = samd_peripherals_get_spi_dopo(clock_pad, miso_pad); + if (dopo > 0x3) { + continue; // pad combination not possible + } + } else { + continue; + } + for (int k = 0; k < NUM_SERCOMS_PER_PIN; k++) { + if (sercom_index == mosi->sercom[k].index) { + mosi_pinmux = PINMUX(mosi->number, (k == 0) ? MUX_C : MUX_D); + mosi_pad = mosi->sercom[k].pad; + for (int m = 0; m < NUM_SERCOMS_PER_PIN; m++) { + if (sercom_index == ss->sercom[m].index) { + ss_pinmux = PINMUX(ss->number, (m == 0) ? MUX_C : MUX_D); + sercom = potential_sercom; + break; + } + } + if (sercom != NULL) { + break; + } + } + } + if (sercom != NULL) { + break; + } + } + if (sercom != NULL) { + break; + } + } + } + if (sercom == NULL) { + raise_ValueError_invalid_pins(); + } + + // Set up SPI clocks on SERCOM. + samd_peripherals_sercom_clock_init(sercom, sercom_index); + + if (spi_m_sync_init(&self->spi_desc, sercom) != ERR_NONE) { + mp_raise_OSError(MP_EIO); + } + + // Pads must be set after spi_m_sync_init(), which uses default values from + // the prototypical SERCOM. + + hri_sercomspi_write_CTRLA_MODE_bf(sercom, 2); + hri_sercomspi_write_CTRLA_DOPO_bf(sercom, dopo); + hri_sercomspi_write_CTRLA_DIPO_bf(sercom, mosi_pad); + hri_sercomspi_write_CTRLB_PLOADEN_bit(sercom, 1); + + // Always start at 250khz which is what SD cards need. They are sensitive to + // SPI bus noise before they are put into SPI mode. + uint8_t baud_value = samd_peripherals_spi_baudrate_to_baud_reg_value(250000); + if (spi_m_sync_set_baudrate(&self->spi_desc, baud_value) != ERR_NONE) { + // spi_m_sync_set_baudrate does not check for validity, just whether the device is + // busy or not + mp_raise_OSError(MP_EIO); + } + + gpio_set_pin_direction(sck->number, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(sck->number, GPIO_PULL_OFF); + gpio_set_pin_function(sck->number, clock_pinmux); + claim_pin(sck); + self->clock_pin = sck->number; + + gpio_set_pin_direction(mosi->number, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(mosi->number, GPIO_PULL_OFF); + gpio_set_pin_function(mosi->number, mosi_pinmux); + self->MOSI_pin = mosi->number; + claim_pin(mosi); + + gpio_set_pin_direction(miso->number, GPIO_DIRECTION_OUT); + gpio_set_pin_pull_mode(miso->number, GPIO_PULL_OFF); + gpio_set_pin_function(miso->number, miso_pinmux); + self->MISO_pin = miso->number; + claim_pin(miso); + + gpio_set_pin_direction(ss->number, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(ss->number, GPIO_PULL_OFF); + gpio_set_pin_function(ss->number, ss_pinmux); + self->SS_pin = ss->number; + claim_pin(ss); + + self->running_dma.failure = 1; // not started + self->mosi_packet = NULL; + self->miso_packet = NULL; + + spi_m_sync_enable(&self->spi_desc); +} + +bool common_hal_spitarget_spi_target_deinited(spitarget_spi_target_obj_t *self) { + return self->clock_pin == NO_PIN; +} + +void common_hal_spitarget_spi_target_deinit(spitarget_spi_target_obj_t *self) { + if (common_hal_spitarget_spi_target_deinited(self)) { + return; + } + allow_reset_sercom(self->spi_desc.dev.prvt); + + spi_m_sync_disable(&self->spi_desc); + spi_m_sync_deinit(&self->spi_desc); + reset_pin_number(self->clock_pin); + reset_pin_number(self->MOSI_pin); + reset_pin_number(self->MISO_pin); + reset_pin_number(self->SS_pin); + self->clock_pin = NO_PIN; +} + +void common_hal_spitarget_spi_target_transfer_start(spitarget_spi_target_obj_t *self, + uint8_t *mosi_packet, const uint8_t *miso_packet, size_t len) { + if (len == 0) { + return; + } + if (self->running_dma.failure != 1) { + mp_raise_RuntimeError(MP_ERROR_TEXT("Async SPI transfer in progress on this bus, keep awaiting.")); + } + + self->mosi_packet = mosi_packet; + self->miso_packet = miso_packet; + + Sercom *sercom = self->spi_desc.dev.prvt; + self->running_dma = shared_dma_transfer_start(sercom, miso_packet, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, mosi_packet, len, 0); + + // There is an issue where if an unexpected SPI transfer is received before the user calls "end" for the in-progress, expected + // transfer, the SERCOM has an error and gets confused. This can be detected from INTFLAG.ERROR. I think the code in + // ports/atmel-samd/peripherals/samd/dma.c at line 277 (as of this commit; it's the part that reads s->SPI.INTFLAG.bit.RXC and + // s->SPI.DATA.reg) is supposed to fix this, but experimentation seems to show that it does not in fact fix anything. Anyways, if + // the ERROR bit is set, let's just reset the peripheral and then setup the transfer again -- that seems to work. + if (hri_sercomspi_get_INTFLAG_ERROR_bit(sercom)) { + shared_dma_transfer_close(self->running_dma); + + // disable the sercom + spi_m_sync_disable(&self->spi_desc); + hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK); + + // save configurations + hri_sercomspi_ctrla_reg_t ctrla_saved_val = hri_sercomspi_get_CTRLA_reg(sercom, -1); // -1 mask is all ones: save all bits + hri_sercomspi_ctrlb_reg_t ctrlb_saved_val = hri_sercomspi_get_CTRLB_reg(sercom, -1); // -1 mask is all ones: save all bits + hri_sercomspi_baud_reg_t baud_saved_val = hri_sercomspi_get_BAUD_reg(sercom, -1); // -1 mask is all ones: save all bits + // reset + hri_sercomspi_set_CTRLA_SWRST_bit(sercom); + hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK); + // re-write configurations + hri_sercomspi_write_CTRLA_reg(sercom, ctrla_saved_val); + hri_sercomspi_write_CTRLB_reg(sercom, ctrlb_saved_val); + hri_sercomspi_write_BAUD_reg(sercom, baud_saved_val); + hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK); + + // re-enable the sercom + spi_m_sync_enable(&self->spi_desc); + hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK); + + self->running_dma = shared_dma_transfer_start(sercom, miso_packet, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, mosi_packet, len, 0); + } +} + +bool common_hal_spitarget_spi_target_transfer_is_finished(spitarget_spi_target_obj_t *self) { + return self->running_dma.failure == 1 || shared_dma_transfer_finished(self->running_dma); +} + +int common_hal_spitarget_spi_target_transfer_close(spitarget_spi_target_obj_t *self) { + if (self->running_dma.failure == 1) { + return 0; + } + int res = shared_dma_transfer_close(self->running_dma); + self->running_dma.failure = 1; + + self->mosi_packet = NULL; + self->miso_packet = NULL; + + return res; +} diff --git a/ports/atmel-samd/common-hal/spitarget/SPITarget.h b/ports/atmel-samd/common-hal/spitarget/SPITarget.h new file mode 100644 index 0000000000000..50f2bcb33094b --- /dev/null +++ b/ports/atmel-samd/common-hal/spitarget/SPITarget.h @@ -0,0 +1,24 @@ +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_BUSIO_SPI_TARGET_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_BUSIO_SPI_TARGET_H + +#include "common-hal/microcontroller/Pin.h" +#include "hal/include/hal_spi_m_sync.h" +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + + struct spi_m_sync_descriptor spi_desc; + + uint8_t clock_pin; + uint8_t MOSI_pin; + uint8_t MISO_pin; + uint8_t SS_pin; + + uint8_t *mosi_packet; + const uint8_t *miso_packet; + + dma_descr_t running_dma; +} spitarget_spi_target_obj_t; + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_BUSIO_SPI_TARGET_H diff --git a/ports/atmel-samd/common-hal/spitarget/__init__.c b/ports/atmel-samd/common-hal/spitarget/__init__.c new file mode 100644 index 0000000000000..c0ce8cef9a164 --- /dev/null +++ b/ports/atmel-samd/common-hal/spitarget/__init__.c @@ -0,0 +1 @@ +// No spitarget module functions. diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index c3449485cf40e..3dde2885d1f68 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -49,6 +49,7 @@ CIRCUITPY_OS_GETENV ?= 0 CIRCUITPY_PIXELMAP ?= 0 CIRCUITPY_RE ?= 0 CIRCUITPY_SDCARDIO ?= 0 +CIRCUITPY_SPITARGET ?= 0 CIRCUITPY_SYNTHIO ?= 0 CIRCUITPY_TOUCHIO_USE_NATIVE ?= 1 CIRCUITPY_TRACEBACK ?= 0 @@ -118,6 +119,7 @@ CIRCUITPY_MAX3421E ?= $(HAS_1MB_FLASH) CIRCUITPY_PS2IO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_SAMD ?= 1 +CIRCUITPY_SPITARGET ?= 1 CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 CIRCUITPY_ULAB_OPTIMIZE_SIZE ?= 1 CIRCUITPY_WATCHDOG ?= 1 diff --git a/ports/atmel-samd/peripherals b/ports/atmel-samd/peripherals index 82e514b6e0d1a..d3210221bbd01 160000 --- a/ports/atmel-samd/peripherals +++ b/ports/atmel-samd/peripherals @@ -1 +1 @@ -Subproject commit 82e514b6e0d1a2b09dc73be9973663b6b837a817 +Subproject commit d3210221bbd018ae9d0183ea4640c42cf4bce672 diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index b497ea7b2cc97..a521b62fb4c30 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = true # Zephyr board has flash struct = true diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index 389201b795ea2..d8266a9b8515f 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = true # Zephyr board has flash struct = true diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 228df6e9c45e8..baacdff5dd1e7 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = true # Zephyr networking enabled +spitarget = false ssl = true # Zephyr networking enabled storage = true # Zephyr board has flash struct = true diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 7025cc3c1e470..31e979864ac1d 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = true # Zephyr board has flash struct = true diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 3134250e940b6..0f366c079bb6b 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = true # Zephyr board has flash struct = true diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 8b661139835cf..cb20c3e59300e 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = false struct = true diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index bc3031f1c47b0..c755838d554a2 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -83,6 +83,7 @@ sdcardio = false sdioio = false sharpdisplay = false socketpool = false +spitarget = false ssl = false storage = true # Zephyr board has flash struct = true diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index f4335f4b2ae5e..ed89ddd6daf9a 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -369,6 +369,9 @@ endif ifeq ($(CIRCUITPY_SOCKETPOOL),1) SRC_PATTERNS += socketpool/% endif +ifeq ($(CIRCUITPY_SPITARGET),1) +SRC_PATTERNS += spitarget/% +endif ifeq ($(CIRCUITPY_SSL),1) SRC_PATTERNS += ssl/% endif @@ -539,6 +542,8 @@ SRC_COMMON_HAL_ALL = \ socketpool/__init__.c \ socketpool/SocketPool.c \ socketpool/Socket.c \ + spitarget/SPITarget.c \ + spitarget/__init__.c \ usb_host/__init__.c \ usb_host/Port.c \ watchdog/WatchDogMode.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index c2c1773d25047..3a240ba1e1db7 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -508,6 +508,9 @@ CFLAGS += -DCIRCUITPY_SKIP_SAFE_MODE_WAIT=$(CIRCUITPY_SKIP_SAFE_MODE_WAIT) CIRCUITPY_SOCKETPOOL ?= $(CIRCUITPY_WIFI) CFLAGS += -DCIRCUITPY_SOCKETPOOL=$(CIRCUITPY_SOCKETPOOL) +CIRCUITPY_SPITARGET ?= 0 +CFLAGS += -DCIRCUITPY_SPITARGET=$(CIRCUITPY_SPITARGET) + CIRCUITPY_SOCKETPOOL_IPV6 ?= 0 CFLAGS += -DCIRCUITPY_SOCKETPOOL_IPV6=$(CIRCUITPY_SOCKETPOOL_IPV6) diff --git a/shared-bindings/_pixelmap/PixelMap.c b/shared-bindings/_pixelmap/PixelMap.c index 53f05c1c3bca7..9e3b9bb0a8da3 100644 --- a/shared-bindings/_pixelmap/PixelMap.c +++ b/shared-bindings/_pixelmap/PixelMap.c @@ -152,6 +152,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(pixelmap_pixelmap_indices_obj, pixelmap_pixelmap_indic //| //| @overload //| def __setitem__(self, index: slice, value: PixelSequence) -> None: ... +//| //| @overload //| def __setitem__(self, index: int, value: PixelType) -> None: //| """Sets the pixel value at the given index. Value can either be a tuple or integer. Tuples are diff --git a/shared-bindings/adafruit_pixelbuf/PixelBuf.c b/shared-bindings/adafruit_pixelbuf/PixelBuf.c index 1499ae956299a..cdffbfa627aa1 100644 --- a/shared-bindings/adafruit_pixelbuf/PixelBuf.c +++ b/shared-bindings/adafruit_pixelbuf/PixelBuf.c @@ -262,6 +262,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(pixelbuf_pixelbuf_fill_obj, pixelbuf_pixelbuf_f //| //| @overload //| def __setitem__(self, index: slice, value: PixelSequence) -> None: ... +//| //| @overload //| def __setitem__(self, index: int, value: PixelType) -> None: //| """Sets the pixel value at the given index. Value can either be a tuple or integer. Tuples are diff --git a/shared-bindings/busio/SPI.c b/shared-bindings/busio/SPI.c index f54c039658868..9fcfc0e87a0ff 100644 --- a/shared-bindings/busio/SPI.c +++ b/shared-bindings/busio/SPI.c @@ -92,7 +92,7 @@ static mp_obj_t busio_spi_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_clock, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_MOSI, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_MISO, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_half_duplex, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_bool = false} }, + { MP_QSTR_half_duplex, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -457,6 +457,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_get_frequency_obj, busio_spi_obj_get_frequen MP_PROPERTY_GETTER(busio_spi_frequency_obj, (mp_obj_t)&busio_spi_get_frequency_obj); + #endif // CIRCUITPY_BUSIO_SPI @@ -474,6 +475,7 @@ static const mp_rom_map_elem_t busio_spi_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&busio_spi_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&busio_spi_write_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&busio_spi_frequency_obj) } + #endif // CIRCUITPY_BUSIO_SPI }; static MP_DEFINE_CONST_DICT(busio_spi_locals_dict, busio_spi_locals_dict_table); diff --git a/shared-bindings/memorymap/AddressRange.c b/shared-bindings/memorymap/AddressRange.c index 9f15f86016d62..efc55ad37d775 100644 --- a/shared-bindings/memorymap/AddressRange.c +++ b/shared-bindings/memorymap/AddressRange.c @@ -134,6 +134,7 @@ static MP_DEFINE_CONST_DICT(memorymap_addressrange_locals_dict, memorymap_addres //| //| @overload //| def __setitem__(self, index: slice, value: ReadableBuffer) -> None: ... +//| //| @overload //| def __setitem__(self, index: int, value: int) -> None: //| """Set the value(s) at the given index. diff --git a/shared-bindings/spitarget/SPITarget.c b/shared-bindings/spitarget/SPITarget.c new file mode 100644 index 0000000000000..eca92d800277b --- /dev/null +++ b/shared-bindings/spitarget/SPITarget.c @@ -0,0 +1,189 @@ +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/spitarget/SPITarget.h" +#include "shared-bindings/time/__init__.h" +#include "shared-bindings/util.h" + +#include "shared/runtime/buffer_helper.h" +#include "shared/runtime/context_manager_helpers.h" +#include "shared/runtime/interrupt_char.h" + +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" + +//| class SPITarget: +//| """Serial Peripheral Interface protocol target""" +//| +//| def __init__( +//| self, +//| sck: microcontroller.Pin, +//| mosi: microcontroller.Pin, +//| miso: microcontroller.Pin, +//| ss: microcontroller.Pin, +//| ) -> None: +//| """SPI is a four-wire protocol for communicating between devices. +//| This implements the secondary (aka target or peripheral) side. +//| +//| :param ~microcontroller.Pin sck: The SPI clock pin +//| :param ~microcontroller.Pin mosi: The pin transferring data from the main to the secondary +//| :param ~microcontroller.Pin miso: The pin transferring data from the secondary to the main +//| :param ~microcontroller.Pin ss: The secondary selection pin""" +//| ... +//| +static mp_obj_t spitarget_spi_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + spitarget_spi_target_obj_t *self = mp_obj_malloc(spitarget_spi_target_obj_t, &spitarget_spi_target_type); + enum { ARG_sck, ARG_mosi, ARG_miso, ARG_ss }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sck, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_mosi, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_miso, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_ss, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *sck = validate_obj_is_free_pin(args[ARG_sck].u_obj, MP_QSTR_sck); + const mcu_pin_obj_t *mosi = validate_obj_is_free_pin(args[ARG_mosi].u_obj, MP_QSTR_mosi); + const mcu_pin_obj_t *miso = validate_obj_is_free_pin(args[ARG_miso].u_obj, MP_QSTR_miso); + const mcu_pin_obj_t *ss = validate_obj_is_free_pin(args[ARG_ss].u_obj, MP_QSTR_ss); + + common_hal_spitarget_spi_target_construct(self, sck, mosi, miso, ss); + return MP_OBJ_FROM_PTR(self); +} + +//| def deinit(self) -> None: +//| """Releases control of the underlying hardware so other classes can use it.""" +//| ... +//| +static mp_obj_t spitarget_spi_target_obj_deinit(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &spitarget_spi_target_type)); + spitarget_spi_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_spitarget_spi_target_deinit(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(spitarget_spi_target_deinit_obj, spitarget_spi_target_obj_deinit); + +//| def __enter__(self) -> SPITarget: +//| """No-op used in Context Managers.""" +//| ... +//| +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware on context exit. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| +static mp_obj_t spitarget_spi_target_obj___exit__(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_type(args[0], &spitarget_spi_target_target_type)); + spitarget_spi_target_obj_t *self = MP_OBJ_TO_PTR(args[0]); + common_hal_spitarget_spi_target_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spitarget_spi_target___exit___obj, 4, 4, spitarget_spi_target_obj___exit__); + +//| def load_packet(self, mosi_packet: bytearray, miso_packet: bytearray) -> None: +//| """Queue data for the next SPI transfer from the main device. +//| If a packet has already been queued for this SPI bus but has not yet been transferred, an error will be raised. +//| +//| :param bytearray miso_packet: Packet data to be sent from secondary to main on next request. +//| :param bytearray mosi_packet: Packet to be filled with data from main on next request. +//| """ +//| +static mp_obj_t spitarget_spi_target_load_packet(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_check_self(mp_obj_is_type(pos_args[0], &spitarget_spi_target_type)); + spitarget_spi_target_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + if (common_hal_spitarget_spi_target_deinited(self)) { + raise_deinited_error(); + } + enum { ARG_mosi_packet, ARG_miso_packet }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mosi_packet, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_miso_packet, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t mosi_bufinfo; + mp_get_buffer_raise(args[ARG_mosi_packet].u_obj, &mosi_bufinfo, MP_BUFFER_WRITE); + + mp_buffer_info_t miso_bufinfo; + mp_get_buffer_raise(args[ARG_miso_packet].u_obj, &miso_bufinfo, MP_BUFFER_READ); + + if (miso_bufinfo.len != mosi_bufinfo.len) { + mp_raise_ValueError(MP_ERROR_TEXT("Packet buffers for an SPI transfer must have the same length.")); + } + + common_hal_spitarget_spi_target_transfer_start(self, ((uint8_t *)mosi_bufinfo.buf), ((uint8_t *)miso_bufinfo.buf), mosi_bufinfo.len); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(spitarget_spi_target_load_packet_obj, 1, spitarget_spi_target_load_packet); + +//| def wait_transfer(self, *, timeout: float = -1) -> bool: +//| """Wait for an SPI transfer from the main device. +//| +//| :param float timeout: Timeout in seconds. Zero means wait forever, a negative value means check once +//| :return: True if the transfer is complete, or False if no response received before the timeout +//| """ +//| +//| +static mp_obj_t spitarget_spi_target_wait_transfer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_check_self(mp_obj_is_type(pos_args[0], &spitarget_spi_target_type)); + spitarget_spi_target_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + if (common_hal_spitarget_spi_target_deinited(self)) { + raise_deinited_error(); + } + enum { ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(-1)} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + #if MICROPY_PY_BUILTINS_FLOAT + float f = mp_obj_get_float(args[ARG_timeout].u_obj) * 1000; + int timeout_ms = (int)f; + #else + int timeout_ms = mp_obj_get_int(args[ARG_timeout].u_obj) * 1000; + #endif + + bool forever = false; + uint64_t timeout_end = 0; + if (timeout_ms == 0) { + forever = true; + } else if (timeout_ms > 0) { + timeout_end = common_hal_time_monotonic_ms() + timeout_ms; + } + + do { + if (common_hal_spitarget_spi_target_transfer_is_finished(self)) { + common_hal_spitarget_spi_target_transfer_close(self); // implicitly discards error indicator code + return mp_const_true; + } + mp_hal_delay_us(10); + } while (forever || common_hal_time_monotonic_ms() < timeout_end); + + return mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(spitarget_spi_target_wait_transfer_obj, 1, spitarget_spi_target_wait_transfer); + +static const mp_rom_map_elem_t spitarget_spi_target_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&spitarget_spi_target_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&spitarget_spi_target___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_load_packet), MP_ROM_PTR(&spitarget_spi_target_load_packet_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_transfer), MP_ROM_PTR(&spitarget_spi_target_wait_transfer_obj) }, + +}; + +static MP_DEFINE_CONST_DICT(spitarget_spi_target_locals_dict, spitarget_spi_target_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + spitarget_spi_target_type, + MP_QSTR_SPITarget, + MP_TYPE_FLAG_NONE, + make_new, spitarget_spi_target_make_new, + locals_dict, &spitarget_spi_target_locals_dict + ); diff --git a/shared-bindings/spitarget/SPITarget.h b/shared-bindings/spitarget/SPITarget.h new file mode 100644 index 0000000000000..a8501976a7f9a --- /dev/null +++ b/shared-bindings/spitarget/SPITarget.h @@ -0,0 +1,21 @@ +#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_BUSIO_SPI_TARGET_H +#define MICROPY_INCLUDED_SHARED_BINDINGS_BUSIO_SPI_TARGET_H + +#include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/spitarget/SPITarget.h" + +extern const mp_obj_type_t spitarget_spi_target_type; + +extern void common_hal_spitarget_spi_target_construct(spitarget_spi_target_obj_t *self, + const mcu_pin_obj_t *sck, const mcu_pin_obj_t *mosi, const mcu_pin_obj_t *miso, const mcu_pin_obj_t *ss); +extern void common_hal_spitarget_spi_target_deinit(spitarget_spi_target_obj_t *self); +extern bool common_hal_spitarget_spi_target_deinited(spitarget_spi_target_obj_t *self); + +extern void common_hal_spitarget_spi_target_transfer_start(spitarget_spi_target_obj_t *self, + uint8_t *mosi_packet, const uint8_t *miso_packet, size_t len); +extern bool common_hal_spitarget_spi_target_transfer_is_finished(spitarget_spi_target_obj_t *self); +extern int common_hal_spitarget_spi_target_transfer_close(spitarget_spi_target_obj_t *self); + +#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BUSIO_SPI_TARGET_H diff --git a/shared-bindings/spitarget/__init__.c b/shared-bindings/spitarget/__init__.c new file mode 100644 index 0000000000000..0fcf90ea34be6 --- /dev/null +++ b/shared-bindings/spitarget/__init__.c @@ -0,0 +1,89 @@ +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/spitarget/SPITarget.h" + +#include "py/runtime.h" + +//| """Serial Peripheral Interface protocol target +//| +//| The `spitarget` module contains classes to support an SPI target. +//| +//| Example that emulates an SPI analog-to-digital converter:: +//| +//| import board +//| import analogio +//| from spitarget import SPITarget +//| +//| ain0 = analogio.AnalogIn(board.A0) +//| ain1 = analogio.AnalogIn(board.A1) +//| selected_channel = ain0 +//| +//| def map_adc_channel(index): +//| return ain0 if (index == 0) else ain1 +//| +//| mosi_buffer = bytearray(2) +//| miso_buffer = bytearray(2) +//| with SPITarget(sck=board.D12, mosi=board.D13, miso=board.D11, ss=board.D10) as device: +//| while True: +//| # Convert analog signal to array of bytes +//| reading = selected_channel.value +//| miso_buffer[0] = (reading >> 8) & 0xFF +//| miso_buffer[1] = (reading) & 0xFF +//| # Send array of bytes over SPI to main +//| device.load_packet(mosi_buffer, miso_buffer) +//| while not device.wait_transfer(timeout=-1): +//| pass +//| # Handle command from main, which sets the ADC channel +//| selected_channel = map_adc_channel((mosi_buffer[0] << 8) | mosi_buffer[1]) +//| +//| Communicating with the ADC emulator from the REPL of an attached CircuitPython board might look like :: +//| +//| >>> import board +//| >>> import digitalio +//| >>> import busio +//| >>> import time +//| >>> +//| >>> ## setup +//| >>> spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +//| >>> cs = digitalio.DigitalInOut(board.CS) +//| >>> cs.direction = digitalio.Direction.OUTPUT +//| >>> cs.value = True +//| >>> spi.try_lock() +//| True +//| >>> +//| >>> ## ADC command: read from A0 +//| >>> cs.value = False +//| >>> spi.write(bytearray([0, 0])) +//| >>> cs.value = True +//| >>> +//| >>> # wait for ADC to read a value +//| >>> +//| >>> ## get two-byte output from ADC +//| >>> adc_result = bytearray(2) +//| >>> cs.value = False +//| >>> spi.readinto(adc_result, write_value=1) +//| >>> cs.value = True +//| >>> list(adc_result) +//| [0, 255] +//| +//| """ + + + +static const mp_rom_map_elem_t spitarget_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_spitarget) }, + { MP_ROM_QSTR(MP_QSTR_SPITarget), MP_ROM_PTR(&spitarget_spi_target_type) }, +}; + +static MP_DEFINE_CONST_DICT(spitarget_module_globals, spitarget_module_globals_table); + +const mp_obj_module_t spitarget_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&spitarget_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_spitarget, spitarget_module);