diff --git a/.gitignore b/.gitignore index 2d20cb18970e8..344ebf1d549c9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ user.props # MacOS desktop metadata files .DS_Store + +# VScode config +.vscode diff --git a/docs/rp2/general.rst b/docs/rp2/general.rst index 6a8958d172a84..01457b6232f20 100644 --- a/docs/rp2/general.rst +++ b/docs/rp2/general.rst @@ -34,3 +34,4 @@ The peripherals include: * 16 PWM channels * USB 1.1 controller * 8 PIO state machines +* 12 DMA channels and 4 pacing timers diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst index 430c130c6e865..9c721dfa907f8 100644 --- a/docs/rp2/quickref.rst +++ b/docs/rp2/quickref.rst @@ -298,6 +298,50 @@ See :ref:`machine.WDT `. :: The maximum value for timeout is 8388 ms. +DMA (Direct memory access) +-------------------------- + +The RP2040 has a DMA subsytem that has 12 hardware controlled DMA channels and 4 pacing timers. + +Example using DMA to fade an LED:: + + from rp2 import DMA, Timer + from machine import Pin, PWM + + led = Pin(16, Pin.OUT) + pwm1 = PWM(led) + pwm1.freq(1000) + pwm1.duty_u16(0) + + # PWM 0A for GPIO16. Lower 16 bits of CC + dest = 0x4005000e + dma = DMA() + dma.claim() + t = Timer() + + t.claim() + # go as slowly as possible + t.set(0x0001, 0xffff) + + data_size = dma.DMA_SIZE_16 + buffer_size = 32768 + from_buffer = bytearray(buffer_size) + for i in range(0, buffer_size, 2): + from_buffer[i]= i & 0xff + from_buffer[i+1]= (i>>8) & 0xff + + dma.write_from(from_buffer=from_buffer, + to_address=dest, + transfer_count=buffer_size>>data_size, + dreq=dreq, + data_size=data_size) + while dma.isbusy(): + time.sleep_ms(1) + + t.unclaim() + dma.unclaim() + + OneWire driver -------------- diff --git a/examples/rp2/dma_write_from.py b/examples/rp2/dma_write_from.py new file mode 100644 index 0000000000000..5f745ceaa9842 --- /dev/null +++ b/examples/rp2/dma_write_from.py @@ -0,0 +1,59 @@ +import time +from rp2 import DMA, Timer +from machine import Pin, PWM + + +def led(): + print("DMA write_from demo") + led = Pin(16, Pin.OUT) + pwm1 = PWM(led) + pwm1.freq(1000) + pwm1.duty_u16(0) + + # pwm 0A attached to GPIO 16 + dest = 0x4005000E + dma = DMA() + dma.claim() + t = Timer() + + X = 0x0001 + Y = 0xFFFF + t.claim() + t.set(X, Y) + + print(f"Starting\nDMA:{dma}\nTimer:{t}") + + dreq = t.dreq() + + data_size = dma.DMA_SIZE_16 + buffer_size = 32768 + from_buffer = bytearray(buffer_size) + + # create a 16 bit fade + for i in range(0, buffer_size, 2): + from_buffer[i] = i & 0xFF + from_buffer[i + 1] = (i >> 8) & 0xFF + + start = time.ticks_ms() + + dma.write_from( + from_buffer=from_buffer, + to_address=dest, + transfer_count=buffer_size >> data_size, + dreq=dreq, + data_size=data_size, + ) + while dma.isbusy(): + time.sleep_ms(100) + + end = time.ticks_ms() + + t.unclaim() + dma.unclaim() + print(f"Write from fade took {end-start}mS") + print("Done") + pwm1.duty_u16(0) + + +if __name__ == "__main__": + led() diff --git a/examples/rp2/dma_write_from_irq.py b/examples/rp2/dma_write_from_irq.py new file mode 100644 index 0000000000000..d5e765a4c2bd0 --- /dev/null +++ b/examples/rp2/dma_write_from_irq.py @@ -0,0 +1,77 @@ +import uasyncio +import time +from rp2 import DMA, Timer +from machine import Pin, PWM + + +async def led(): + print("DMA write_from demo") + led = Pin(16, Pin.OUT) + pwm1 = PWM(led) + pwm1.freq(1000) + pwm1.duty_u16(0) + + # pwm 0A attached to GPIO 16 + dest = 0x4005000E + dma = DMA() + dma.claim() + t = Timer() + + # slow the timer to it's slowest rate + X = 0x0001 + Y = 0xFFFF + t.claim() + t.set(X, Y) + + print(f"Starting\nDMA:{dma}\nTimer:{t}") + + dreq = t.dreq() + + data_size = dma.DMA_SIZE_16 + buffer_size = 32768 + from_buffer = bytearray(buffer_size) + + # create a 16 bit fade + for i in range(0, buffer_size, 2): + from_buffer[i] = i & 0xFF + from_buffer[i + 1] = (i >> 8) & 0xFF + + start = time.ticks_ms() + + e = uasyncio.ThreadSafeFlag() + + def _callback(dma_status): + print("callback") + e.set() + + dma.write_from( + from_buffer=from_buffer, + to_address=dest, + transfer_count=buffer_size >> data_size, + dreq=dreq, + data_size=data_size, + handler=_callback, + ) + + await e.wait() + + end = time.ticks_ms() + + t.unclaim() + dma.unclaim() + print(f"Write from fade took {end-start}mS") + print("Done") + pwm1.duty_u16(0) + + +async def async_led(): + t = uasyncio.create_task(led()) + await t + + +def main(): + uasyncio.run(async_led()) + + +if __name__ == "__main__": + main() diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 357125e734c3a..35d2a0283a787 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -109,6 +109,7 @@ set(MICROPY_SOURCE_PORT fatfs_port.c machine_adc.c machine_bitstream.c + dma.c machine_i2c.c machine_i2s.c machine_pin.c @@ -140,6 +141,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c ${PROJECT_SOURCE_DIR}/machine_adc.c + ${PROJECT_SOURCE_DIR}/dma.c ${PROJECT_SOURCE_DIR}/machine_i2c.c ${PROJECT_SOURCE_DIR}/machine_i2s.c ${PROJECT_SOURCE_DIR}/machine_pin.c diff --git a/ports/rp2/README.md b/ports/rp2/README.md index 078919ce81940..8e52eb6b23051 100644 --- a/ports/rp2/README.md +++ b/ports/rp2/README.md @@ -10,7 +10,7 @@ Currently supported features are: - `uos` module with VFS support. - `machine` module with the following classes: `Pin`, `ADC`, `PWM`, `I2C`, `SPI`, `SoftI2C`, `SoftSPI`, `Timer`, `UART`, `WDT`. -- `rp2` module with programmable IO (PIO) support. +- `rp2` module with programmable IO (PIO) support and DMA module. See the `examples/rp2/` directory for some example code. diff --git a/ports/rp2/dma.c b/ports/rp2/dma.c new file mode 100644 index 0000000000000..5622c8b0a41ed --- /dev/null +++ b/ports/rp2/dma.c @@ -0,0 +1,621 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022-2023 Mark Burton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "string.h" + +#include "py/gc.h" +#include "py/mphal.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "shared/runtime/mpirq.h" + +#include "hardware/dma.h" + +#define RP2_DEBUG 0 + +#if RP2_DEBUG + #define DBG_PRINTF(...) mp_printf(&mp_plat_print, "rp2.dma: " __VA_ARGS__) +#else + #define DBG_PRINTF(...) +#endif + +extern const mp_obj_type_t dma_DMA_type; +extern const mp_obj_type_t dma_Timer_type; + +/***************************************************************************** + * Data types +*/ +typedef struct _dma_DMA_obj_t { + mp_obj_base_t base; + uint8_t dma_channel; + uint32_t requests; + uint32_t transfers; + mp_obj_t handler; +} dma_DMA_obj_t; + +typedef struct _dma_Timer_obj_t { + mp_obj_base_t base; + uint8_t timer; +} dma_Timer_obj_t; + +/****************************************************************************** + * Private methods +******************************************************************************/ +void dma_irq_handler(uint32_t irq_status) { + DBG_PRINTF("dma_irq_handler 0x%08x\n", irq_status); + dma_DMA_obj_t *dma_callback = NULL; + mp_obj_t callback = mp_const_none; + + for (uint8_t i = 0; i < NUM_DMA_CHANNELS; i++) + { + if (irq_status & (1 << i)) { + dma_callback = MP_STATE_PORT(dma_DMA_obj_all)[i]; + if (dma_callback) { + callback = dma_callback->handler; + if (callback != mp_const_none) { + mp_sched_lock(); + // When executing code within a handler we must lock the GC to prevent + // any memory allocations. We must also catch any exceptions. + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + uint32_t status = dma_channel_hw_addr(i)->ctrl_trig; + mp_call_function_1(callback, mp_obj_new_int_from_uint((status >> 29) & 3)); + dma_callback->handler = mp_const_none; + nlr_pop(); + } else { + dma_callback->handler = mp_const_none; + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + gc_unlock(); + mp_sched_unlock(); + } + } + } + } +} + +void dma_irq_handler0() { + uint32_t insts0 = dma_hw->ints0; + // clear the dma irq + dma_hw->ints0 = dma_hw->ints0; + dma_irq_handler(insts0); +} + +void dma_irq_handler1() { + uint32_t insts1 = dma_hw->ints1; + // clear the dma irq + dma_hw->ints1 = dma_hw->ints1; + dma_irq_handler(insts1); +} + +void dma_irq_init(dma_DMA_obj_t *self, mp_obj_t handler, uint32_t irq) { + DBG_PRINTF("dma_irq_init %d\n", irq); + self->handler = handler; + MP_STATE_PORT(dma_DMA_obj_all)[self->dma_channel] = self; + + // work around to prevent spurious IRQ's + dma_irqn_set_channel_enabled(irq, self->dma_channel, false); + dma_channel_abort(self->dma_channel); + // clear the spurious IRQ (if there was one) + dma_channel_acknowledge_irq0(self->dma_channel); + if (irq == DMA_IRQ_0) { + dma_irqn_set_channel_enabled(0, self->dma_channel, true); + } else { + dma_irqn_set_channel_enabled(1, self->dma_channel, true); + } + + if (irq == DMA_IRQ_0) { + irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler0); + } else { + irq_set_exclusive_handler(DMA_IRQ_1, dma_irq_handler1); + } + + irq_set_enabled(irq, true); +} + +void check_init(dma_DMA_obj_t *self) { + if (self->dma_channel == 0xff) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA channel not claimed")); + } +} + +void check_busy(dma_DMA_obj_t *self) { + if (dma_channel_is_busy(self->dma_channel)) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA channel is already active")); + } +} + +void check_irq(uint32_t irq) { + if (irq != DMA_IRQ_0 && irq != DMA_IRQ_1) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA invalid IRQ %d"), irq); + } +} + +uint32_t dma_get_error_status(uint channel) { + dma_channel_hw_t *dma = dma_channel_hw_addr(channel); + return (dma->ctrl_trig >> 29) & 7; +} + +void dma_check_and_raise_status(dma_DMA_obj_t *self, dma_channel_config *config) { + uint32_t status = dma_get_error_status(self->dma_channel); + if (status & 0x04) { + dma_channel_hw_t *dma = dma_channel_hw_addr(self->dma_channel); + channel_config_set_enable(config, false); + // disable the channel on IRQ0 + dma_channel_set_irq0_enabled(self->dma_channel, false); + // abort the channel + dma_channel_abort(self->dma_channel); + // clear the spurious IRQ (if there was one) + dma_channel_acknowledge_irq0(self->dma_channel); + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA transfer error %d read addr: 0x%08x write addr: 0x%08x"), status & 3, dma->read_addr, dma->write_addr); + } +} + +void dma_start_request(dma_DMA_obj_t *self, + dma_channel_config *config, + void *to_address_ptr, + void *from_address_ptr, + uint32_t transfer_count, + uint32_t dreq, + uint32_t data_size, + mp_obj_t handler, + uint32_t irq) { + if (dreq != 0) { + channel_config_set_dreq(config, dreq); + } + + channel_config_set_transfer_data_size(config, data_size); + + // IRQ stuff + if (mp_obj_is_callable(handler)) { + dma_irq_init(self, handler, irq); + } else { + dma_channel_set_irq0_enabled(self->dma_channel, false); + dma_channel_set_irq1_enabled(self->dma_channel, false); + } + + // configure a one shot DMA request. + dma_channel_configure(self->dma_channel, config, + to_address_ptr, // Destinatinon pointer + from_address_ptr, // Source pointer + transfer_count, // Number of transfers + true // Start immediately + ); + + dma_check_and_raise_status(self, config); +} + +MP_REGISTER_ROOT_POINTER(struct _dma_DMA_obj_t *dma_DMA_obj_all[NUM_DMA_CHANNELS]); + +void dma_init(void) { + memset(MP_STATE_PORT(dma_DMA_obj_all), 0, sizeof(MP_STATE_PORT(dma_DMA_obj_all))); +} + +STATIC mp_obj_t dma_DMA_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + dma_DMA_obj_t *self = mp_obj_malloc(dma_DMA_obj_t, &dma_DMA_type); + self->dma_channel = 0xff; + self->handler = mp_const_none; + self->requests = 0; + self->transfers = 0; + return MP_OBJ_FROM_PTR(self); +} + +STATIC void dma_DMA_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->dma_channel, self->requests, self->transfers); +} + +STATIC mp_obj_t dma_DMA_claim(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->dma_channel = dma_claim_unused_channel(false); + if (self->dma_channel == -1) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA No channels available")); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_claim_obj, dma_DMA_claim); + +STATIC mp_obj_t dma_DMA_unclaim(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->dma_channel != 0xff) { + dma_irqn_set_channel_enabled(DMA_IRQ_0, self->dma_channel, false); + dma_channel_abort(self->dma_channel); + dma_channel_unclaim(self->dma_channel); + self->dma_channel = 0xff; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_unclaim_obj, dma_DMA_unclaim); + +STATIC mp_obj_t dma_DMA_isclaimed(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(dma_channel_is_claimed(self->dma_channel)); +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_isclaimed_obj, dma_DMA_isclaimed); + + +/***************************************************************************** + * write_from + * + * Arguments + * from_buffer - bytearray + * data_size - 1,2,4 bytes (use constant) + * transfer_count - + * to_address - address to send data + * dreq - optional throttle transfer. Default is to go as fast as possible + * handler - optional IRQ handler + * irq - optional irq channel. Either DMA_IRQ_0 or DMA_IRQ_1 + * +*******************************************************************************/ +STATIC mp_obj_t dma_DMA_write_from(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_from_buffer, ARG_to_address, ARG_dreq, ARG_transfer_count, ARG_data_size, ARG_handler, ARG_irq}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_from_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_to_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_dreq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_transfer_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_data_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DMA_SIZE_8} }, + { MP_QSTR_handler, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_irq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DMA_IRQ_0} }, + + }; + DBG_PRINTF("write_from\n"); + 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); + + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + check_init(self); + check_busy(self); + + void *to_address_ptr = (void *)args[ARG_to_address].u_int; + uint32_t dreq = args[ARG_dreq].u_int; + uint32_t transfer_count = args[ARG_transfer_count].u_int; + uint32_t data_size = args[ARG_data_size].u_int; + mp_obj_t handler = args[ARG_handler].u_obj; + uint32_t irq = args[ARG_irq].u_int; + + check_irq(irq); + + mp_buffer_info_t bufinfo; + bufinfo.buf = NULL; + mp_get_buffer_raise(args[ARG_from_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + + if (transfer_count == 0) { + transfer_count = bufinfo.len >> data_size; + } + + self->requests++; + self->transfers += transfer_count; + + dma_channel_config config = dma_channel_get_default_config(self->dma_channel); + + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, false); + + dma_start_request(self, &config, to_address_ptr, bufinfo.buf, transfer_count, dreq, data_size, handler, irq); + + return mp_obj_new_int(bufinfo.len); + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dma_DMA_write_from_obj, 1, dma_DMA_write_from); + + +/***************************************************************************** + * read_into + * + * Arguments + * from_address - address to send data + * data_size - 1,2,4 bytes (use constant) + * transfer_count - + * to_buffer - bytearray + * dreq - optional throttle transfer. Default is to go as fast as possible + * handler - optional IRQ handler + * irq - optional irq channel. Either DMA_IRQ_0 or DMA_IRQ_1 + * +*******************************************************************************/ +STATIC mp_obj_t dma_DMA_read_into(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_from_address, ARG_to_buffer, ARG_transfer_count, ARG_data_size, ARG_dreq, ARG_handler, ARG_irq}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_from_address, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_to_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_transfer_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_data_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DMA_SIZE_8} }, + { MP_QSTR_dreq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_handler, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_irq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DMA_IRQ_0} }, + }; + DBG_PRINTF("read_into\n"); + 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); + + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_init(self); + check_busy(self); + + void *from_address_ptr = (void *)(args[ARG_from_address].u_int); + + uint32_t dreq = args[ARG_dreq].u_int; + uint32_t transfer_count = args[ARG_transfer_count].u_int; + uint32_t data_size = args[ARG_data_size].u_int; + mp_obj_t handler = args[ARG_handler].u_obj; + uint32_t irq = args[ARG_irq].u_int; + + check_irq(irq); + + mp_buffer_info_t bufinfo; + bufinfo.buf = NULL; + mp_get_buffer_raise(args[ARG_to_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + if (transfer_count == 0) { + transfer_count = bufinfo.len >> data_size; + } + + self->requests++; + self->transfers += transfer_count; + + dma_channel_config config = dma_channel_get_default_config(self->dma_channel); + channel_config_set_read_increment(&config, false); + channel_config_set_write_increment(&config, true); + + dma_start_request(self, &config, bufinfo.buf, from_address_ptr, transfer_count, dreq, data_size, handler, irq); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dma_DMA_read_into_obj, 1, dma_DMA_read_into); + +/***************************************************************************** + * copy + * + * Arguments + * from_buffer - source bytearray + * to_buffer - bytearray + * handler - optional IRQ handler + * irq - optional irq channel. Either DMA_IRQ_0 or DMA_IRQ_1 + * +*******************************************************************************/ +STATIC mp_obj_t dma_DMA_copy(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_from_buffer, ARG_to_buffer, ARG_handler, ARG_irq}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_from_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_to_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_handler, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_irq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DMA_IRQ_0} }, + }; + DBG_PRINTF("copy\n"); + 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); + + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + uint32_t irq = args[ARG_irq].u_int; + + check_init(self); + check_busy(self); + check_irq(irq); + + mp_buffer_info_t from_buffer; + from_buffer.buf = NULL; + mp_get_buffer_raise(args[ARG_from_buffer].u_obj, &from_buffer, MP_BUFFER_READ); + + mp_buffer_info_t to_buffer; + to_buffer.buf = NULL; + mp_get_buffer_raise(args[ARG_to_buffer].u_obj, &to_buffer, MP_BUFFER_WRITE); + mp_obj_t handler = args[ARG_handler].u_obj; + + if (from_buffer.len > to_buffer.len) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA buffer overrun %d > %d "), from_buffer.len, to_buffer.len); + } + + self->requests++; + self->transfers += from_buffer.len; + + dma_channel_config config = dma_channel_get_default_config(self->dma_channel); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, true); + + dma_start_request(self, &config, to_buffer.buf, from_buffer.buf, from_buffer.len, 0x3f, DMA_SIZE_8, handler, irq); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dma_DMA_copy_obj, 1, dma_DMA_copy); + +STATIC mp_obj_t dma_DMA_abort(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + + dma_channel_set_irq0_enabled(self->dma_channel, false); + // abort the channel + dma_channel_abort(self->dma_channel); + // clear the spurious IRQ (if there was one) + dma_channel_acknowledge_irq0(self->dma_channel); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_abort_obj, dma_DMA_abort); + +STATIC mp_obj_t dma_DMA_isbusy(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(dma_channel_is_busy(self->dma_channel)); +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_isbusy_obj, dma_DMA_isbusy); + +STATIC mp_obj_t dma_DMA_error_status(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t status = dma_channel_hw_addr(self->dma_channel)->ctrl_trig; + return mp_obj_new_int_from_uint((status >> 29) & 3); +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_error_status_obj, dma_DMA_error_status); + +STATIC mp_obj_t dma_DMA_clear_error(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + hw_set_bits(&dma_hw->ch[self->dma_channel].al1_ctrl, 3u << 29); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_clear_error_obj, dma_DMA_clear_error); + +STATIC mp_obj_t dma_DMA_request_count(mp_obj_t self_in) { + dma_DMA_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->requests); + +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_DMA_request_count_obj, dma_DMA_request_count); + +STATIC const mp_rom_map_elem_t dma_DMA_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_claim), MP_ROM_PTR(&dma_DMA_claim_obj) }, + { MP_ROM_QSTR(MP_QSTR_unclaim), MP_ROM_PTR(&dma_DMA_unclaim_obj) }, + { MP_ROM_QSTR(MP_QSTR_isclaimed), MP_ROM_PTR(&dma_DMA_isclaimed_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_from), (mp_obj_t)&dma_DMA_write_from_obj }, + { MP_ROM_QSTR(MP_QSTR_read_into), MP_ROM_PTR(&dma_DMA_read_into_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&dma_DMA_copy_obj) }, + { MP_ROM_QSTR(MP_QSTR_abort), MP_ROM_PTR(&dma_DMA_abort_obj) }, + { MP_ROM_QSTR(MP_QSTR_isbusy), MP_ROM_PTR(&dma_DMA_isbusy_obj) }, + { MP_ROM_QSTR(MP_QSTR_error_status), (mp_obj_t)&dma_DMA_error_status_obj }, + { MP_ROM_QSTR(MP_QSTR_clear_error), MP_ROM_PTR(&dma_DMA_clear_error_obj) }, + { MP_ROM_QSTR(MP_QSTR_request_count), MP_ROM_PTR(&dma_DMA_request_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_DMA_SIZE_8), MP_ROM_INT(DMA_SIZE_8) }, + { MP_ROM_QSTR(MP_QSTR_DMA_SIZE_16), MP_ROM_INT(DMA_SIZE_16) }, + { MP_ROM_QSTR(MP_QSTR_DMA_SIZE_32), MP_ROM_INT(DMA_SIZE_32) }, + { MP_ROM_QSTR(MP_QSTR_DMA_IRQ_0), MP_ROM_INT(DMA_IRQ_0) }, + { MP_ROM_QSTR(MP_QSTR_DMA_IRQ_1), MP_ROM_INT(DMA_IRQ_1) }, +}; + +STATIC MP_DEFINE_CONST_DICT(dma_DMA_locals_dict, dma_DMA_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + dma_DMA_type, + MP_QSTR_DMA, + MP_TYPE_FLAG_NONE, + make_new, dma_DMA_make_new, + print, dma_DMA_print, + locals_dict, &dma_DMA_locals_dict + ); + + +/////////////////////////////////////////////////////////////////////////////////// +/* + * Timer + * Arguments + * timer_id +*/ +STATIC mp_obj_t dma_Timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + dma_Timer_obj_t *self = mp_obj_malloc(dma_Timer_obj_t, &dma_Timer_type); + self->timer = 0xff; + return MP_OBJ_FROM_PTR(self); +} + +STATIC void dma_Timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->timer); +} + + +STATIC mp_obj_t dma_Timer_claim(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_timer_id, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xff} }, + }; + + 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); + + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + uint32_t timer_id = args[0].u_int; + if (self->timer != 0xff) { + mp_raise_ValueError("Timer already claimed"); + } else if (timer_id == 0xff) { + int claimed_timer_id = dma_claim_unused_timer(false); + if (claimed_timer_id == -1) { + mp_raise_ValueError("No timers available"); + } else { + self->timer = claimed_timer_id; + } + } else { + self->timer = timer_id; + if (dma_timer_is_claimed(self->timer)) { + mp_raise_ValueError("Timer already claimed"); + } else { + dma_timer_claim(self->timer); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dma_Timer_claim_obj, 1, dma_Timer_claim); + +STATIC mp_obj_t dma_Timer_unclaim(mp_obj_t self_in) { + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (dma_timer_is_claimed(self->timer)) { + dma_timer_unclaim(self->timer); + self->timer = 0xff; + } else { + mp_raise_ValueError("Timer already unclaimed"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_Timer_unclaim_obj, dma_Timer_unclaim); + +STATIC mp_obj_t dma_Timer_isclaimed(mp_obj_t self_in) { + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(dma_timer_is_claimed(self->timer)); + +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_Timer_isclaimed_obj, dma_Timer_isclaimed); + + +STATIC mp_obj_t dma_Timer_set(mp_obj_t self_in, mp_obj_t numerator_obj, mp_obj_t denominator_obj) { + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint16_t numerator = mp_obj_get_int(numerator_obj); + uint16_t denominator = mp_obj_get_int(denominator_obj); + dma_timer_set_fraction(self->timer, numerator, denominator); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(dma_Timer_set_obj, dma_Timer_set); + +STATIC mp_obj_t dma_Timer_dreq(mp_obj_t self_in) { + dma_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(dma_get_timer_dreq(self->timer)); +} +MP_DEFINE_CONST_FUN_OBJ_1(dma_Timer_dreq_obj, dma_Timer_dreq); + + +STATIC const mp_rom_map_elem_t dma_Timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_claim), MP_ROM_PTR(&dma_Timer_claim_obj) }, + { MP_ROM_QSTR(MP_QSTR_unclaim), MP_ROM_PTR(&dma_Timer_unclaim_obj) }, + { MP_ROM_QSTR(MP_QSTR_isclaimed), MP_ROM_PTR(&dma_Timer_isclaimed_obj) }, + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&dma_Timer_set_obj) }, + { MP_ROM_QSTR(MP_QSTR_dreq), MP_ROM_PTR(&dma_Timer_dreq_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(dma_Timer_locals_dict, dma_Timer_locals_dict_table); + + +MP_DEFINE_CONST_OBJ_TYPE( + dma_Timer_type, + MP_QSTR_Timer, + MP_TYPE_FLAG_NONE, + make_new, dma_Timer_make_new, + print, dma_Timer_print, + locals_dict, &dma_Timer_locals_dict + ); diff --git a/ports/rp2/main.c b/ports/rp2/main.c index c5b50374912c3..0ec371ab815c6 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -162,6 +162,7 @@ int main(int argc, char **argv) { machine_pin_init(); rp2_pio_init(); machine_i2s_init0(); + dma_init(); #if MICROPY_PY_BLUETOOTH mp_bluetooth_hci_init(); diff --git a/ports/rp2/modrp2.c b/ports/rp2/modrp2.c index 2601a7f443de4..6e2c62095cda9 100644 --- a/ports/rp2/modrp2.c +++ b/ports/rp2/modrp2.c @@ -55,6 +55,8 @@ STATIC const mp_rom_map_elem_t rp2_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&rp2_flash_type) }, { MP_ROM_QSTR(MP_QSTR_PIO), MP_ROM_PTR(&rp2_pio_type) }, { MP_ROM_QSTR(MP_QSTR_StateMachine), MP_ROM_PTR(&rp2_state_machine_type) }, + { MP_ROM_QSTR(MP_QSTR_DMA), MP_ROM_PTR(&dma_DMA_type) }, + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&dma_Timer_type) }, #if MICROPY_PY_NETWORK_CYW43 { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&rp2_country_obj) }, diff --git a/ports/rp2/modrp2.h b/ports/rp2/modrp2.h index 805c785f2d8cd..15d13623825bd 100644 --- a/ports/rp2/modrp2.h +++ b/ports/rp2/modrp2.h @@ -31,8 +31,12 @@ extern const mp_obj_type_t rp2_flash_type; extern const mp_obj_type_t rp2_pio_type; extern const mp_obj_type_t rp2_state_machine_type; +extern const mp_obj_type_t dma_DMA_type; +extern const mp_obj_type_t dma_Timer_type; + void rp2_pio_init(void); void rp2_pio_deinit(void); +void dma_init(); #endif // MICROPY_INCLUDED_RP2_MODRP2_H