From 1e574aad8a62981aaf6f403d1054c02e5fdef651 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Tue, 24 Feb 2026 09:36:28 -0800 Subject: [PATCH 1/4] Busio: Added DMA functions for I2C, SPI, and UART Implemented and tested on rp2040 port --- ports/raspberrypi/common-hal/busio/dma.c | 396 +++++++++++++++++++++++ ports/raspberrypi/common-hal/busio/dma.h | 24 ++ ports/raspberrypi/mpconfigport.mk | 1 + py/circuitpy_defns.mk | 1 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/busio/I2C.c | 119 ++++++- shared-bindings/busio/SPI.c | 122 ++++++- shared-bindings/busio/UART.c | 53 +++ shared-bindings/busio/dma.c | 11 + shared-bindings/busio/dma.h | 30 ++ 10 files changed, 746 insertions(+), 14 deletions(-) create mode 100644 ports/raspberrypi/common-hal/busio/dma.c create mode 100644 ports/raspberrypi/common-hal/busio/dma.h create mode 100644 shared-bindings/busio/dma.c create mode 100644 shared-bindings/busio/dma.h diff --git a/ports/raspberrypi/common-hal/busio/dma.c b/ports/raspberrypi/common-hal/busio/dma.c new file mode 100644 index 0000000000000..726d9d391ddd7 --- /dev/null +++ b/ports/raspberrypi/common-hal/busio/dma.c @@ -0,0 +1,396 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/busio/dma.h" + +#include "hardware/dma.h" +#include "hardware/i2c.h" +#include "hardware/spi.h" +#include "hardware/uart.h" + +#include + +#include "py/runtime.h" +#include "supervisor/port.h" + +typedef struct { + uint paired_dma_channel; + bool pending_last_command; + i2c_inst_t *i2c; + uint8_t *tx_copy; + uint32_t repeated_command; + uint32_t last_command; +} i2c_dma_channel_state_t; + +typedef struct { + uint paired_rx_channel; + uint8_t repeated_tx_data; + uint8_t discard_rx_data; +} spi_dma_channel_state_t; + +static i2c_dma_channel_state_t i2c_dma_channel_state[NUM_DMA_CHANNELS]; +static spi_dma_channel_state_t spi_dma_channel_state[NUM_DMA_CHANNELS]; + +// Addresses of the form 000 0xxx or 111 1xxx are reserved. No slave should +// have these addresses. +#define i2c_reserved_addr(addr) (((addr) & 0x78) == 0 || ((addr) & 0x78) == 0x78) + +static uint common_hal_i2c_write_dma(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) { + invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses + invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); + invalid_params_if(HARDWARE_I2C, len == 0); + invalid_params_if(HARDWARE_I2C, ((int)len) < 0); + + uint dma_channel = dma_claim_unused_channel(true); + + i2c->hw->enable = 0; + i2c->hw->tar = addr; + i2c->hw->enable = 1; + + i2c_dma_channel_state[dma_channel].paired_dma_channel = NUM_DMA_CHANNELS; + i2c_dma_channel_state[dma_channel].pending_last_command = false; + i2c_dma_channel_state[dma_channel].i2c = i2c; + i2c_dma_channel_state[dma_channel].tx_copy = NULL; + i2c_dma_channel_state[dma_channel].repeated_command = 0; + i2c_dma_channel_state[dma_channel].last_command = 0; + + if (len == 1) { + uint32_t command = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + src[0]; + i2c->hw->data_cmd = command; + i2c_dma_channel_state[dma_channel].last_command = command; + i2c->restart_on_next = nostop; + return dma_channel; + } + + i2c->hw->data_cmd = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + src[0]; + + size_t middle_len = len - 2; + if (middle_len > 0) { + i2c_dma_channel_state[dma_channel].tx_copy = port_malloc(middle_len, true); + if (i2c_dma_channel_state[dma_channel].tx_copy == NULL) { + dma_channel_unclaim(dma_channel); + m_malloc_fail(middle_len); + } + memcpy(i2c_dma_channel_state[dma_channel].tx_copy, src + 1, middle_len); + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, false); + channel_config_set_dreq(&config, I2C_DREQ_NUM(i2c, true)); + + dma_channel_configure(dma_channel, &config, &i2c->hw->data_cmd, + i2c_dma_channel_state[dma_channel].tx_copy, middle_len, true); + } + + i2c_dma_channel_state[dma_channel].pending_last_command = true; + i2c_dma_channel_state[dma_channel].last_command = + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + src[len - 1]; + i2c->restart_on_next = nostop; + return dma_channel; +} + +static uint common_hal_i2c_read_dma(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) { + invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses + invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); + invalid_params_if(HARDWARE_I2C, len == 0); + invalid_params_if(HARDWARE_I2C, ((int)len) < 0); + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + + i2c->hw->enable = 0; + i2c->hw->tar = addr; + i2c->hw->enable = 1; + + i2c_dma_channel_state[tx_dma_channel].paired_dma_channel = rx_dma_channel; + i2c_dma_channel_state[tx_dma_channel].pending_last_command = false; + i2c_dma_channel_state[tx_dma_channel].i2c = i2c; + i2c_dma_channel_state[tx_dma_channel].tx_copy = NULL; + i2c_dma_channel_state[tx_dma_channel].repeated_command = 0; + i2c_dma_channel_state[tx_dma_channel].last_command = 0; + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, I2C_DREQ_NUM(i2c, false)); + + dma_channel_configure(rx_dma_channel, &rx_config, dst, &i2c->hw->data_cmd, len, true); + + uint32_t first_command = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + I2C_IC_DATA_CMD_CMD_BITS; + uint32_t middle_command = I2C_IC_DATA_CMD_CMD_BITS; + uint32_t last_command = + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + I2C_IC_DATA_CMD_CMD_BITS; + + if (len == 1) { + uint32_t command = first_command | last_command; + i2c->hw->data_cmd = command; + i2c_dma_channel_state[tx_dma_channel].last_command = command; + } else { + i2c->hw->data_cmd = first_command; + + size_t middle_len = len - 2; + if (middle_len > 0) { + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_32); + channel_config_set_read_increment(&tx_config, false); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, I2C_DREQ_NUM(i2c, true)); + + i2c_dma_channel_state[tx_dma_channel].repeated_command = middle_command; + + dma_channel_configure(tx_dma_channel, &tx_config, &i2c->hw->data_cmd, + &i2c_dma_channel_state[tx_dma_channel].repeated_command, middle_len, true); + } + + i2c_dma_channel_state[tx_dma_channel].pending_last_command = true; + i2c_dma_channel_state[tx_dma_channel].last_command = last_command; + } + + i2c->restart_on_next = nostop; + return tx_dma_channel; +} + +static bool common_hal_i2c_dma_is_busy(uint dma_channel) { + if (dma_channel_is_busy(dma_channel)) { + return true; + } + + i2c_dma_channel_state_t *state = &i2c_dma_channel_state[dma_channel]; + if (state->pending_last_command) { + if (!i2c_get_write_available(state->i2c)) { + return true; + } + + state->i2c->hw->data_cmd = state->last_command; + state->pending_last_command = false; + return true; + } + + uint paired_dma_channel = state->paired_dma_channel; + if (paired_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(paired_dma_channel)) { + return true; + } + + if (paired_dma_channel < NUM_DMA_CHANNELS) { + dma_channel_unclaim(paired_dma_channel); + state->paired_dma_channel = NUM_DMA_CHANNELS; + } + + if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_TX_EMPTY_BITS)) { + return true; + } + + if (state->last_command & I2C_IC_DATA_CMD_STOP_BITS) { + if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_STOP_DET_BITS)) { + return true; + } + state->i2c->hw->clr_stop_det; + } + + if (state->tx_copy != NULL) { + port_free(state->tx_copy); + state->tx_copy = NULL; + } + + state->i2c = NULL; + + dma_channel_unclaim(dma_channel); + return false; +} + +static uint common_hal_spi_write_dma(spi_inst_t *spi, const uint8_t *src, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + + spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; + spi_dma_channel_state[tx_dma_channel].discard_rx_data = 0; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, true); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, false); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, &spi_dma_channel_state[tx_dma_channel].discard_rx_data, + &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return tx_dma_channel; +} + +static uint common_hal_spi_read_dma(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + + spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; + spi_dma_channel_state[tx_dma_channel].repeated_tx_data = repeated_tx_data; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, false); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, + &spi_dma_channel_state[tx_dma_channel].repeated_tx_data, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return tx_dma_channel; +} + +static uint common_hal_spi_write_read_dma(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + + spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, true); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return tx_dma_channel; +} + +static bool common_hal_spi_dma_is_busy(uint dma_channel) { + if (dma_channel_is_busy(dma_channel)) { + return true; + } + + uint rx_dma_channel = spi_dma_channel_state[dma_channel].paired_rx_channel; + if (rx_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(rx_dma_channel)) { + return true; + } + + if (rx_dma_channel < NUM_DMA_CHANNELS) { + dma_channel_unclaim(rx_dma_channel); + } + dma_channel_unclaim(dma_channel); + + spi_dma_channel_state[dma_channel].paired_rx_channel = NUM_DMA_CHANNELS; + return false; +} + +static uint common_hal_uart_write_dma(uart_inst_t *uart, const uint8_t *src, size_t len) { + invalid_params_if(HARDWARE_UART, 0 > (int)len); + invalid_params_if(HARDWARE_UART, len == 0); + + uint dma_channel = dma_claim_unused_channel(true); + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, false); + channel_config_set_dreq(&config, UART_DREQ_NUM(uart, true)); + + dma_channel_configure(dma_channel, &config, &uart_get_hw(uart)->dr, src, len, true); + return dma_channel; +} + +static uint common_hal_uart_read_dma(uart_inst_t *uart, uint8_t *dst, size_t len) { + invalid_params_if(HARDWARE_UART, 0 > (int)len); + invalid_params_if(HARDWARE_UART, len == 0); + + uint dma_channel = dma_claim_unused_channel(true); + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_read_increment(&config, false); + channel_config_set_write_increment(&config, true); + channel_config_set_dreq(&config, UART_DREQ_NUM(uart, false)); + + dma_channel_configure(dma_channel, &config, dst, &uart_get_hw(uart)->dr, len, true); + return dma_channel; +} + +static bool common_hal_uart_dma_is_busy(uint dma_channel) { + if (dma_channel_is_busy(dma_channel)) { + return true; + } + dma_channel_unclaim(dma_channel); + return false; +} + +uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop) { + return common_hal_i2c_read_dma(i2c->peripheral, address, data, len, nostop); +} + +uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop) { + return common_hal_i2c_write_dma(i2c->peripheral, address, data, len, nostop); +} + +bool common_hal_busio_dma_i2c_is_busy(uint dma_channel) { + return common_hal_i2c_dma_is_busy(dma_channel); +} + +uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len) { + return common_hal_spi_write_dma(spi->peripheral, data, len); +} + +uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len) { + return common_hal_spi_read_dma(spi->peripheral, write_value, data, len); +} + +uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len) { + return common_hal_spi_write_read_dma(spi->peripheral, out_data, in_data, len); +} + +bool common_hal_busio_dma_spi_is_busy(uint dma_channel) { + return common_hal_spi_dma_is_busy(dma_channel); +} + +uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len) { + return common_hal_uart_read_dma(uart->uart, data, len); +} + +uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len) { + return common_hal_uart_write_dma(uart->uart, data, len); +} + +bool common_hal_busio_dma_uart_is_busy(uint dma_channel) { + return common_hal_uart_dma_is_busy(dma_channel); +} diff --git a/ports/raspberrypi/common-hal/busio/dma.h b/ports/raspberrypi/common-hal/busio/dma.h new file mode 100644 index 0000000000000..f540210f9ecc4 --- /dev/null +++ b/ports/raspberrypi/common-hal/busio/dma.h @@ -0,0 +1,24 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/busio/I2C.h" +#include "common-hal/busio/SPI.h" +#include "common-hal/busio/UART.h" + +uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); +uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); +bool common_hal_busio_dma_i2c_is_busy(uint dma_channel); + +uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len); +uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len); +uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); +bool common_hal_busio_dma_spi_is_busy(uint dma_channel); + +uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len); +uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); +bool common_hal_busio_dma_uart_is_busy(uint dma_channel); diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 8401c5d75453a..b3873f5064fe3 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -44,6 +44,7 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOBUSIO ?= 1 CIRCUITPY_AUDIOCORE ?= 1 CIRCUITPY_AUDIOPWMIO ?= 1 +CIRCUITPY_BUSIO_DMA ?= 1 CIRCUITPY_AUDIOMIXER ?= 1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index d8c95d18a79b9..64f6d4ac8a0f1 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -501,6 +501,7 @@ SRC_COMMON_HAL_ALL = \ audiopwmio/PWMAudioOut.c \ audiopwmio/__init__.c \ board/__init__.c \ + busio/dma.c \ busio/I2C.c \ busio/SPI.c \ busio/UART.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 56fc695742133..2ce0210b6b7d5 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -220,6 +220,9 @@ CFLAGS += -DCIRCUITPY_BUSIO_SPI=$(CIRCUITPY_BUSIO_SPI) CIRCUITPY_BUSIO_UART ?= $(CIRCUITPY_BUSIO) CFLAGS += -DCIRCUITPY_BUSIO_UART=$(CIRCUITPY_BUSIO_UART) +CIRCUITPY_BUSIO_DMA ?= 0 +CFLAGS += -DCIRCUITPY_BUSIO_DMA=$(CIRCUITPY_BUSIO_DMA) + CIRCUITPY_CAMERA ?= 0 CFLAGS += -DCIRCUITPY_CAMERA=$(CIRCUITPY_CAMERA) diff --git a/shared-bindings/busio/I2C.c b/shared-bindings/busio/I2C.c index a2724e60c3422..61ac6a3acb427 100644 --- a/shared-bindings/busio/I2C.c +++ b/shared-bindings/busio/I2C.c @@ -9,6 +9,7 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/busio/I2C.h" +#include "shared-bindings/busio/dma.h" #include "shared-bindings/util.h" #include "shared/runtime/buffer_helper.h" @@ -92,6 +93,14 @@ static void check_for_deinit(busio_i2c_obj_t *self) { } } +static busio_i2c_obj_t *native_i2c(mp_obj_t i2c_obj) { + mp_obj_t native_i2c = mp_obj_cast_to_native_base(i2c_obj, MP_OBJ_FROM_PTR(&busio_i2c_type)); + if (native_i2c == MP_OBJ_NULL) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Must be a %q subclass."), MP_QSTR_I2C); + } + return MP_OBJ_TO_PTR(native_i2c); +} + //| def __enter__(self) -> I2C: //| """No-op used in Context Managers.""" //| ... @@ -112,6 +121,97 @@ static void check_lock(busio_i2c_obj_t *self) { } } +#if CIRCUITPY_BUSIO_DMA +//| def dma_readinto(self, address: int, buffer: WriteableBuffer, *, end: bool = False) -> int: +//| """Start a DMA I2C read into ``buffer`` and return the DMA channel. +//| +//| The I2C object must be locked before calling. +//| +//| :param int address: 7-bit I2C target address +//| :param ~circuitpython_typing.WriteableBuffer buffer: destination buffer +//| :param bool end: If ``True``, send a STOP condition at the end of the transfer +//| :return: DMA channel used by this transfer +//| :rtype: int +//| """ +//| +static mp_obj_t busio_i2c_dma_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address, ARG_buffer, ARG_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + busio_i2c_obj_t *self = native_i2c(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + 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_int_t address = args[ARG_address].u_int; + mp_arg_validate_int_range(address, 0, 0x7f, MP_QSTR_address); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + uint dma_channel = common_hal_busio_dma_i2c_read(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_dma_read_obj, 1, busio_i2c_dma_read); + +//| def dma_write(self, address: int, buffer: ReadableBuffer, *, end: bool = False) -> int: +//| """Start a DMA I2C write from ``buffer`` and return the DMA channel. +//| +//| The I2C object must be locked before calling. +//| +//| :param int address: 7-bit I2C target address +//| :param ~circuitpython_typing.ReadableBuffer buffer: source buffer +//| :param bool end: If ``True``, send a STOP condition at the end of the transfer +//| :return: DMA channel used by this transfer +//| :rtype: int +//| """ +//| +static mp_obj_t busio_i2c_dma_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address, ARG_buffer, ARG_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + busio_i2c_obj_t *self = native_i2c(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + 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_int_t address = args[ARG_address].u_int; + mp_arg_validate_int_range(address, 0, 0x7f, MP_QSTR_address); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + + uint dma_channel = common_hal_busio_dma_i2c_write(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_dma_write_obj, 1, busio_i2c_dma_write); + +//| def dma_is_busy(self, dma_channel: int) -> bool: +//| """Return ``True`` while the I2C DMA channel is active. +//| +//| :param int dma_channel: DMA channel returned by `dma_readinto` or `dma_write` +//| """ +//| +static mp_obj_t busio_i2c_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { + busio_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_busio_dma_i2c_is_busy(mp_obj_get_int(dma_channel_obj))); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_dma_is_busy_obj, busio_i2c_dma_is_busy); +#endif + //| def probe(self, address: int) -> List[int]: //| """Check if a device at the specified address responds. //| @@ -121,7 +221,7 @@ static void check_lock(busio_i2c_obj_t *self) { //| ... //| static mp_obj_t busio_i2c_probe(mp_obj_t self_in, mp_obj_t address_obj) { - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); check_lock(self); @@ -139,7 +239,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_probe_obj, busio_i2c_probe); //| ... //| static mp_obj_t busio_i2c_scan(mp_obj_t self_in) { - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); check_lock(self); mp_obj_t list = mp_obj_new_list(0, NULL); @@ -162,7 +262,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(busio_i2c_scan_obj, busio_i2c_scan); //| ... //| static mp_obj_t busio_i2c_obj_try_lock(mp_obj_t self_in) { - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); return mp_obj_new_bool(common_hal_busio_i2c_try_lock(self)); } @@ -173,7 +273,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(busio_i2c_try_lock_obj, busio_i2c_obj_try_lock); //| ... //| static mp_obj_t busio_i2c_obj_unlock(mp_obj_t self_in) { - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); common_hal_busio_i2c_unlock(self); return mp_const_none; @@ -206,7 +306,7 @@ static mp_obj_t busio_i2c_readfrom_into(size_t n_args, const mp_obj_t *pos_args, { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, }; - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_i2c_obj_t *self = native_i2c(pos_args[0]); check_for_deinit(self); check_lock(self); @@ -268,7 +368,7 @@ static mp_obj_t busio_i2c_writeto(size_t n_args, const mp_obj_t *pos_args, mp_ma { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, }; - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_i2c_obj_t *self = native_i2c(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -348,7 +448,7 @@ static mp_obj_t busio_i2c_writeto_then_readfrom(size_t n_args, const mp_obj_t *p { MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, }; - busio_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_i2c_obj_t *self = native_i2c(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -403,6 +503,11 @@ static const mp_rom_map_elem_t busio_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readfrom_into), MP_ROM_PTR(&busio_i2c_readfrom_into_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&busio_i2c_writeto_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto_then_readfrom), MP_ROM_PTR(&busio_i2c_writeto_then_readfrom_obj) }, + #if CIRCUITPY_BUSIO_DMA + { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_i2c_dma_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_i2c_dma_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_i2c_dma_is_busy_obj) }, + #endif #endif // CIRCUITPY_BUSIO_I2C }; diff --git a/shared-bindings/busio/SPI.c b/shared-bindings/busio/SPI.c index 24c02a16e634b..f83d5fa9c990a 100644 --- a/shared-bindings/busio/SPI.c +++ b/shared-bindings/busio/SPI.c @@ -11,6 +11,7 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/busio/SPI.h" +#include "shared-bindings/busio/dma.h" #include "shared-bindings/util.h" #include "shared/runtime/buffer_helper.h" @@ -152,6 +153,107 @@ static void check_for_deinit(busio_spi_obj_t *self) { } } +static busio_spi_obj_t *native_spi(mp_obj_t spi_obj) { + mp_obj_t native_spi = mp_obj_cast_to_native_base(spi_obj, MP_OBJ_FROM_PTR(&busio_spi_type)); + if (native_spi == MP_OBJ_NULL) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Must be a %q subclass."), MP_QSTR_SPI); + } + return MP_OBJ_TO_PTR(native_spi); +} + +#if CIRCUITPY_BUSIO_DMA +//| def dma_write(self, buffer: ReadableBuffer) -> int: +//| """Start a DMA SPI write from ``buffer`` and return the DMA channel. +//| +//| The SPI object must be locked before calling. +//| """ +//| +static mp_obj_t busio_spi_dma_write(mp_obj_t self_in, mp_obj_t buffer_obj) { + busio_spi_obj_t *self = native_spi(self_in); + check_for_deinit(self); + check_lock(self); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); + + uint dma_channel = common_hal_busio_dma_spi_write(self, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_dma_write_obj, busio_spi_dma_write); + +//| def dma_readinto(self, buffer: WriteableBuffer, *, write_value: int = 0) -> int: +//| """Start a DMA SPI read into ``buffer`` and return the DMA channel. +//| +//| ``write_value`` is transmitted while reading. +//| The SPI object must be locked before calling. +//| """ +//| +static mp_obj_t busio_spi_dma_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_write_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_write_value, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + busio_spi_obj_t *self = native_spi(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + 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_int_t write_value = args[ARG_write_value].u_int; + mp_arg_validate_int_range(write_value, 0, 0xff, MP_QSTR_write_value); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + uint dma_channel = common_hal_busio_dma_spi_read(self, write_value, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_dma_readinto_obj, 1, busio_spi_dma_readinto); + +//| def dma_write_readinto(self, out_buffer: ReadableBuffer, in_buffer: WriteableBuffer) -> int: +//| """Start a DMA SPI write/read transfer and return the DMA channel. +//| +//| ``out_buffer`` and ``in_buffer`` must have the same length. +//| The SPI object must be locked before calling. +//| """ +//| +static mp_obj_t busio_spi_dma_write_readinto(mp_obj_t self_in, mp_obj_t out_buffer_obj, mp_obj_t in_buffer_obj) { + busio_spi_obj_t *self = native_spi(self_in); + check_for_deinit(self); + check_lock(self); + + mp_buffer_info_t out_bufinfo; + mp_get_buffer_raise(out_buffer_obj, &out_bufinfo, MP_BUFFER_READ); + + mp_buffer_info_t in_bufinfo; + mp_get_buffer_raise(in_buffer_obj, &in_bufinfo, MP_BUFFER_WRITE); + + if (out_bufinfo.len != in_bufinfo.len) { + mp_raise_ValueError(MP_ERROR_TEXT("buffers must be same length")); + } + + uint dma_channel = common_hal_busio_dma_spi_transfer(self, out_bufinfo.buf, in_bufinfo.buf, out_bufinfo.len); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_3(busio_spi_dma_write_readinto_obj, busio_spi_dma_write_readinto); + +//| def dma_is_busy(self, dma_channel: int) -> bool: +//| """Return ``True`` while the SPI DMA channel is active. +//| +//| :param int dma_channel: DMA channel returned by SPI DMA transfer functions +//| """ +//| +static mp_obj_t busio_spi_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { + busio_spi_obj_t *self = native_spi(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_busio_dma_spi_is_busy(mp_obj_get_int(dma_channel_obj))); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_dma_is_busy_obj, busio_spi_dma_is_busy); +#endif + //| def configure( //| self, *, baudrate: int = 100000, polarity: int = 0, phase: int = 0, bits: int = 8 //| ) -> None: @@ -186,7 +288,7 @@ static mp_obj_t busio_spi_configure(size_t n_args, const mp_obj_t *pos_args, mp_ { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, }; - busio_spi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_spi_obj_t *self = native_spi(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -213,7 +315,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_configure_obj, 1, busio_spi_configure); //| static mp_obj_t busio_spi_obj_try_lock(mp_obj_t self_in) { - busio_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_spi_obj_t *self = native_spi(self_in); return mp_obj_new_bool(common_hal_busio_spi_try_lock(self)); } MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_try_lock_obj, busio_spi_obj_try_lock); @@ -224,7 +326,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_try_lock_obj, busio_spi_obj_try_lock); //| static mp_obj_t busio_spi_obj_unlock(mp_obj_t self_in) { - busio_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); common_hal_busio_spi_unlock(self); return mp_const_none; @@ -255,7 +357,7 @@ static mp_obj_t busio_spi_write(size_t n_args, const mp_obj_t *pos_args, mp_map_ { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, }; - busio_spi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_spi_obj_t *self = native_spi(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -322,7 +424,7 @@ static mp_obj_t busio_spi_readinto(size_t n_args, const mp_obj_t *pos_args, mp_m { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, { MP_QSTR_write_value, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; - busio_spi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_spi_obj_t *self = native_spi(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -399,7 +501,7 @@ static mp_obj_t busio_spi_write_readinto(size_t n_args, const mp_obj_t *pos_args { MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, }; - busio_spi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + busio_spi_obj_t *self = native_spi(pos_args[0]); check_for_deinit(self); check_lock(self); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -451,7 +553,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_write_readinto_obj, 1, busio_spi_write_read //| static mp_obj_t busio_spi_obj_get_frequency(mp_obj_t self_in) { - busio_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); return MP_OBJ_NEW_SMALL_INT(common_hal_busio_spi_get_frequency(self)); } @@ -477,6 +579,12 @@ static const mp_rom_map_elem_t busio_spi_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&busio_spi_readinto_obj) }, { 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) }, + #if CIRCUITPY_BUSIO_DMA + { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_spi_dma_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_spi_dma_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_write_readinto), MP_ROM_PTR(&busio_spi_dma_write_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_spi_dma_is_busy_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&busio_spi_frequency_obj) } #endif // CIRCUITPY_BUSIO_SPI diff --git a/shared-bindings/busio/UART.c b/shared-bindings/busio/UART.c index 4714c3ee0733a..2855b353c5fd2 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -7,6 +7,7 @@ #include #include "shared-bindings/busio/UART.h" +#include "shared-bindings/busio/dma.h" #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/util.h" @@ -175,6 +176,53 @@ static busio_uart_obj_t *native_uart(mp_obj_t uart_obj) { return MP_OBJ_TO_PTR(native_uart); } +static void check_for_deinit(busio_uart_obj_t *self); + +#if CIRCUITPY_BUSIO_DMA +//| def dma_readinto(self, buffer: WriteableBuffer) -> int: +//| """Start a DMA UART read into ``buffer`` and return the DMA channel.""" +//| +static mp_obj_t busio_uart_dma_readinto(mp_obj_t self_in, mp_obj_t buffer_obj) { + busio_uart_obj_t *self = native_uart(self_in); + check_for_deinit(self); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_WRITE); + + uint dma_channel = common_hal_busio_dma_uart_read(self, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_readinto_obj, busio_uart_dma_readinto); + +//| def dma_write(self, buffer: ReadableBuffer) -> int: +//| """Start a DMA UART write from ``buffer`` and return the DMA channel.""" +//| +static mp_obj_t busio_uart_dma_write(mp_obj_t self_in, mp_obj_t buffer_obj) { + busio_uart_obj_t *self = native_uart(self_in); + check_for_deinit(self); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); + + uint dma_channel = common_hal_busio_dma_uart_write(self, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_uint(dma_channel); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_write_obj, busio_uart_dma_write); + +//| def dma_is_busy(self, dma_channel: int) -> bool: +//| """Return ``True`` while the UART DMA channel is active. +//| +//| :param int dma_channel: DMA channel returned by `dma_readinto` or `dma_write` +//| """ +//| +static mp_obj_t busio_uart_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { + busio_uart_obj_t *self = native_uart(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_busio_dma_uart_is_busy(mp_obj_get_int(dma_channel_obj))); +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_is_busy_obj, busio_uart_dma_is_busy); +#endif + //| def deinit(self) -> None: //| """Deinitialises the UART and releases any hardware resources for reuse.""" @@ -424,6 +472,11 @@ static const mp_rom_map_elem_t busio_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + #if CIRCUITPY_BUSIO_DMA + { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_uart_dma_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_uart_dma_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_uart_dma_is_busy_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&busio_uart_reset_input_buffer_obj) }, diff --git a/shared-bindings/busio/dma.c b/shared-bindings/busio/dma.c new file mode 100644 index 0000000000000..eab511d05db9e --- /dev/null +++ b/shared-bindings/busio/dma.c @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/busio/dma.h" + +#if CIRCUITPY_BUSIO_DMA +// DMA APIs are exposed as methods on busio.I2C, busio.SPI, and busio.UART. +#endif diff --git a/shared-bindings/busio/dma.h b/shared-bindings/busio/dma.h new file mode 100644 index 0000000000000..134f76a8681b6 --- /dev/null +++ b/shared-bindings/busio/dma.h @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +#if CIRCUITPY_BUSIO_DMA + +#include "common-hal/busio/I2C.h" +#include "common-hal/busio/SPI.h" +#include "common-hal/busio/UART.h" + +uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); +uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); +bool common_hal_busio_dma_i2c_is_busy(uint dma_channel); + +uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len); +uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len); +uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); +bool common_hal_busio_dma_spi_is_busy(uint dma_channel); + +uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len); +uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); +bool common_hal_busio_dma_uart_is_busy(uint dma_channel); + +#endif From d907b7046abccfe5992d2bc01f12f3e73b4c093e Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Thu, 26 Feb 2026 16:16:59 -0800 Subject: [PATCH 2/4] Busio: Refactored asynchronous methods Removed any mention of DMA from the shared-bindings Added opaque object for tracking transfer state Updated uncrustify config to replace deprecated options --- locale/circuitpython.pot | 5 + ports/raspberrypi/common-hal/busio/I2C.c | 223 +++++++++++- ports/raspberrypi/common-hal/busio/I2C.h | 7 + ports/raspberrypi/common-hal/busio/SPI.c | 142 ++++++++ ports/raspberrypi/common-hal/busio/SPI.h | 5 + ports/raspberrypi/common-hal/busio/UART.c | 47 +++ ports/raspberrypi/common-hal/busio/UART.h | 5 + ports/raspberrypi/common-hal/busio/dma.c | 396 ---------------------- ports/raspberrypi/common-hal/busio/dma.h | 24 -- py/circuitpy_defns.mk | 1 - py/circuitpy_mpconfig.mk | 3 + shared-bindings/busio/I2C.c | 69 ++-- shared-bindings/busio/I2C.h | 7 + shared-bindings/busio/SPI.c | 70 ++-- shared-bindings/busio/SPI.h | 5 + shared-bindings/busio/UART.c | 50 +-- shared-bindings/busio/UART.h | 5 + shared-bindings/busio/dma.c | 11 - shared-bindings/busio/dma.h | 30 -- tools/uncrustify.cfg | 36 +- 20 files changed, 563 insertions(+), 578 deletions(-) delete mode 100644 ports/raspberrypi/common-hal/busio/dma.c delete mode 100644 ports/raspberrypi/common-hal/busio/dma.h delete mode 100644 shared-bindings/busio/dma.c delete mode 100644 shared-bindings/busio/dma.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 0a776db93db7f..d7750214d1743 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1466,6 +1466,7 @@ msgstr "" msgid "Mount point directory missing" msgstr "" +#: shared-bindings/busio/I2C.c shared-bindings/busio/SPI.c #: shared-bindings/busio/UART.c shared-bindings/displayio/Group.c msgid "Must be a %q subclass." msgstr "" @@ -2732,6 +2733,10 @@ msgstr "" msgid "buffer too small for requested bytes" msgstr "" +#: shared-bindings/busio/SPI.c +msgid "buffers must be same length" +msgstr "" + #: py/emitbc.c msgid "bytecode overflow" msgstr "" diff --git a/ports/raspberrypi/common-hal/busio/I2C.c b/ports/raspberrypi/common-hal/busio/I2C.c index 1441a2a7a0668..df7116b1456a1 100644 --- a/ports/raspberrypi/common-hal/busio/I2C.c +++ b/ports/raspberrypi/common-hal/busio/I2C.c @@ -13,7 +13,12 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/bitbangio/I2C.h" +#include "hardware/dma.h" #include "hardware/gpio.h" +#include "hardware/i2c.h" +#include "supervisor/port.h" + +#include // Synopsys DW_apb_i2c (v2.01) IP @@ -22,7 +27,205 @@ // One second #define BUS_TIMEOUT_US 1000000 -static i2c_inst_t *i2c[2] = {i2c0, i2c1}; +struct i2c_transfer_state { + uint dma_channel; + uint paired_dma_channel; + bool pending_last_command; + i2c_inst_t *i2c; + uint8_t *tx_copy; + uint32_t repeated_command; + uint32_t last_command; +}; + +typedef struct i2c_transfer_state i2c_transfer_state; + +static i2c_transfer_state i2c_dma_channel_state[NUM_DMA_CHANNELS]; + +// Addresses of the form 000 0xxx or 111 1xxx are reserved. No slave should +// have these addresses. +#define i2c_reserved_addr(addr) (((addr) & 0x78) == 0 || ((addr) & 0x78) == 0x78) + +static i2c_inst_t *i2c_hw_instances[2] = {i2c0, i2c1}; + +static i2c_transfer_state *common_hal_i2c_write_dma(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) { + invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses + invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); + invalid_params_if(HARDWARE_I2C, len == 0); + invalid_params_if(HARDWARE_I2C, ((int)len) < 0); + + uint dma_channel = dma_claim_unused_channel(true); + i2c_transfer_state *state = &i2c_dma_channel_state[dma_channel]; + state->dma_channel = dma_channel; + + i2c->hw->enable = 0; + i2c->hw->tar = addr; + i2c->hw->enable = 1; + + state->paired_dma_channel = NUM_DMA_CHANNELS; + state->pending_last_command = false; + state->i2c = i2c; + state->tx_copy = NULL; + state->repeated_command = 0; + state->last_command = 0; + + if (len == 1) { + uint32_t command = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + src[0]; + i2c->hw->data_cmd = command; + state->last_command = command; + i2c->restart_on_next = nostop; + return state; + } + + i2c->hw->data_cmd = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + src[0]; + + size_t middle_len = len - 2; + if (middle_len > 0) { + state->tx_copy = port_malloc(middle_len, true); + if (state->tx_copy == NULL) { + dma_channel_unclaim(dma_channel); + m_malloc_fail(middle_len); + } + memcpy(state->tx_copy, src + 1, middle_len); + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, false); + channel_config_set_dreq(&config, I2C_DREQ_NUM(i2c, true)); + + dma_channel_configure(dma_channel, &config, &i2c->hw->data_cmd, + state->tx_copy, middle_len, true); + } + + state->pending_last_command = true; + state->last_command = + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + src[len - 1]; + i2c->restart_on_next = nostop; + return state; +} + +static i2c_transfer_state *common_hal_i2c_read_dma(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) { + invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses + invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); + invalid_params_if(HARDWARE_I2C, len == 0); + invalid_params_if(HARDWARE_I2C, ((int)len) < 0); + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + i2c_transfer_state *state = &i2c_dma_channel_state[tx_dma_channel]; + state->dma_channel = tx_dma_channel; + + i2c->hw->enable = 0; + i2c->hw->tar = addr; + i2c->hw->enable = 1; + + state->paired_dma_channel = rx_dma_channel; + state->pending_last_command = false; + state->i2c = i2c; + state->tx_copy = NULL; + state->repeated_command = 0; + state->last_command = 0; + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, I2C_DREQ_NUM(i2c, false)); + + dma_channel_configure(rx_dma_channel, &rx_config, dst, &i2c->hw->data_cmd, len, true); + + uint32_t first_command = + bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | + I2C_IC_DATA_CMD_CMD_BITS; + uint32_t middle_command = I2C_IC_DATA_CMD_CMD_BITS; + uint32_t last_command = + bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | + I2C_IC_DATA_CMD_CMD_BITS; + + if (len == 1) { + uint32_t command = first_command | last_command; + i2c->hw->data_cmd = command; + state->last_command = command; + } else { + i2c->hw->data_cmd = first_command; + + size_t middle_len = len - 2; + if (middle_len > 0) { + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_32); + channel_config_set_read_increment(&tx_config, false); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, I2C_DREQ_NUM(i2c, true)); + + state->repeated_command = middle_command; + + dma_channel_configure(tx_dma_channel, &tx_config, &i2c->hw->data_cmd, + &state->repeated_command, middle_len, true); + } + + state->pending_last_command = true; + state->last_command = last_command; + } + + i2c->restart_on_next = nostop; + return state; +} + +static bool common_hal_i2c_dma_is_busy(i2c_transfer_state *state) { + if (state == NULL) { + return false; + } + uint dma_channel = state->dma_channel; + if (dma_channel_is_busy(dma_channel)) { + return true; + } + + if (state->pending_last_command) { + if (!i2c_get_write_available(state->i2c)) { + return true; + } + + state->i2c->hw->data_cmd = state->last_command; + state->pending_last_command = false; + return true; + } + + uint paired_dma_channel = state->paired_dma_channel; + if (paired_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(paired_dma_channel)) { + return true; + } + + if (paired_dma_channel < NUM_DMA_CHANNELS) { + dma_channel_unclaim(paired_dma_channel); + state->paired_dma_channel = NUM_DMA_CHANNELS; + } + + if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_TX_EMPTY_BITS)) { + return true; + } + + if (state->last_command & I2C_IC_DATA_CMD_STOP_BITS) { + if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_STOP_DET_BITS)) { + return true; + } + state->i2c->hw->clr_stop_det; + } + + if (state->tx_copy != NULL) { + port_free(state->tx_copy); + state->tx_copy = NULL; + } + + state->i2c = NULL; + + dma_channel_unclaim(dma_channel); + return false; +} void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) { @@ -36,7 +239,7 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, size_t scl_instance = (scl->number / 2) % 2; size_t sda_instance = (sda->number / 2) % 2; if (scl->number % 2 == 1 && sda->number % 2 == 0 && scl_instance == sda_instance) { - self->peripheral = i2c[sda_instance]; + self->peripheral = i2c_hw_instances[sda_instance]; } if (self->peripheral == NULL) { raise_ValueError_invalid_pins(); @@ -217,3 +420,19 @@ void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { never_reset_pin_number(self->scl_pin); never_reset_pin_number(self->sda_pin); } + +i2c_transfer_state *common_hal_busio_i2c_start_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop) { + return common_hal_i2c_read_dma(i2c->peripheral, address, data, len, nostop); +} + +i2c_transfer_state *common_hal_busio_i2c_start_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop) { + return common_hal_i2c_write_dma(i2c->peripheral, address, data, len, nostop); +} + +bool common_hal_busio_i2c_read_isbusy(i2c_transfer_state *state) { + return common_hal_i2c_dma_is_busy(state); +} + +bool common_hal_busio_i2c_write_isbusy(i2c_transfer_state *state) { + return common_hal_i2c_dma_is_busy(state); +} diff --git a/ports/raspberrypi/common-hal/busio/I2C.h b/ports/raspberrypi/common-hal/busio/I2C.h index 7a6fd1b9d1f37..02e4a1021eb1b 100644 --- a/ports/raspberrypi/common-hal/busio/I2C.h +++ b/ports/raspberrypi/common-hal/busio/I2C.h @@ -22,3 +22,10 @@ typedef struct { uint8_t scl_pin; uint8_t sda_pin; } busio_i2c_obj_t; + +typedef struct i2c_transfer_state i2c_transfer_state; + +i2c_transfer_state *common_hal_busio_i2c_start_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); +i2c_transfer_state *common_hal_busio_i2c_start_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); +bool common_hal_busio_i2c_read_isbusy(i2c_transfer_state *state); +bool common_hal_busio_i2c_write_isbusy(i2c_transfer_state *state); diff --git a/ports/raspberrypi/common-hal/busio/SPI.c b/ports/raspberrypi/common-hal/busio/SPI.c index aeb06d919ae45..b2422560e48dc 100644 --- a/ports/raspberrypi/common-hal/busio/SPI.c +++ b/ports/raspberrypi/common-hal/busio/SPI.c @@ -17,8 +17,133 @@ #include "hardware/dma.h" #include "hardware/gpio.h" +struct spi_transfer_state { + uint dma_channel; + uint paired_rx_channel; + uint8_t repeated_tx_data; + uint8_t discard_rx_data; +}; + +typedef struct spi_transfer_state spi_transfer_state; + +static spi_transfer_state spi_dma_channel_state[NUM_DMA_CHANNELS]; + #define NO_INSTANCE 0xff +static spi_transfer_state *common_hal_spi_write_dma(spi_inst_t *spi, const uint8_t *src, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + spi_transfer_state *state = &spi_dma_channel_state[tx_dma_channel]; + state->dma_channel = tx_dma_channel; + + state->paired_rx_channel = rx_dma_channel; + state->discard_rx_data = 0; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, true); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, false); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, &state->discard_rx_data, + &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return state; +} + +static spi_transfer_state *common_hal_spi_read_dma(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + spi_transfer_state *state = &spi_dma_channel_state[tx_dma_channel]; + state->dma_channel = tx_dma_channel; + + state->paired_rx_channel = rx_dma_channel; + state->repeated_tx_data = repeated_tx_data; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, false); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, + &state->repeated_tx_data, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return state; +} + +static spi_transfer_state *common_hal_spi_write_read_dma(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len) { + invalid_params_if(HARDWARE_SPI, 0 > (int)len); + invalid_params_if(HARDWARE_SPI, len == 0); + + uint tx_dma_channel = dma_claim_unused_channel(true); + uint rx_dma_channel = dma_claim_unused_channel(true); + spi_transfer_state *state = &spi_dma_channel_state[tx_dma_channel]; + state->dma_channel = tx_dma_channel; + + state->paired_rx_channel = rx_dma_channel; + + dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + channel_config_set_read_increment(&tx_config, true); + channel_config_set_write_increment(&tx_config, false); + channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); + dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); + + dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); + channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); + channel_config_set_read_increment(&rx_config, false); + channel_config_set_write_increment(&rx_config, true); + channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); + dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); + + dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); + return state; +} + +static bool common_hal_spi_dma_is_busy(spi_transfer_state *state) { + if (state == NULL) { + return false; + } + uint dma_channel = state->dma_channel; + if (dma_channel_is_busy(dma_channel)) { + return true; + } + + uint rx_dma_channel = state->paired_rx_channel; + if (rx_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(rx_dma_channel)) { + return true; + } + + if (rx_dma_channel < NUM_DMA_CHANNELS) { + dma_channel_unclaim(rx_dma_channel); + } + dma_channel_unclaim(dma_channel); + + state->paired_rx_channel = NUM_DMA_CHANNELS; + return false; +} + 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) { @@ -275,3 +400,20 @@ uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { return self->polarity; } + +spi_transfer_state *common_hal_busio_spi_start_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len) { + if (out_data == NULL && in_data == NULL) { + return NULL; + } + if (out_data == NULL) { + return common_hal_spi_read_dma(spi->peripheral, 0, in_data, len); + } + if (in_data == NULL) { + return common_hal_spi_write_dma(spi->peripheral, out_data, len); + } + return common_hal_spi_write_read_dma(spi->peripheral, out_data, in_data, len); +} + +bool common_hal_busio_spi_transfer_isbusy(spi_transfer_state *state) { + return common_hal_spi_dma_is_busy(state); +} diff --git a/ports/raspberrypi/common-hal/busio/SPI.h b/ports/raspberrypi/common-hal/busio/SPI.h index 3d43c1eff0072..575953693f4fc 100644 --- a/ports/raspberrypi/common-hal/busio/SPI.h +++ b/ports/raspberrypi/common-hal/busio/SPI.h @@ -25,3 +25,8 @@ typedef struct { uint8_t phase; uint8_t bits; } busio_spi_obj_t; + +typedef struct spi_transfer_state spi_transfer_state; + +spi_transfer_state *common_hal_busio_spi_start_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); +bool common_hal_busio_spi_transfer_isbusy(spi_transfer_state *state); diff --git a/ports/raspberrypi/common-hal/busio/UART.c b/ports/raspberrypi/common-hal/busio/UART.c index 17fcfa172293d..3f45f70388a51 100644 --- a/ports/raspberrypi/common-hal/busio/UART.c +++ b/ports/raspberrypi/common-hal/busio/UART.c @@ -15,6 +15,7 @@ #include "shared-bindings/microcontroller/Pin.h" #include "hardware/irq.h" +#include "hardware/dma.h" #include "hardware/gpio.h" #define NO_PIN 0xff @@ -29,6 +30,44 @@ typedef enum { static uart_status_t uart_status[NUM_UARTS]; +struct uart_transfer_state { + uint dma_channel; +}; + +typedef struct uart_transfer_state uart_transfer_state; + +static uart_transfer_state uart_transfer_state_by_channel[NUM_DMA_CHANNELS]; + +static uart_transfer_state *common_hal_uart_write_dma(uart_inst_t *uart, const uint8_t *src, size_t len) { + invalid_params_if(HARDWARE_UART, 0 > (int)len); + invalid_params_if(HARDWARE_UART, len == 0); + + uint dma_channel = dma_claim_unused_channel(true); + uart_transfer_state *state = &uart_transfer_state_by_channel[dma_channel]; + state->dma_channel = dma_channel; + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, false); + channel_config_set_dreq(&config, UART_DREQ_NUM(uart, true)); + + dma_channel_configure(dma_channel, &config, &uart_get_hw(uart)->dr, src, len, true); + return state; +} + +static bool common_hal_uart_dma_is_busy(uart_transfer_state *state) { + if (state == NULL) { + return false; + } + uint dma_channel = state->dma_channel; + if (dma_channel_is_busy(dma_channel)) { + return true; + } + dma_channel_unclaim(dma_channel); + return false; +} + void reset_uart(void) { for (uint8_t num = 0; num < NUM_UARTS; num++) { if (uart_status[num] == STATUS_BUSY) { @@ -357,3 +396,11 @@ void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { pin_never_reset(self->rs485_dir_pin); pin_never_reset(self->rts_pin); } + +uart_transfer_state *common_hal_busio_uart_start_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len) { + return common_hal_uart_write_dma(uart->uart, data, len); +} + +bool common_hal_busio_uart_write_isbusy(uart_transfer_state *state) { + return common_hal_uart_dma_is_busy(state); +} diff --git a/ports/raspberrypi/common-hal/busio/UART.h b/ports/raspberrypi/common-hal/busio/UART.h index 3709907633cb0..1eae0e4018ed2 100644 --- a/ports/raspberrypi/common-hal/busio/UART.h +++ b/ports/raspberrypi/common-hal/busio/UART.h @@ -27,5 +27,10 @@ typedef struct { ringbuf_t ringbuf; } busio_uart_obj_t; +typedef struct uart_transfer_state uart_transfer_state; + +uart_transfer_state *common_hal_busio_uart_start_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); +bool common_hal_busio_uart_write_isbusy(uart_transfer_state *state); + extern void reset_uart(void); extern void never_reset_uart(uint8_t num); diff --git a/ports/raspberrypi/common-hal/busio/dma.c b/ports/raspberrypi/common-hal/busio/dma.c deleted file mode 100644 index 726d9d391ddd7..0000000000000 --- a/ports/raspberrypi/common-hal/busio/dma.c +++ /dev/null @@ -1,396 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2026 -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/dma.h" - -#include "hardware/dma.h" -#include "hardware/i2c.h" -#include "hardware/spi.h" -#include "hardware/uart.h" - -#include - -#include "py/runtime.h" -#include "supervisor/port.h" - -typedef struct { - uint paired_dma_channel; - bool pending_last_command; - i2c_inst_t *i2c; - uint8_t *tx_copy; - uint32_t repeated_command; - uint32_t last_command; -} i2c_dma_channel_state_t; - -typedef struct { - uint paired_rx_channel; - uint8_t repeated_tx_data; - uint8_t discard_rx_data; -} spi_dma_channel_state_t; - -static i2c_dma_channel_state_t i2c_dma_channel_state[NUM_DMA_CHANNELS]; -static spi_dma_channel_state_t spi_dma_channel_state[NUM_DMA_CHANNELS]; - -// Addresses of the form 000 0xxx or 111 1xxx are reserved. No slave should -// have these addresses. -#define i2c_reserved_addr(addr) (((addr) & 0x78) == 0 || ((addr) & 0x78) == 0x78) - -static uint common_hal_i2c_write_dma(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) { - invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses - invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); - invalid_params_if(HARDWARE_I2C, len == 0); - invalid_params_if(HARDWARE_I2C, ((int)len) < 0); - - uint dma_channel = dma_claim_unused_channel(true); - - i2c->hw->enable = 0; - i2c->hw->tar = addr; - i2c->hw->enable = 1; - - i2c_dma_channel_state[dma_channel].paired_dma_channel = NUM_DMA_CHANNELS; - i2c_dma_channel_state[dma_channel].pending_last_command = false; - i2c_dma_channel_state[dma_channel].i2c = i2c; - i2c_dma_channel_state[dma_channel].tx_copy = NULL; - i2c_dma_channel_state[dma_channel].repeated_command = 0; - i2c_dma_channel_state[dma_channel].last_command = 0; - - if (len == 1) { - uint32_t command = - bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | - bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | - src[0]; - i2c->hw->data_cmd = command; - i2c_dma_channel_state[dma_channel].last_command = command; - i2c->restart_on_next = nostop; - return dma_channel; - } - - i2c->hw->data_cmd = - bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | - src[0]; - - size_t middle_len = len - 2; - if (middle_len > 0) { - i2c_dma_channel_state[dma_channel].tx_copy = port_malloc(middle_len, true); - if (i2c_dma_channel_state[dma_channel].tx_copy == NULL) { - dma_channel_unclaim(dma_channel); - m_malloc_fail(middle_len); - } - memcpy(i2c_dma_channel_state[dma_channel].tx_copy, src + 1, middle_len); - - dma_channel_config config = dma_channel_get_default_config(dma_channel); - channel_config_set_transfer_data_size(&config, DMA_SIZE_8); - channel_config_set_read_increment(&config, true); - channel_config_set_write_increment(&config, false); - channel_config_set_dreq(&config, I2C_DREQ_NUM(i2c, true)); - - dma_channel_configure(dma_channel, &config, &i2c->hw->data_cmd, - i2c_dma_channel_state[dma_channel].tx_copy, middle_len, true); - } - - i2c_dma_channel_state[dma_channel].pending_last_command = true; - i2c_dma_channel_state[dma_channel].last_command = - bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | - src[len - 1]; - i2c->restart_on_next = nostop; - return dma_channel; -} - -static uint common_hal_i2c_read_dma(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) { - invalid_params_if(HARDWARE_I2C, addr >= 0x80); // 7-bit addresses - invalid_params_if(HARDWARE_I2C, i2c_reserved_addr(addr)); - invalid_params_if(HARDWARE_I2C, len == 0); - invalid_params_if(HARDWARE_I2C, ((int)len) < 0); - uint tx_dma_channel = dma_claim_unused_channel(true); - uint rx_dma_channel = dma_claim_unused_channel(true); - - i2c->hw->enable = 0; - i2c->hw->tar = addr; - i2c->hw->enable = 1; - - i2c_dma_channel_state[tx_dma_channel].paired_dma_channel = rx_dma_channel; - i2c_dma_channel_state[tx_dma_channel].pending_last_command = false; - i2c_dma_channel_state[tx_dma_channel].i2c = i2c; - i2c_dma_channel_state[tx_dma_channel].tx_copy = NULL; - i2c_dma_channel_state[tx_dma_channel].repeated_command = 0; - i2c_dma_channel_state[tx_dma_channel].last_command = 0; - - dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); - channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); - channel_config_set_read_increment(&rx_config, false); - channel_config_set_write_increment(&rx_config, true); - channel_config_set_dreq(&rx_config, I2C_DREQ_NUM(i2c, false)); - - dma_channel_configure(rx_dma_channel, &rx_config, dst, &i2c->hw->data_cmd, len, true); - - uint32_t first_command = - bool_to_bit(i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | - I2C_IC_DATA_CMD_CMD_BITS; - uint32_t middle_command = I2C_IC_DATA_CMD_CMD_BITS; - uint32_t last_command = - bool_to_bit(!nostop) << I2C_IC_DATA_CMD_STOP_LSB | - I2C_IC_DATA_CMD_CMD_BITS; - - if (len == 1) { - uint32_t command = first_command | last_command; - i2c->hw->data_cmd = command; - i2c_dma_channel_state[tx_dma_channel].last_command = command; - } else { - i2c->hw->data_cmd = first_command; - - size_t middle_len = len - 2; - if (middle_len > 0) { - dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); - channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_32); - channel_config_set_read_increment(&tx_config, false); - channel_config_set_write_increment(&tx_config, false); - channel_config_set_dreq(&tx_config, I2C_DREQ_NUM(i2c, true)); - - i2c_dma_channel_state[tx_dma_channel].repeated_command = middle_command; - - dma_channel_configure(tx_dma_channel, &tx_config, &i2c->hw->data_cmd, - &i2c_dma_channel_state[tx_dma_channel].repeated_command, middle_len, true); - } - - i2c_dma_channel_state[tx_dma_channel].pending_last_command = true; - i2c_dma_channel_state[tx_dma_channel].last_command = last_command; - } - - i2c->restart_on_next = nostop; - return tx_dma_channel; -} - -static bool common_hal_i2c_dma_is_busy(uint dma_channel) { - if (dma_channel_is_busy(dma_channel)) { - return true; - } - - i2c_dma_channel_state_t *state = &i2c_dma_channel_state[dma_channel]; - if (state->pending_last_command) { - if (!i2c_get_write_available(state->i2c)) { - return true; - } - - state->i2c->hw->data_cmd = state->last_command; - state->pending_last_command = false; - return true; - } - - uint paired_dma_channel = state->paired_dma_channel; - if (paired_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(paired_dma_channel)) { - return true; - } - - if (paired_dma_channel < NUM_DMA_CHANNELS) { - dma_channel_unclaim(paired_dma_channel); - state->paired_dma_channel = NUM_DMA_CHANNELS; - } - - if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_TX_EMPTY_BITS)) { - return true; - } - - if (state->last_command & I2C_IC_DATA_CMD_STOP_BITS) { - if (!(state->i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_STOP_DET_BITS)) { - return true; - } - state->i2c->hw->clr_stop_det; - } - - if (state->tx_copy != NULL) { - port_free(state->tx_copy); - state->tx_copy = NULL; - } - - state->i2c = NULL; - - dma_channel_unclaim(dma_channel); - return false; -} - -static uint common_hal_spi_write_dma(spi_inst_t *spi, const uint8_t *src, size_t len) { - invalid_params_if(HARDWARE_SPI, 0 > (int)len); - invalid_params_if(HARDWARE_SPI, len == 0); - - uint tx_dma_channel = dma_claim_unused_channel(true); - uint rx_dma_channel = dma_claim_unused_channel(true); - - spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; - spi_dma_channel_state[tx_dma_channel].discard_rx_data = 0; - - dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); - channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); - channel_config_set_read_increment(&tx_config, true); - channel_config_set_write_increment(&tx_config, false); - channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); - dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); - - dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); - channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); - channel_config_set_read_increment(&rx_config, false); - channel_config_set_write_increment(&rx_config, false); - channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); - dma_channel_configure(rx_dma_channel, &rx_config, &spi_dma_channel_state[tx_dma_channel].discard_rx_data, - &spi_get_hw(spi)->dr, len, false); - - dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); - return tx_dma_channel; -} - -static uint common_hal_spi_read_dma(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len) { - invalid_params_if(HARDWARE_SPI, 0 > (int)len); - invalid_params_if(HARDWARE_SPI, len == 0); - - uint tx_dma_channel = dma_claim_unused_channel(true); - uint rx_dma_channel = dma_claim_unused_channel(true); - - spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; - spi_dma_channel_state[tx_dma_channel].repeated_tx_data = repeated_tx_data; - - dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); - channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); - channel_config_set_read_increment(&tx_config, false); - channel_config_set_write_increment(&tx_config, false); - channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); - dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, - &spi_dma_channel_state[tx_dma_channel].repeated_tx_data, len, false); - - dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); - channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); - channel_config_set_read_increment(&rx_config, false); - channel_config_set_write_increment(&rx_config, true); - channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); - dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); - - dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); - return tx_dma_channel; -} - -static uint common_hal_spi_write_read_dma(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len) { - invalid_params_if(HARDWARE_SPI, 0 > (int)len); - invalid_params_if(HARDWARE_SPI, len == 0); - - uint tx_dma_channel = dma_claim_unused_channel(true); - uint rx_dma_channel = dma_claim_unused_channel(true); - - spi_dma_channel_state[tx_dma_channel].paired_rx_channel = rx_dma_channel; - - dma_channel_config tx_config = dma_channel_get_default_config(tx_dma_channel); - channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); - channel_config_set_read_increment(&tx_config, true); - channel_config_set_write_increment(&tx_config, false); - channel_config_set_dreq(&tx_config, SPI_DREQ_NUM(spi, true)); - dma_channel_configure(tx_dma_channel, &tx_config, &spi_get_hw(spi)->dr, src, len, false); - - dma_channel_config rx_config = dma_channel_get_default_config(rx_dma_channel); - channel_config_set_transfer_data_size(&rx_config, DMA_SIZE_8); - channel_config_set_read_increment(&rx_config, false); - channel_config_set_write_increment(&rx_config, true); - channel_config_set_dreq(&rx_config, SPI_DREQ_NUM(spi, false)); - dma_channel_configure(rx_dma_channel, &rx_config, dst, &spi_get_hw(spi)->dr, len, false); - - dma_start_channel_mask((1u << tx_dma_channel) | (1u << rx_dma_channel)); - return tx_dma_channel; -} - -static bool common_hal_spi_dma_is_busy(uint dma_channel) { - if (dma_channel_is_busy(dma_channel)) { - return true; - } - - uint rx_dma_channel = spi_dma_channel_state[dma_channel].paired_rx_channel; - if (rx_dma_channel < NUM_DMA_CHANNELS && dma_channel_is_busy(rx_dma_channel)) { - return true; - } - - if (rx_dma_channel < NUM_DMA_CHANNELS) { - dma_channel_unclaim(rx_dma_channel); - } - dma_channel_unclaim(dma_channel); - - spi_dma_channel_state[dma_channel].paired_rx_channel = NUM_DMA_CHANNELS; - return false; -} - -static uint common_hal_uart_write_dma(uart_inst_t *uart, const uint8_t *src, size_t len) { - invalid_params_if(HARDWARE_UART, 0 > (int)len); - invalid_params_if(HARDWARE_UART, len == 0); - - uint dma_channel = dma_claim_unused_channel(true); - - dma_channel_config config = dma_channel_get_default_config(dma_channel); - channel_config_set_transfer_data_size(&config, DMA_SIZE_8); - channel_config_set_read_increment(&config, true); - channel_config_set_write_increment(&config, false); - channel_config_set_dreq(&config, UART_DREQ_NUM(uart, true)); - - dma_channel_configure(dma_channel, &config, &uart_get_hw(uart)->dr, src, len, true); - return dma_channel; -} - -static uint common_hal_uart_read_dma(uart_inst_t *uart, uint8_t *dst, size_t len) { - invalid_params_if(HARDWARE_UART, 0 > (int)len); - invalid_params_if(HARDWARE_UART, len == 0); - - uint dma_channel = dma_claim_unused_channel(true); - - dma_channel_config config = dma_channel_get_default_config(dma_channel); - channel_config_set_transfer_data_size(&config, DMA_SIZE_8); - channel_config_set_read_increment(&config, false); - channel_config_set_write_increment(&config, true); - channel_config_set_dreq(&config, UART_DREQ_NUM(uart, false)); - - dma_channel_configure(dma_channel, &config, dst, &uart_get_hw(uart)->dr, len, true); - return dma_channel; -} - -static bool common_hal_uart_dma_is_busy(uint dma_channel) { - if (dma_channel_is_busy(dma_channel)) { - return true; - } - dma_channel_unclaim(dma_channel); - return false; -} - -uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop) { - return common_hal_i2c_read_dma(i2c->peripheral, address, data, len, nostop); -} - -uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop) { - return common_hal_i2c_write_dma(i2c->peripheral, address, data, len, nostop); -} - -bool common_hal_busio_dma_i2c_is_busy(uint dma_channel) { - return common_hal_i2c_dma_is_busy(dma_channel); -} - -uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len) { - return common_hal_spi_write_dma(spi->peripheral, data, len); -} - -uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len) { - return common_hal_spi_read_dma(spi->peripheral, write_value, data, len); -} - -uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len) { - return common_hal_spi_write_read_dma(spi->peripheral, out_data, in_data, len); -} - -bool common_hal_busio_dma_spi_is_busy(uint dma_channel) { - return common_hal_spi_dma_is_busy(dma_channel); -} - -uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len) { - return common_hal_uart_read_dma(uart->uart, data, len); -} - -uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len) { - return common_hal_uart_write_dma(uart->uart, data, len); -} - -bool common_hal_busio_dma_uart_is_busy(uint dma_channel) { - return common_hal_uart_dma_is_busy(dma_channel); -} diff --git a/ports/raspberrypi/common-hal/busio/dma.h b/ports/raspberrypi/common-hal/busio/dma.h deleted file mode 100644 index f540210f9ecc4..0000000000000 --- a/ports/raspberrypi/common-hal/busio/dma.h +++ /dev/null @@ -1,24 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2026 -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "common-hal/busio/I2C.h" -#include "common-hal/busio/SPI.h" -#include "common-hal/busio/UART.h" - -uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); -uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); -bool common_hal_busio_dma_i2c_is_busy(uint dma_channel); - -uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len); -uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len); -uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); -bool common_hal_busio_dma_spi_is_busy(uint dma_channel); - -uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len); -uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); -bool common_hal_busio_dma_uart_is_busy(uint dma_channel); diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 64f6d4ac8a0f1..d8c95d18a79b9 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -501,7 +501,6 @@ SRC_COMMON_HAL_ALL = \ audiopwmio/PWMAudioOut.c \ audiopwmio/__init__.c \ board/__init__.c \ - busio/dma.c \ busio/I2C.c \ busio/SPI.c \ busio/UART.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 2ce0210b6b7d5..3bf9e69b43bc4 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -223,6 +223,9 @@ CFLAGS += -DCIRCUITPY_BUSIO_UART=$(CIRCUITPY_BUSIO_UART) CIRCUITPY_BUSIO_DMA ?= 0 CFLAGS += -DCIRCUITPY_BUSIO_DMA=$(CIRCUITPY_BUSIO_DMA) +CIRCUITPY_BUSIO_NOBLOCK ?= $(CIRCUITPY_BUSIO_DMA) +CFLAGS += -DCIRCUITPY_BUSIO_NOBLOCK=$(CIRCUITPY_BUSIO_NOBLOCK) + CIRCUITPY_CAMERA ?= 0 CFLAGS += -DCIRCUITPY_CAMERA=$(CIRCUITPY_CAMERA) diff --git a/shared-bindings/busio/I2C.c b/shared-bindings/busio/I2C.c index 61ac6a3acb427..9888d6b793cf8 100644 --- a/shared-bindings/busio/I2C.c +++ b/shared-bindings/busio/I2C.c @@ -7,9 +7,10 @@ // This file contains all of the Python API definitions for the // busio.I2C class. +#include + #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/busio/I2C.h" -#include "shared-bindings/busio/dma.h" #include "shared-bindings/util.h" #include "shared/runtime/buffer_helper.h" @@ -121,20 +122,20 @@ static void check_lock(busio_i2c_obj_t *self) { } } -#if CIRCUITPY_BUSIO_DMA -//| def dma_readinto(self, address: int, buffer: WriteableBuffer, *, end: bool = False) -> int: -//| """Start a DMA I2C read into ``buffer`` and return the DMA channel. +#if CIRCUITPY_BUSIO_NOBLOCK +//| def nonblocking_readinto(self, address: int, buffer: WriteableBuffer, *, end: bool = False) -> int: +//| """Start a non-blocking I2C read into ``buffer`` and return the channel. //| //| The I2C object must be locked before calling. //| //| :param int address: 7-bit I2C target address //| :param ~circuitpython_typing.WriteableBuffer buffer: destination buffer //| :param bool end: If ``True``, send a STOP condition at the end of the transfer -//| :return: DMA channel used by this transfer +//| :return: channel used by this transfer //| :rtype: int //| """ //| -static mp_obj_t busio_i2c_dma_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t busio_i2c_nonblocking_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_address, ARG_buffer, ARG_end }; static const mp_arg_t allowed_args[] = { { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, @@ -155,24 +156,24 @@ static mp_obj_t busio_i2c_dma_read(size_t n_args, const mp_obj_t *pos_args, mp_m mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); - uint dma_channel = common_hal_busio_dma_i2c_read(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); - return mp_obj_new_int_from_uint(dma_channel); + i2c_transfer_state *state = common_hal_busio_i2c_start_read(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); + return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_dma_read_obj, 1, busio_i2c_dma_read); +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_nonblocking_readinto_obj, 1, busio_i2c_nonblocking_readinto); -//| def dma_write(self, address: int, buffer: ReadableBuffer, *, end: bool = False) -> int: -//| """Start a DMA I2C write from ``buffer`` and return the DMA channel. +//| def nonblocking_write(self, address: int, buffer: ReadableBuffer, *, end: bool = False) -> int: +//| """Start a non-blocking I2C write from ``buffer`` and return the channel. //| //| The I2C object must be locked before calling. //| //| :param int address: 7-bit I2C target address //| :param ~circuitpython_typing.ReadableBuffer buffer: source buffer //| :param bool end: If ``True``, send a STOP condition at the end of the transfer -//| :return: DMA channel used by this transfer +//| :return: channel used by this transfer //| :rtype: int //| """ //| -static mp_obj_t busio_i2c_dma_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t busio_i2c_nonblocking_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_address, ARG_buffer, ARG_end }; static const mp_arg_t allowed_args[] = { { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, @@ -193,23 +194,38 @@ static mp_obj_t busio_i2c_dma_write(size_t n_args, const mp_obj_t *pos_args, mp_ mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); - uint dma_channel = common_hal_busio_dma_i2c_write(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); - return mp_obj_new_int_from_uint(dma_channel); + i2c_transfer_state *state = common_hal_busio_i2c_start_write(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); + return mp_obj_new_int_from_ull((uintptr_t)state); +} +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_nonblocking_write_obj, 1, busio_i2c_nonblocking_write); + +//| def nonblocking_write_is_busy(self, channel: int) -> bool: +//| """Return ``True`` while the I2C non-blocking write channel is active. +//| +//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` +//| """ +//| +static mp_obj_t busio_i2c_nonblocking_write_isbusy(mp_obj_t self_in, mp_obj_t channel_obj) { + busio_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + i2c_transfer_state *state = (i2c_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); + return mp_obj_new_bool(common_hal_busio_i2c_write_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_dma_write_obj, 1, busio_i2c_dma_write); +MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_nonblocking_write_isbusy_obj, busio_i2c_nonblocking_write_isbusy); -//| def dma_is_busy(self, dma_channel: int) -> bool: -//| """Return ``True`` while the I2C DMA channel is active. +//| def nonblocking_read_is_busy(self, channel: int) -> bool: +//| """Return ``True`` while the I2C non-blocking read channel is active. //| -//| :param int dma_channel: DMA channel returned by `dma_readinto` or `dma_write` +//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` //| """ //| -static mp_obj_t busio_i2c_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { +static mp_obj_t busio_i2c_nonblocking_read_isbusy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); - return mp_obj_new_bool(common_hal_busio_dma_i2c_is_busy(mp_obj_get_int(dma_channel_obj))); + i2c_transfer_state *state = (i2c_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); + return mp_obj_new_bool(common_hal_busio_i2c_read_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_dma_is_busy_obj, busio_i2c_dma_is_busy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_nonblocking_read_isbusy_obj, busio_i2c_nonblocking_read_isbusy); #endif //| def probe(self, address: int) -> List[int]: @@ -503,10 +519,11 @@ static const mp_rom_map_elem_t busio_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readfrom_into), MP_ROM_PTR(&busio_i2c_readfrom_into_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&busio_i2c_writeto_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto_then_readfrom), MP_ROM_PTR(&busio_i2c_writeto_then_readfrom_obj) }, - #if CIRCUITPY_BUSIO_DMA - { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_i2c_dma_read_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_i2c_dma_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_i2c_dma_is_busy_obj) }, + #if CIRCUITPY_BUSIO_NOBLOCK + { MP_ROM_QSTR(MP_QSTR_nonblocking_readinto), MP_ROM_PTR(&busio_i2c_nonblocking_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_i2c_nonblocking_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_read_is_busy), MP_ROM_PTR(&busio_i2c_nonblocking_read_isbusy_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_write_is_busy), MP_ROM_PTR(&busio_i2c_nonblocking_write_isbusy_obj) }, #endif #endif // CIRCUITPY_BUSIO_I2C }; diff --git a/shared-bindings/busio/I2C.h b/shared-bindings/busio/I2C.h index 2978a90754490..51b98c310b52f 100644 --- a/shared-bindings/busio/I2C.h +++ b/shared-bindings/busio/I2C.h @@ -51,3 +51,10 @@ mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint1 // This is used by the supervisor to claim I2C devices indefinitely. extern void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self); + +#if CIRCUITPY_BUSIO_NOBLOCK +i2c_transfer_state *common_hal_busio_i2c_start_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); +i2c_transfer_state *common_hal_busio_i2c_start_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); +bool common_hal_busio_i2c_read_isbusy(i2c_transfer_state *state); +bool common_hal_busio_i2c_write_isbusy(i2c_transfer_state *state); +#endif diff --git a/shared-bindings/busio/SPI.c b/shared-bindings/busio/SPI.c index f83d5fa9c990a..7fd6b1cf72d98 100644 --- a/shared-bindings/busio/SPI.c +++ b/shared-bindings/busio/SPI.c @@ -8,10 +8,10 @@ // busio.SPI class. #include +#include #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/busio/SPI.h" -#include "shared-bindings/busio/dma.h" #include "shared-bindings/util.h" #include "shared/runtime/buffer_helper.h" @@ -161,14 +161,14 @@ static busio_spi_obj_t *native_spi(mp_obj_t spi_obj) { return MP_OBJ_TO_PTR(native_spi); } -#if CIRCUITPY_BUSIO_DMA -//| def dma_write(self, buffer: ReadableBuffer) -> int: -//| """Start a DMA SPI write from ``buffer`` and return the DMA channel. +#if CIRCUITPY_BUSIO_NOBLOCK +//| def nonblocking_write(self, buffer: ReadableBuffer) -> int: +//| """Start a non-blocking SPI write from ``buffer`` and return the channel. //| //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_dma_write(mp_obj_t self_in, mp_obj_t buffer_obj) { +static mp_obj_t busio_spi_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); check_lock(self); @@ -176,23 +176,21 @@ static mp_obj_t busio_spi_dma_write(mp_obj_t self_in, mp_obj_t buffer_obj) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); - uint dma_channel = common_hal_busio_dma_spi_write(self, bufinfo.buf, bufinfo.len); - return mp_obj_new_int_from_uint(dma_channel); + spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, bufinfo.buf, NULL, bufinfo.len); + return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_dma_write_obj, busio_spi_dma_write); +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_nonblocking_write_obj, busio_spi_nonblocking_write); -//| def dma_readinto(self, buffer: WriteableBuffer, *, write_value: int = 0) -> int: -//| """Start a DMA SPI read into ``buffer`` and return the DMA channel. +//| def nonblocking_readinto(self, buffer: WriteableBuffer) -> int: +//| """Start a non-blocking SPI read into ``buffer`` and return the channel. //| -//| ``write_value`` is transmitted while reading. //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_dma_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_buffer, ARG_write_value }; +static mp_obj_t busio_spi_nonblocking_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer }; static const mp_arg_t allowed_args[] = { { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_write_value, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; busio_spi_obj_t *self = native_spi(pos_args[0]); @@ -202,25 +200,22 @@ static mp_obj_t busio_spi_dma_readinto(size_t n_args, const mp_obj_t *pos_args, 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_int_t write_value = args[ARG_write_value].u_int; - mp_arg_validate_int_range(write_value, 0, 0xff, MP_QSTR_write_value); - mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); - uint dma_channel = common_hal_busio_dma_spi_read(self, write_value, bufinfo.buf, bufinfo.len); - return mp_obj_new_int_from_uint(dma_channel); + spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, NULL, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_dma_readinto_obj, 1, busio_spi_dma_readinto); +MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_nonblocking_readinto_obj, 1, busio_spi_nonblocking_readinto); -//| def dma_write_readinto(self, out_buffer: ReadableBuffer, in_buffer: WriteableBuffer) -> int: -//| """Start a DMA SPI write/read transfer and return the DMA channel. +//| def nonblocking_write_readinto(self, out_buffer: ReadableBuffer, in_buffer: WriteableBuffer) -> int: +//| """Start a non-blocking SPI write/read transfer and return the channel. //| //| ``out_buffer`` and ``in_buffer`` must have the same length. //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_dma_write_readinto(mp_obj_t self_in, mp_obj_t out_buffer_obj, mp_obj_t in_buffer_obj) { +static mp_obj_t busio_spi_nonblocking_write_readinto(mp_obj_t self_in, mp_obj_t out_buffer_obj, mp_obj_t in_buffer_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); check_lock(self); @@ -235,23 +230,24 @@ static mp_obj_t busio_spi_dma_write_readinto(mp_obj_t self_in, mp_obj_t out_buff mp_raise_ValueError(MP_ERROR_TEXT("buffers must be same length")); } - uint dma_channel = common_hal_busio_dma_spi_transfer(self, out_bufinfo.buf, in_bufinfo.buf, out_bufinfo.len); - return mp_obj_new_int_from_uint(dma_channel); + spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, out_bufinfo.buf, in_bufinfo.buf, out_bufinfo.len); + return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_3(busio_spi_dma_write_readinto_obj, busio_spi_dma_write_readinto); +MP_DEFINE_CONST_FUN_OBJ_3(busio_spi_nonblocking_write_readinto_obj, busio_spi_nonblocking_write_readinto); -//| def dma_is_busy(self, dma_channel: int) -> bool: -//| """Return ``True`` while the SPI DMA channel is active. +//| def nonblocking_is_busy(self, channel: int) -> bool: +//| """Return ``True`` while the SPI non-blocking channel is active. //| -//| :param int dma_channel: DMA channel returned by SPI DMA transfer functions +//| :param int channel: channel returned by SPI non-blocking transfer functions //| """ //| -static mp_obj_t busio_spi_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { +static mp_obj_t busio_spi_nonblocking_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); - return mp_obj_new_bool(common_hal_busio_dma_spi_is_busy(mp_obj_get_int(dma_channel_obj))); + spi_transfer_state *state = (spi_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); + return mp_obj_new_bool(common_hal_busio_spi_transfer_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_dma_is_busy_obj, busio_spi_dma_is_busy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_nonblocking_is_busy_obj, busio_spi_nonblocking_is_busy); #endif //| def configure( @@ -579,11 +575,11 @@ static const mp_rom_map_elem_t busio_spi_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&busio_spi_readinto_obj) }, { 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) }, - #if CIRCUITPY_BUSIO_DMA - { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_spi_dma_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_spi_dma_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_write_readinto), MP_ROM_PTR(&busio_spi_dma_write_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_spi_dma_is_busy_obj) }, + #if CIRCUITPY_BUSIO_NOBLOCK + { MP_ROM_QSTR(MP_QSTR_nonblocking_readinto), MP_ROM_PTR(&busio_spi_nonblocking_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_spi_nonblocking_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_write_readinto), MP_ROM_PTR(&busio_spi_nonblocking_write_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_is_busy), MP_ROM_PTR(&busio_spi_nonblocking_is_busy_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&busio_spi_frequency_obj) } diff --git a/shared-bindings/busio/SPI.h b/shared-bindings/busio/SPI.h index 34f34c927f613..b136f30301121 100644 --- a/shared-bindings/busio/SPI.h +++ b/shared-bindings/busio/SPI.h @@ -54,3 +54,8 @@ uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self); extern void common_hal_busio_spi_never_reset(busio_spi_obj_t *self); extern busio_spi_obj_t *validate_obj_is_spi_bus(mp_obj_t obj_in, qstr arg_name); + +#if CIRCUITPY_BUSIO_NOBLOCK +spi_transfer_state *common_hal_busio_spi_start_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); +bool common_hal_busio_spi_transfer_isbusy(spi_transfer_state *state); +#endif diff --git a/shared-bindings/busio/UART.c b/shared-bindings/busio/UART.c index 2855b353c5fd2..b11f60396661f 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -7,7 +7,6 @@ #include #include "shared-bindings/busio/UART.h" -#include "shared-bindings/busio/dma.h" #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/util.h" @@ -178,49 +177,35 @@ static busio_uart_obj_t *native_uart(mp_obj_t uart_obj) { static void check_for_deinit(busio_uart_obj_t *self); -#if CIRCUITPY_BUSIO_DMA -//| def dma_readinto(self, buffer: WriteableBuffer) -> int: -//| """Start a DMA UART read into ``buffer`` and return the DMA channel.""" +#if CIRCUITPY_BUSIO_NOBLOCK +//| def nonblocking_write(self, buffer: ReadableBuffer) -> int: +//| """Start a non-blocking UART write from ``buffer`` and return the channel.""" //| -static mp_obj_t busio_uart_dma_readinto(mp_obj_t self_in, mp_obj_t buffer_obj) { - busio_uart_obj_t *self = native_uart(self_in); - check_for_deinit(self); - - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_WRITE); - - uint dma_channel = common_hal_busio_dma_uart_read(self, bufinfo.buf, bufinfo.len); - return mp_obj_new_int_from_uint(dma_channel); -} -MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_readinto_obj, busio_uart_dma_readinto); - -//| def dma_write(self, buffer: ReadableBuffer) -> int: -//| """Start a DMA UART write from ``buffer`` and return the DMA channel.""" -//| -static mp_obj_t busio_uart_dma_write(mp_obj_t self_in, mp_obj_t buffer_obj) { +static mp_obj_t busio_uart_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_obj) { busio_uart_obj_t *self = native_uart(self_in); check_for_deinit(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); - uint dma_channel = common_hal_busio_dma_uart_write(self, bufinfo.buf, bufinfo.len); - return mp_obj_new_int_from_uint(dma_channel); + uart_transfer_state *state = common_hal_busio_uart_start_write(self, bufinfo.buf, bufinfo.len); + return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_write_obj, busio_uart_dma_write); +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_nonblocking_write_obj, busio_uart_nonblocking_write); -//| def dma_is_busy(self, dma_channel: int) -> bool: -//| """Return ``True`` while the UART DMA channel is active. +//| def nonblocking_is_busy(self, channel: int) -> bool: +//| """Return ``True`` while the UART non-blocking channel is active. //| -//| :param int dma_channel: DMA channel returned by `dma_readinto` or `dma_write` +//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` //| """ //| -static mp_obj_t busio_uart_dma_is_busy(mp_obj_t self_in, mp_obj_t dma_channel_obj) { +static mp_obj_t busio_uart_nonblocking_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_uart_obj_t *self = native_uart(self_in); check_for_deinit(self); - return mp_obj_new_bool(common_hal_busio_dma_uart_is_busy(mp_obj_get_int(dma_channel_obj))); + uart_transfer_state *state = (uart_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); + return mp_obj_new_bool(common_hal_busio_uart_write_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_dma_is_busy_obj, busio_uart_dma_is_busy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_nonblocking_is_busy_obj, busio_uart_nonblocking_is_busy); #endif @@ -472,10 +457,9 @@ static const mp_rom_map_elem_t busio_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, - #if CIRCUITPY_BUSIO_DMA - { MP_ROM_QSTR(MP_QSTR_dma_readinto), MP_ROM_PTR(&busio_uart_dma_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_write), MP_ROM_PTR(&busio_uart_dma_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_dma_is_busy), MP_ROM_PTR(&busio_uart_dma_is_busy_obj) }, + #if CIRCUITPY_BUSIO_NOBLOCK + { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_uart_nonblocking_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_nonblocking_is_busy), MP_ROM_PTR(&busio_uart_nonblocking_is_busy_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&busio_uart_reset_input_buffer_obj) }, diff --git a/shared-bindings/busio/UART.h b/shared-bindings/busio/UART.h index c8e6755c129fc..778ffe4b0d466 100644 --- a/shared-bindings/busio/UART.h +++ b/shared-bindings/busio/UART.h @@ -48,3 +48,8 @@ extern void common_hal_busio_uart_clear_rx_buffer(busio_uart_obj_t *self); extern bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self); extern void common_hal_busio_uart_never_reset(busio_uart_obj_t *self); + +#if CIRCUITPY_BUSIO_NOBLOCK +uart_transfer_state *common_hal_busio_uart_start_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); +bool common_hal_busio_uart_write_isbusy(uart_transfer_state *state); +#endif diff --git a/shared-bindings/busio/dma.c b/shared-bindings/busio/dma.c deleted file mode 100644 index eab511d05db9e..0000000000000 --- a/shared-bindings/busio/dma.c +++ /dev/null @@ -1,11 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2026 -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/dma.h" - -#if CIRCUITPY_BUSIO_DMA -// DMA APIs are exposed as methods on busio.I2C, busio.SPI, and busio.UART. -#endif diff --git a/shared-bindings/busio/dma.h b/shared-bindings/busio/dma.h deleted file mode 100644 index 134f76a8681b6..0000000000000 --- a/shared-bindings/busio/dma.h +++ /dev/null @@ -1,30 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2026 -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -#if CIRCUITPY_BUSIO_DMA - -#include "common-hal/busio/I2C.h" -#include "common-hal/busio/SPI.h" -#include "common-hal/busio/UART.h" - -uint common_hal_busio_dma_i2c_read(busio_i2c_obj_t *i2c, uint8_t address, uint8_t *data, size_t len, bool nostop); -uint common_hal_busio_dma_i2c_write(busio_i2c_obj_t *i2c, uint8_t address, const uint8_t *data, size_t len, bool nostop); -bool common_hal_busio_dma_i2c_is_busy(uint dma_channel); - -uint common_hal_busio_dma_spi_write(busio_spi_obj_t *spi, const uint8_t *data, size_t len); -uint common_hal_busio_dma_spi_read(busio_spi_obj_t *spi, uint8_t write_value, uint8_t *data, size_t len); -uint common_hal_busio_dma_spi_transfer(busio_spi_obj_t *spi, const uint8_t *out_data, uint8_t *in_data, size_t len); -bool common_hal_busio_dma_spi_is_busy(uint dma_channel); - -uint common_hal_busio_dma_uart_read(busio_uart_obj_t *uart, uint8_t *data, size_t len); -uint common_hal_busio_dma_uart_write(busio_uart_obj_t *uart, const uint8_t *data, size_t len); -bool common_hal_busio_dma_uart_is_busy(uint dma_channel); - -#endif diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg index bf5e37bf3ff81..29d6529d0a054 100644 --- a/tools/uncrustify.cfg +++ b/tools/uncrustify.cfg @@ -178,7 +178,7 @@ sp_paren_paren = remove # ignore/add/remove/force sp_cparen_oparen = ignore # ignore/add/remove/force # Whether to balance spaces inside nested parentheses. -sp_balance_nested_parens = false # true/false +# Replaced by sp_paren_paren in newer uncrustify versions. # Add or remove space between ')' and '{'. sp_paren_brace = force # ignore/add/remove/force @@ -408,7 +408,7 @@ sp_before_ellipsis = ignore # ignore/add/remove/force sp_type_ellipsis = ignore # ignore/add/remove/force # (D) Add or remove space between a type and '?'. -sp_type_question = ignore # ignore/add/remove/force +# Deprecated in newer uncrustify versions. # Add or remove space between ')' and '...'. sp_paren_ellipsis = ignore # ignore/add/remove/force @@ -902,10 +902,10 @@ sp_inside_newop_paren_open = ignore # ignore/add/remove/force sp_inside_newop_paren_close = ignore # ignore/add/remove/force # Add or remove space before a trailing or embedded comment. -sp_before_tr_emb_cmt = ignore # ignore/add/remove/force +sp_before_tr_cmt = ignore # ignore/add/remove/force # Number of spaces before a trailing or embedded comment. -sp_num_before_tr_emb_cmt = 0 # unsigned number +sp_num_before_tr_cmt = 0 # unsigned number # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force @@ -1046,8 +1046,8 @@ indent_else_if = false # true/false # Amount to indent variable declarations after a open brace. # -# <0: Relative -# >=0: Absolute +# negative: Relative +# zero or positive: Absolute indent_var_def_blk = 0 # number # Whether to indent continued variable declarations instead of aligning. @@ -1055,7 +1055,7 @@ indent_var_def_cont = false # true/false # Whether to indent continued shift expressions ('<<' and '>>') instead of # aligning. Set align_left_shift=false when enabling this. -indent_shift = false # true/false +indent_shift = 0 # number # Whether to force indentation of function definitions to start in column 1. indent_func_def_force_col1 = false # true/false @@ -1121,7 +1121,7 @@ indent_member = 0 # unsigned number indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. -indent_sing_line_comments = 0 # unsigned number +indent_single_line_comments_before = 0 # unsigned number # When opening a paren for a control statement (if, for, while, etc), increase # the indent level by this value. Negative values decrease the indent level. @@ -1201,12 +1201,12 @@ indent_paren_after_func_decl = false # true/false indent_paren_after_func_call = false # true/false # Whether to indent a comma when inside a parenthesis. -# If true, aligns under the open parenthesis. -indent_comma_paren = false # true/false +# If non-zero, aligns under the open parenthesis. +indent_comma_paren = 0 # number # Whether to indent a Boolean operator when inside a parenthesis. -# If true, aligns under the open parenthesis. -indent_bool_paren = false # true/false +# If non-zero, aligns under the open parenthesis. +indent_bool_paren = 0 # number # Whether to indent a semicolon when inside a for parenthesis. # If true, aligns under the open for parenthesis. @@ -2041,7 +2041,7 @@ nl_after_func_body_one_liner = 0 # unsigned number # of a function body. # # 0: No change (default). -nl_func_var_def_blk = 0 # unsigned number +nl_var_def_blk_end_func_top = 0 # unsigned number # The number of newlines before a block of typedefs. If nl_after_access_spec # is non-zero, that option takes precedence. @@ -2535,7 +2535,7 @@ align_oc_msg_spec_span = 0 # unsigned number # Whether to align macros wrapped with a backslash and a newline. This will # not work right if the macro contains a multi-line comment. -align_nl_cont = false # true/false +align_nl_cont = 0 # unsigned number # Whether to align macro functions and variables together. align_pp_define_together = false # true/false @@ -2724,7 +2724,7 @@ mod_full_brace_if = force # ignore/add/remove/force # blocks. # # Overrides mod_full_brace_if. -mod_full_brace_if_chain = false # true/false +mod_full_brace_if_chain = 0 # unsigned number # Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. # If true, mod_full_brace_if_chain will only remove braces from an 'if' that @@ -2889,7 +2889,7 @@ pp_indent_at_level = true # true/false pp_indent_count = 1 # unsigned number # Add or remove space after # based on pp_level of #if blocks. -pp_space = remove # ignore/add/remove/force +pp_space_after = remove # ignore/add/remove/force # Sets the number of spaces per level added with pp_space. pp_space_count = 0 # unsigned number @@ -2943,8 +2943,8 @@ pp_indent_extern = true # true/false # Only applies to the indent of the preprocessor that the braces are directly # inside of. # -# Default: true -pp_indent_brace = true # true/false +# Default: 1 +pp_indent_brace = 1 # number # # Sort includes options From 71c783312c6f932cfbd6db50e144e2b19d9a697d Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Thu, 26 Feb 2026 17:18:02 -0800 Subject: [PATCH 3/4] Busio: Updated documentation to reflect API changes --- shared-bindings/busio/I2C.c | 48 ++++++++++++++++++------------------ shared-bindings/busio/SPI.c | 42 +++++++++++++++---------------- shared-bindings/busio/UART.c | 22 ++++++++--------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/shared-bindings/busio/I2C.c b/shared-bindings/busio/I2C.c index 9888d6b793cf8..58565c55716f7 100644 --- a/shared-bindings/busio/I2C.c +++ b/shared-bindings/busio/I2C.c @@ -123,19 +123,19 @@ static void check_lock(busio_i2c_obj_t *self) { } #if CIRCUITPY_BUSIO_NOBLOCK -//| def nonblocking_readinto(self, address: int, buffer: WriteableBuffer, *, end: bool = False) -> int: -//| """Start a non-blocking I2C read into ``buffer`` and return the channel. +//| def start_read(self, address: int, buffer: WriteableBuffer, *, end: bool = False) -> int: +//| """Start a non-blocking I2C read into ``buffer`` and return the transfer_state. //| //| The I2C object must be locked before calling. //| //| :param int address: 7-bit I2C target address //| :param ~circuitpython_typing.WriteableBuffer buffer: destination buffer //| :param bool end: If ``True``, send a STOP condition at the end of the transfer -//| :return: channel used by this transfer +//| :return: transfer_state used by this transfer //| :rtype: int //| """ //| -static mp_obj_t busio_i2c_nonblocking_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t busio_i2c_start_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_address, ARG_buffer, ARG_end }; static const mp_arg_t allowed_args[] = { { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, @@ -159,21 +159,21 @@ static mp_obj_t busio_i2c_nonblocking_readinto(size_t n_args, const mp_obj_t *po i2c_transfer_state *state = common_hal_busio_i2c_start_read(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_nonblocking_readinto_obj, 1, busio_i2c_nonblocking_readinto); +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_start_read_obj, 1, busio_i2c_start_read); -//| def nonblocking_write(self, address: int, buffer: ReadableBuffer, *, end: bool = False) -> int: -//| """Start a non-blocking I2C write from ``buffer`` and return the channel. +//| def start_write(self, address: int, buffer: ReadableBuffer, *, end: bool = False) -> int: +//| """Start a non-blocking I2C write from ``buffer`` and return the transfer_state. //| //| The I2C object must be locked before calling. //| //| :param int address: 7-bit I2C target address //| :param ~circuitpython_typing.ReadableBuffer buffer: source buffer //| :param bool end: If ``True``, send a STOP condition at the end of the transfer -//| :return: channel used by this transfer +//| :return: transfer_state used by this transfer //| :rtype: int //| """ //| -static mp_obj_t busio_i2c_nonblocking_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t busio_i2c_start_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_address, ARG_buffer, ARG_end }; static const mp_arg_t allowed_args[] = { { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, @@ -197,35 +197,35 @@ static mp_obj_t busio_i2c_nonblocking_write(size_t n_args, const mp_obj_t *pos_a i2c_transfer_state *state = common_hal_busio_i2c_start_write(self, address, bufinfo.buf, bufinfo.len, args[ARG_end].u_bool); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_nonblocking_write_obj, 1, busio_i2c_nonblocking_write); +MP_DEFINE_CONST_FUN_OBJ_KW(busio_i2c_start_write_obj, 1, busio_i2c_start_write); -//| def nonblocking_write_is_busy(self, channel: int) -> bool: -//| """Return ``True`` while the I2C non-blocking write channel is active. +//| def write_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the I2C non-blocking write transfer_state is active. //| -//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` +//| :param int transfer_state: transfer_state returned by `start_read` or `start_write` //| """ //| -static mp_obj_t busio_i2c_nonblocking_write_isbusy(mp_obj_t self_in, mp_obj_t channel_obj) { +static mp_obj_t busio_i2c_write_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); i2c_transfer_state *state = (i2c_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); return mp_obj_new_bool(common_hal_busio_i2c_write_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_nonblocking_write_isbusy_obj, busio_i2c_nonblocking_write_isbusy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_write_is_busy_obj, busio_i2c_write_is_busy); -//| def nonblocking_read_is_busy(self, channel: int) -> bool: -//| """Return ``True`` while the I2C non-blocking read channel is active. +//| def read_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the I2C non-blocking read transfer_state is active. //| -//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` +//| :param int transfer_state: transfer_state returned by `start_read` or `start_write` //| """ //| -static mp_obj_t busio_i2c_nonblocking_read_isbusy(mp_obj_t self_in, mp_obj_t channel_obj) { +static mp_obj_t busio_i2c_read_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_i2c_obj_t *self = native_i2c(self_in); check_for_deinit(self); i2c_transfer_state *state = (i2c_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); return mp_obj_new_bool(common_hal_busio_i2c_read_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_nonblocking_read_isbusy_obj, busio_i2c_nonblocking_read_isbusy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_i2c_read_is_busy_obj, busio_i2c_read_is_busy); #endif //| def probe(self, address: int) -> List[int]: @@ -520,10 +520,10 @@ static const mp_rom_map_elem_t busio_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&busio_i2c_writeto_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto_then_readfrom), MP_ROM_PTR(&busio_i2c_writeto_then_readfrom_obj) }, #if CIRCUITPY_BUSIO_NOBLOCK - { MP_ROM_QSTR(MP_QSTR_nonblocking_readinto), MP_ROM_PTR(&busio_i2c_nonblocking_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_i2c_nonblocking_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_read_is_busy), MP_ROM_PTR(&busio_i2c_nonblocking_read_isbusy_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_write_is_busy), MP_ROM_PTR(&busio_i2c_nonblocking_write_isbusy_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_read), MP_ROM_PTR(&busio_i2c_start_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_write), MP_ROM_PTR(&busio_i2c_start_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_is_busy), MP_ROM_PTR(&busio_i2c_read_is_busy_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_is_busy), MP_ROM_PTR(&busio_i2c_write_is_busy_obj) }, #endif #endif // CIRCUITPY_BUSIO_I2C }; diff --git a/shared-bindings/busio/SPI.c b/shared-bindings/busio/SPI.c index 7fd6b1cf72d98..ab498037be630 100644 --- a/shared-bindings/busio/SPI.c +++ b/shared-bindings/busio/SPI.c @@ -162,13 +162,13 @@ static busio_spi_obj_t *native_spi(mp_obj_t spi_obj) { } #if CIRCUITPY_BUSIO_NOBLOCK -//| def nonblocking_write(self, buffer: ReadableBuffer) -> int: -//| """Start a non-blocking SPI write from ``buffer`` and return the channel. +//| def start_write(self, buffer: ReadableBuffer) -> int: +//| """Start a non-blocking SPI write from ``buffer`` and return the transfer_state. //| //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_obj) { +static mp_obj_t busio_spi_start_write(mp_obj_t self_in, mp_obj_t buffer_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); check_lock(self); @@ -179,15 +179,15 @@ static mp_obj_t busio_spi_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_ob spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, bufinfo.buf, NULL, bufinfo.len); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_nonblocking_write_obj, busio_spi_nonblocking_write); +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_start_write_obj, busio_spi_start_write); -//| def nonblocking_readinto(self, buffer: WriteableBuffer) -> int: -//| """Start a non-blocking SPI read into ``buffer`` and return the channel. +//| def start_read(self, buffer: WriteableBuffer) -> int: +//| """Start a non-blocking SPI read into ``buffer`` and return the transfer_state. //| //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_nonblocking_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t busio_spi_start_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_buffer }; static const mp_arg_t allowed_args[] = { { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, @@ -206,16 +206,16 @@ static mp_obj_t busio_spi_nonblocking_readinto(size_t n_args, const mp_obj_t *po spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, NULL, bufinfo.buf, bufinfo.len); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_nonblocking_readinto_obj, 1, busio_spi_nonblocking_readinto); +MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_start_read_obj, 1, busio_spi_start_read); -//| def nonblocking_write_readinto(self, out_buffer: ReadableBuffer, in_buffer: WriteableBuffer) -> int: -//| """Start a non-blocking SPI write/read transfer and return the channel. +//| def start_transfer(self, out_buffer: ReadableBuffer, in_buffer: WriteableBuffer) -> int: +//| """Start a non-blocking SPI write/read transfer and return the transfer_state. //| //| ``out_buffer`` and ``in_buffer`` must have the same length. //| The SPI object must be locked before calling. //| """ //| -static mp_obj_t busio_spi_nonblocking_write_readinto(mp_obj_t self_in, mp_obj_t out_buffer_obj, mp_obj_t in_buffer_obj) { +static mp_obj_t busio_spi_start_transfer(mp_obj_t self_in, mp_obj_t out_buffer_obj, mp_obj_t in_buffer_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); check_lock(self); @@ -233,21 +233,21 @@ static mp_obj_t busio_spi_nonblocking_write_readinto(mp_obj_t self_in, mp_obj_t spi_transfer_state *state = common_hal_busio_spi_start_transfer(self, out_bufinfo.buf, in_bufinfo.buf, out_bufinfo.len); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_3(busio_spi_nonblocking_write_readinto_obj, busio_spi_nonblocking_write_readinto); +MP_DEFINE_CONST_FUN_OBJ_3(busio_spi_start_transfer_obj, busio_spi_start_transfer); -//| def nonblocking_is_busy(self, channel: int) -> bool: -//| """Return ``True`` while the SPI non-blocking channel is active. +//| def transfer_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the SPI non-blocking transfer_state is active. //| -//| :param int channel: channel returned by SPI non-blocking transfer functions +//| :param int transfer_state: transfer_state returned by `start_write`, `start_read`, or `start_transfer` //| """ //| -static mp_obj_t busio_spi_nonblocking_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { +static mp_obj_t busio_spi_transfer_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_spi_obj_t *self = native_spi(self_in); check_for_deinit(self); spi_transfer_state *state = (spi_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); return mp_obj_new_bool(common_hal_busio_spi_transfer_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_nonblocking_is_busy_obj, busio_spi_nonblocking_is_busy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_spi_transfer_is_busy_obj, busio_spi_transfer_is_busy); #endif //| def configure( @@ -576,10 +576,10 @@ 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) }, #if CIRCUITPY_BUSIO_NOBLOCK - { MP_ROM_QSTR(MP_QSTR_nonblocking_readinto), MP_ROM_PTR(&busio_spi_nonblocking_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_spi_nonblocking_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_write_readinto), MP_ROM_PTR(&busio_spi_nonblocking_write_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_is_busy), MP_ROM_PTR(&busio_spi_nonblocking_is_busy_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_read), MP_ROM_PTR(&busio_spi_start_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_write), MP_ROM_PTR(&busio_spi_start_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_transfer), MP_ROM_PTR(&busio_spi_start_transfer_obj) }, + { MP_ROM_QSTR(MP_QSTR_transfer_is_busy), MP_ROM_PTR(&busio_spi_transfer_is_busy_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&busio_spi_frequency_obj) } diff --git a/shared-bindings/busio/UART.c b/shared-bindings/busio/UART.c index b11f60396661f..71f24692d322b 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -178,10 +178,10 @@ static busio_uart_obj_t *native_uart(mp_obj_t uart_obj) { static void check_for_deinit(busio_uart_obj_t *self); #if CIRCUITPY_BUSIO_NOBLOCK -//| def nonblocking_write(self, buffer: ReadableBuffer) -> int: -//| """Start a non-blocking UART write from ``buffer`` and return the channel.""" +//| def start_write(self, buffer: ReadableBuffer) -> int: +//| """Start a non-blocking UART write from ``buffer`` and return the transfer_state.""" //| -static mp_obj_t busio_uart_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_obj) { +static mp_obj_t busio_uart_start_write(mp_obj_t self_in, mp_obj_t buffer_obj) { busio_uart_obj_t *self = native_uart(self_in); check_for_deinit(self); @@ -191,21 +191,21 @@ static mp_obj_t busio_uart_nonblocking_write(mp_obj_t self_in, mp_obj_t buffer_o uart_transfer_state *state = common_hal_busio_uart_start_write(self, bufinfo.buf, bufinfo.len); return mp_obj_new_int_from_ull((uintptr_t)state); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_nonblocking_write_obj, busio_uart_nonblocking_write); +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_start_write_obj, busio_uart_start_write); -//| def nonblocking_is_busy(self, channel: int) -> bool: -//| """Return ``True`` while the UART non-blocking channel is active. +//| def write_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the UART non-blocking transfer_state is active. //| -//| :param int channel: channel returned by `nonblocking_readinto` or `nonblocking_write` +//| :param int transfer_state: transfer_state returned by `start_write` //| """ //| -static mp_obj_t busio_uart_nonblocking_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { +static mp_obj_t busio_uart_write_is_busy(mp_obj_t self_in, mp_obj_t channel_obj) { busio_uart_obj_t *self = native_uart(self_in); check_for_deinit(self); uart_transfer_state *state = (uart_transfer_state *)(uintptr_t)mp_obj_get_int(channel_obj); return mp_obj_new_bool(common_hal_busio_uart_write_isbusy(state)); } -MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_nonblocking_is_busy_obj, busio_uart_nonblocking_is_busy); +MP_DEFINE_CONST_FUN_OBJ_2(busio_uart_write_is_busy_obj, busio_uart_write_is_busy); #endif @@ -458,8 +458,8 @@ static const mp_rom_map_elem_t busio_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, #if CIRCUITPY_BUSIO_NOBLOCK - { MP_ROM_QSTR(MP_QSTR_nonblocking_write), MP_ROM_PTR(&busio_uart_nonblocking_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_nonblocking_is_busy), MP_ROM_PTR(&busio_uart_nonblocking_is_busy_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_write), MP_ROM_PTR(&busio_uart_start_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_is_busy), MP_ROM_PTR(&busio_uart_write_is_busy_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&busio_uart_reset_input_buffer_obj) }, From d59277a7fef41c59a62d9d3d33491f32d3c48ab7 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Fri, 27 Feb 2026 11:01:58 -0800 Subject: [PATCH 4/4] Busio: Added test suite for nonblocking API --- .../busio/nonblocking_i2c.py | 179 ++++++++++++++++++ .../busio/nonblocking_spi.py | 158 ++++++++++++++++ .../busio/nonblocking_uart.py | 136 +++++++++++++ 3 files changed, 473 insertions(+) create mode 100644 tests/circuitpython-manual/busio/nonblocking_i2c.py create mode 100644 tests/circuitpython-manual/busio/nonblocking_spi.py create mode 100644 tests/circuitpython-manual/busio/nonblocking_uart.py diff --git a/tests/circuitpython-manual/busio/nonblocking_i2c.py b/tests/circuitpython-manual/busio/nonblocking_i2c.py new file mode 100644 index 0000000000000..cc202fb9d61ab --- /dev/null +++ b/tests/circuitpython-manual/busio/nonblocking_i2c.py @@ -0,0 +1,179 @@ +""" +Manual test for busio.I2C nonblocking start_write()/start_read(). + +What this covers: +1) Correct operation path (optional): nonblocking write to an ACKing device. +2) Long write/read path: large-block start_write/start_read with nonblocking checks. +3) Failure path: nonblocking write to a likely-unused address that should NACK. +4) Resource behavior: repeated NACK attempts to detect DMA channel leaks. + +Hardware / wiring: +- Required for failure-path portion: normal I2C pull-ups on SDA/SCL. +- Optional for operation-path portion: any I2C target device connected to SDA/SCL. + If no target is present, the operation-path check is skipped. +- Required for long read/write path: an I2C target at TEST_RW_ADDRESS that accepts + multi-kB writes and returns multi-kB reads without requiring register-address + setup between transactions. A second CircuitPython board acting as a simple + I2C RAM/echo slave is recommended. + +Pin assumptions: +- Uses board.SCL and board.SDA. + +Expected behavior notes: +- Blocking busio I2C writeto() should raise OSError on a NACK address. +- Nonblocking start_write() currently may not surface a NACK as an exception; + this script reports what happens and then checks for post-failure channel reuse. +- For long transfers, start_* should return quickly and transfer should still be + active immediately after return (proving nonblocking behavior). +""" + +import board +import busio +import time + +LONG_BLOCK_LEN = 2048 + + +def wait_for_unlock(i2c, timeout_s=1.0): + deadline = time.monotonic() + timeout_s + while not i2c.try_lock(): + if time.monotonic() >= deadline: + raise RuntimeError("timed out waiting for I2C lock") + time.sleep(0.001) + + +def wait_for_transfer(i2c, state, timeout_s=1.0): + deadline = time.monotonic() + timeout_s + while i2c.write_is_busy(state): + if time.monotonic() >= deadline: + raise RuntimeError("timed out waiting for nonblocking I2C write") + + +def wait_for_read_transfer(i2c, state, timeout_s=2.0): + deadline = time.monotonic() + timeout_s + while i2c.read_is_busy(state): + if time.monotonic() >= deadline: + raise RuntimeError("timed out waiting for nonblocking I2C read") + + +def first_unused_address(used): + for addr in range(0x08, 0x78): + if addr not in used: + return addr + raise RuntimeError("no unused I2C addresses available") + + +def main(): + if not hasattr(busio.I2C, "start_write") or not hasattr(busio.I2C, "write_is_busy"): + print("SKIP: nonblocking I2C API not available on this build") + return + + have_read_noblock = hasattr(busio.I2C, "start_read") and hasattr(busio.I2C, "read_is_busy") + + i2c = busio.I2C(board.SCL, board.SDA, frequency=10000) + + try: + wait_for_unlock(i2c) + found = i2c.scan() + print("I2C scan:", [hex(x) for x in found]) + + ack_addr = found[0] if found else None + nack_addr = first_unused_address(set(found)) + print("Using NACK test address:", hex(nack_addr)) + + if ack_addr is not None: + print("Operation check on ACK address:", hex(ack_addr)) + state = i2c.start_write(ack_addr, b"\x00", end=True) + wait_for_transfer(i2c, state) + print("PASS: nonblocking write completed on ACK device") + else: + print("SKIP: no ACKing I2C device found; operation-path check skipped") + + if ack_addr is None: + print("SKIP: long write/read (no I2C target address available)") + else: + if True: + print("SKIP: Long write (target device may not support it)") + else: + long_write_buf = bytearray(LONG_BLOCK_LEN) + for idx in range(LONG_BLOCK_LEN): + long_write_buf[idx] = idx & 0xFF + + t0 = time.monotonic() + write_state = i2c.start_write(ack_addr, long_write_buf, end=True) + start_return_s = time.monotonic() - t0 + write_busy_immediate = i2c.write_is_busy(write_state) + wait_for_transfer(i2c, write_state, timeout_s=4.0) + + print( + "Long write len:", + LONG_BLOCK_LEN, + "start_return_ms:", + int(start_return_s * 1000), + ) + if write_busy_immediate: + print("PASS: long start_write returned before transfer completion") + else: + print("WARN: long start_write completed before first busy poll") + + if have_read_noblock: + long_read_buf = bytearray(LONG_BLOCK_LEN) + + t0 = time.monotonic() + read_state = i2c.start_read(ack_addr, long_read_buf, end=True) + read_start_return_s = time.monotonic() - t0 + read_busy_immediate = i2c.read_is_busy(read_state) + wait_for_read_transfer(i2c, read_state, timeout_s=4.0) + + print( + "Long read len:", + LONG_BLOCK_LEN, + "start_return_ms:", + int(read_start_return_s * 1000), + ) + if read_busy_immediate: + print("PASS: long start_read returned before transfer completion") + else: + print("WARN: long start_read completed before first busy poll") + + # Data content is target-dependent; only transfer completion is asserted here. + print("PASS: long start_read transfer completed") + else: + print("SKIP: long start_read (API not available on this build)") + + # Blocking behavior reference for failure correctness. + try: + i2c.writeto(nack_addr, b"\xaa") + print("WARN: blocking writeto() unexpectedly succeeded at NACK address") + except OSError as exc: + print("PASS: blocking writeto() failed on NACK address:", repr(exc)) + + # Nonblocking failure stress: if DMA channels leak after each failure, + # this loop should eventually fail to start a transfer. + iterations = 128 + print("Running", iterations, "nonblocking NACK attempts for leak detection") + for idx in range(iterations): + try: + state = i2c.start_write(nack_addr, b"\xde\xad\xbe\xef", end=True) + wait_for_transfer(i2c, state) + except Exception as exc: + print("FAIL: nonblocking NACK loop failed at iteration", idx, repr(exc)) + return + + print("PASS: nonblocking NACK stress loop completed without channel exhaustion") + + # Post-failure sanity: verify the bus still performs a known operation. + if ack_addr is not None: + state = i2c.start_write(ack_addr, b"\x01", end=True) + wait_for_transfer(i2c, state) + print("PASS: post-failure ACK write succeeded") + else: + print("INFO: no ACK device available for post-failure functional check") + + finally: + if i2c.try_lock(): + i2c.unlock() + i2c.deinit() + + +main() diff --git a/tests/circuitpython-manual/busio/nonblocking_spi.py b/tests/circuitpython-manual/busio/nonblocking_spi.py new file mode 100644 index 0000000000000..c57e99d71b97f --- /dev/null +++ b/tests/circuitpython-manual/busio/nonblocking_spi.py @@ -0,0 +1,158 @@ +""" +Manual test for nonblocking busio.SPI transfer modes. + +Hardware / wiring: +- Connect MOSI to MISO (SPI loopback). +- Use board SCK/MOSI/MISO pins. +- No CS pin is required for this loopback-only transfer test. + +What this covers: +1) Read-only mode (start_read). +2) Write-only mode (start_write). +3) Full-duplex start_transfer() with different buffers. +4) Full-duplex start_transfer() with the same buffer object for in/out. +5) Full-duplex start_transfer() with same backing buffer but different indexes + (memoryview slices). +6) Full-duplex start_transfer() with a large buffer, verifying nonblocking start. + +Expected behavior: +- Every transfer returns a transfer_state and completes (transfer_is_busy -> False). +- With MOSI<->MISO loopback, received bytes should mirror transmitted bytes. +- For large transfers, start_transfer() should return quickly and transfer should + still be active immediately after return (nonblocking behavior). +""" + +import board +import busio +import time + + +LONG_TRANSFER_LEN = 4096 + + +def wait_for_done(spi, state, timeout_s=1.0): + deadline = time.monotonic() + timeout_s + while spi.transfer_is_busy(state): + if time.monotonic() >= deadline: + raise RuntimeError("timed out waiting for SPI nonblocking transfer") + + +def get_pin(name): + if hasattr(board, name): + return getattr(board, name) + return None + + +def main(): + required_api = ( + hasattr(busio.SPI, "start_write") + and hasattr(busio.SPI, "start_read") + and hasattr(busio.SPI, "start_transfer") + and hasattr(busio.SPI, "transfer_is_busy") + ) + if not required_api: + print("SKIP: nonblocking SPI API not available on this build") + return + + sck = get_pin("SCK") + mosi = get_pin("MOSI") + miso = get_pin("MISO") + if sck is None or mosi is None or miso is None: + print("SKIP: board does not expose SCK/MOSI/MISO") + return + + spi = busio.SPI(sck, MOSI=mosi, MISO=miso) + while not spi.try_lock(): + pass + + try: + spi.configure(baudrate=500000, polarity=0, phase=0) + + # 1) Read-only mode via start_read(). With loopback, MOSI transmits 0x00, + # so MISO should read back 0x00 bytes. + read_only_buf = bytearray(8) + state = spi.start_read(read_only_buf) + wait_for_done(spi, state) + if read_only_buf == b"\x00" * len(read_only_buf): + print("PASS: read-only start_read") + else: + print("FAIL: read-only start_read data", bytes(read_only_buf)) + return + + # 2) Write-only mode via start_write(). Completion-only check. + write_only_data = b"\xa5\x5a\x11\x22\x33\x44\x55\x66" + state = spi.start_write(write_only_data) + wait_for_done(spi, state) + print("PASS: write-only start_write") + + # 3) Full duplex with different buffers. + out_a = bytearray(b"ABCDEFGH") + in_a = bytearray(len(out_a)) + state = spi.start_transfer(out_a, in_a) + wait_for_done(spi, state) + if bytes(in_a) == bytes(out_a): + print("PASS: start_transfer different buffers") + else: + print("FAIL: start_transfer different buffers", bytes(out_a), bytes(in_a)) + return + + # 4) Full duplex with same buffer for in/out. + both = bytearray(b"qrstuvwx") + expected_both = bytes(both) + state = spi.start_transfer(both, both) + wait_for_done(spi, state) + if bytes(both) == expected_both: + print("PASS: start_transfer same buffer") + else: + print("FAIL: start_transfer same buffer", expected_both, bytes(both)) + return + + # 5) Full duplex with same backing buffer, different indexes. + # out_view: shared[0:8], in_view: shared[4:12] (overlapping slices). + shared = bytearray(b"12345678abcdefgh") + out_view = memoryview(shared)[0:8] + in_view = memoryview(shared)[4:12] + expected_shift = bytes(out_view) + state = spi.start_transfer(out_view, in_view) + wait_for_done(spi, state) + got_shift = bytes(memoryview(shared)[4:12]) + if got_shift == expected_shift: + print("PASS: start_transfer same buffer different indexes") + else: + print("FAIL: start_transfer indexed slices", expected_shift, got_shift) + print("Shared:", bytes(shared)) + return + + # 6) Large full-duplex transfer, verify nonblocking start behavior. + long_out = bytearray(LONG_TRANSFER_LEN) + for idx in range(LONG_TRANSFER_LEN): + long_out[idx] = idx & 0xFF + long_in = bytearray(LONG_TRANSFER_LEN) + + t0 = time.monotonic() + state = spi.start_transfer(long_out, long_in) + start_return_s = time.monotonic() - t0 + busy_immediate = spi.transfer_is_busy(state) + wait_for_done(spi, state, timeout_s=4.0) + + if bytes(long_in) != bytes(long_out): + print("FAIL: long start_transfer data mismatch") + return + + print( + "Long transfer len:", LONG_TRANSFER_LEN, "start_return_ms:", int(start_return_s * 1000) + ) + if busy_immediate: + print("PASS: long start_transfer returned before completion") + else: + print("WARN: long start_transfer completed before first busy poll") + print("PASS: long start_transfer data") + + print("ALL PASS") + + finally: + spi.unlock() + spi.deinit() + + +main() diff --git a/tests/circuitpython-manual/busio/nonblocking_uart.py b/tests/circuitpython-manual/busio/nonblocking_uart.py new file mode 100644 index 0000000000000..5b9ffd2579de0 --- /dev/null +++ b/tests/circuitpython-manual/busio/nonblocking_uart.py @@ -0,0 +1,136 @@ +""" +Manual test for busio.UART nonblocking start_write()/write_is_busy(), and +partial read timeout behavior. + +Hardware / wiring: +- Connect TX to RX on the same board (loopback wire). +- Optional but recommended: common GND reference if using external transceiver. + +Pin assumptions: +- Uses board.TX and board.RX. + +What this covers: +1) Correct operation path: nonblocking write and full readback on loopback. +2) Failure/timeout path: request more bytes than sent and verify readinto() + returns a partial count after timeout. +3) Large-transfer path: verify start_write() is nonblocking for a large buffer. + +Expected behavior: +- start_write() returns a transfer_state token. +- write_is_busy(token) is True until the transfer completes. +- readinto(buf) returns the number of bytes read; with timeout and partial data, + that number should be >0 and = deadline: + raise RuntimeError("timed out waiting for nonblocking UART write") + + +def clear_rx(uart): + while uart.in_waiting: + uart.read(uart.in_waiting) + + +def read_exact(uart, nbytes, timeout_s=3.0): + out = bytearray() + deadline = time.monotonic() + timeout_s + while len(out) < nbytes: + chunk = uart.read(nbytes - len(out)) + if chunk: + out.extend(chunk) + deadline = time.monotonic() + timeout_s + if time.monotonic() >= deadline: + break + return bytes(out) + + +def main(): + if not hasattr(busio.UART, "start_write") or not hasattr(busio.UART, "write_is_busy"): + print("SKIP: nonblocking UART API not available on this build") + return + + uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=115200, timeout=0.05) + time.sleep(0.05) + + try: + clear_rx(uart) + + payload = b"CP-NONBLOCK-OK" + state = uart.start_write(payload) + wait_for_write_done(uart, state) + + rx = uart.read(len(payload)) + if rx == payload: + print("PASS: nonblocking UART write/read loopback operation") + else: + print("FAIL: loopback mismatch", rx, payload) + return + + clear_rx(uart) + + partial_payload = b"XY" + state = uart.start_write(partial_payload) + wait_for_write_done(uart, state) + + target_len = 6 + read_buf = bytearray(target_len) + count = uart.readinto(read_buf) + + if count is None: + print("FAIL: readinto() returned None, expected partial count") + return + + if 0 < count < target_len and bytes(read_buf[:count]) == partial_payload: + print("PASS: partial-timeout read returned", count, "bytes:", bytes(read_buf[:count])) + else: + print("FAIL: partial-timeout behavior unexpected") + print(" count:", count) + print(" data:", bytes(read_buf[: count or 0])) + return + + clear_rx(uart) + + long_payload = bytearray(LONG_WRITE_LEN) + for idx in range(LONG_WRITE_LEN): + long_payload[idx] = idx & 0xFF + + t0 = time.monotonic() + state = uart.start_write(long_payload) + start_return_s = time.monotonic() - t0 + busy_immediate = uart.write_is_busy(state) + + long_rx = read_exact(uart, LONG_WRITE_LEN, timeout_s=4.0) + if long_rx != bytes(long_payload): + print("FAIL: long nonblocking UART write/read mismatch") + print(" got:", len(long_rx), "expected:", LONG_WRITE_LEN) + return + + if uart.write_is_busy(state): + print("WARN: UART is still busy after write completion") + return + + print("Long write len:", LONG_WRITE_LEN, "start_return_ms:", int(start_return_s * 1000)) + if busy_immediate: + print("PASS: long start_write returned before completion") + else: + print("WARN: long start_write completed before first busy poll") + print("PASS: long start_write data") + + finally: + uart.deinit() + + +main()