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/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_mpconfig.mk b/py/circuitpy_mpconfig.mk index 56fc695742133..3bf9e69b43bc4 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -220,6 +220,12 @@ 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_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 a2724e60c3422..58565c55716f7 100644 --- a/shared-bindings/busio/I2C.c +++ b/shared-bindings/busio/I2C.c @@ -7,6 +7,8 @@ // 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/util.h" @@ -92,6 +94,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 +122,112 @@ static void check_lock(busio_i2c_obj_t *self) { } } +#if CIRCUITPY_BUSIO_NOBLOCK +//| 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: transfer_state used by this transfer +//| :rtype: int +//| """ +//| +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} }, + { 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); + + 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_start_read_obj, 1, busio_i2c_start_read); + +//| 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: transfer_state used by this transfer +//| :rtype: int +//| """ +//| +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} }, + { 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); + + 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_start_write_obj, 1, busio_i2c_start_write); + +//| def write_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the I2C non-blocking write transfer_state is active. +//| +//| :param int transfer_state: transfer_state returned by `start_read` or `start_write` +//| """ +//| +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_write_is_busy_obj, busio_i2c_write_is_busy); + +//| def read_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the I2C non-blocking read transfer_state is active. +//| +//| :param int transfer_state: transfer_state returned by `start_read` or `start_write` +//| """ +//| +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_read_is_busy_obj, busio_i2c_read_is_busy); +#endif + //| def probe(self, address: int) -> List[int]: //| """Check if a device at the specified address responds. //| @@ -121,7 +237,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 +255,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 +278,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 +289,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 +322,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 +384,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 +464,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 +519,12 @@ 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_NOBLOCK + { 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/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 24c02a16e634b..ab498037be630 100644 --- a/shared-bindings/busio/SPI.c +++ b/shared-bindings/busio/SPI.c @@ -8,6 +8,7 @@ // busio.SPI class. #include +#include #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/busio/SPI.h" @@ -152,6 +153,103 @@ 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_NOBLOCK +//| 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_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); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); + + 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_start_write_obj, busio_spi_start_write); + +//| 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_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} }, + }; + + 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_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + 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_start_read_obj, 1, busio_spi_start_read); + +//| 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_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); + + 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")); + } + + 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_start_transfer_obj, busio_spi_start_transfer); + +//| def transfer_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the SPI non-blocking transfer_state is active. +//| +//| :param int transfer_state: transfer_state returned by `start_write`, `start_read`, or `start_transfer` +//| """ +//| +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_transfer_is_busy_obj, busio_spi_transfer_is_busy); +#endif + //| def configure( //| self, *, baudrate: int = 100000, polarity: int = 0, phase: int = 0, bits: int = 8 //| ) -> None: @@ -186,7 +284,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 +311,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 +322,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 +353,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 +420,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 +497,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 +549,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 +575,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_NOBLOCK + { 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) } #endif // CIRCUITPY_BUSIO_SPI 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 4714c3ee0733a..71f24692d322b 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -175,6 +175,39 @@ 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_NOBLOCK +//| 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_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); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ); + + 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_start_write_obj, busio_uart_start_write); + +//| def write_is_busy(self, transfer_state: int) -> bool: +//| """Return ``True`` while the UART non-blocking transfer_state is active. +//| +//| :param int transfer_state: transfer_state returned by `start_write` +//| """ +//| +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_write_is_busy_obj, busio_uart_write_is_busy); +#endif + //| def deinit(self) -> None: //| """Deinitialises the UART and releases any hardware resources for reuse.""" @@ -424,6 +457,10 @@ 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_NOBLOCK + { 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) }, 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/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() 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