From 5a032dae9c973be05ba72e796c443275ac2770dd Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:26:27 +1000 Subject: [PATCH 01/87] lib/pico-sdk: Update to new rp2350 version. Signed-off-by: Damien George --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index 6a7db34ff6334..efe2103f9b284 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit 6a7db34ff63345a7badec79ebea3aaef1712f374 +Subproject commit efe2103f9b28458a1615ff096054479743ade236 From 9a9ceeb92d1096b73088025b67c86158797467d5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:26:45 +1000 Subject: [PATCH 02/87] lib/tinyusb: Update to support RP2350. Signed-off-by: Damien George --- lib/tinyusb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tinyusb b/lib/tinyusb index d10b65ada4be7..4232642899362 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit d10b65ada4be7d5754b3128e80a9b4db72bdb23f +Subproject commit 4232642899362fa5e9cf0dc59bad6f1f6d32c563 From 72d47a70cde160d86d4acf335158a756774af045 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 Aug 2024 14:20:35 +1000 Subject: [PATCH 03/87] shared/tinyusb: Use new persistent-tx-fifo configure interface. Signed-off-by: Damien George --- shared/tinyusb/mp_usbd.h | 2 ++ shared/tinyusb/mp_usbd_runtime.c | 2 ++ shared/tinyusb/tusb_config.h | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 31234566b6ca4..937cc070157c6 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -126,6 +126,8 @@ inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd static inline void mp_usbd_init(void) { // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() tusb_init(); + tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; + tud_cdc_configure_fifo(&cfg); } #endif diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index fe28a4a727791..a17aaf1f43224 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -429,6 +429,8 @@ void mp_usbd_init(void) { if (need_usb) { tusb_init(); // Safe to call redundantly + tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; + tud_cdc_configure_fifo(&cfg); tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected } } diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 1f8f5e5f6dc56..ab47321afdc04 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -79,7 +79,6 @@ #if CFG_TUD_CDC #define CFG_TUD_CDC_RX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) #define CFG_TUD_CDC_TX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) -#define CFG_TUD_CDC_PERSISTENT_TX_BUFF (1) #endif // MSC Configuration From eb9e4712fe94f5df8cb60c9dc46c5e7d0c581d45 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:21:56 +0100 Subject: [PATCH 04/87] rp2/boards: Add RPI_PICO2 board. Signed-off-by: Damien George --- ports/rp2/boards/RPI_PICO2/board.json | 22 +++++++++++++++ .../rp2/boards/RPI_PICO2/mpconfigboard.cmake | 2 ++ ports/rp2/boards/RPI_PICO2/mpconfigboard.h | 3 ++ .../boards/RPI_PICO2/mpconfigvariant.cmake | 1 + .../RPI_PICO2/mpconfigvariant_RISCV.cmake | 1 + ports/rp2/boards/RPI_PICO2/pins.csv | 28 +++++++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 ports/rp2/boards/RPI_PICO2/board.json create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigboard.h create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/pins.csv diff --git a/ports/rp2/boards/RPI_PICO2/board.json b/ports/rp2/boards/RPI_PICO2/board.json new file mode 100644 index 0000000000000..8f3e4bde71a2e --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "USB" + ], + "images": [ + "rp2-pico2.jpg" + ], + "mcu": "rp2350", + "product": "Pico 2", + "thumbnail": "", + "url": "https://www.raspberrypi.com/products/raspberry-pi-pico-2/", + "variants": { + "RISCV": "RISC-V CPU mode" + }, + "vendor": "Raspberry Pi" +} diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake new file mode 100644 index 0000000000000..80e84eef184b5 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake @@ -0,0 +1,2 @@ +# cmake file for Raspberry Pi Pico2 +set(PICO_BOARD "pico2") diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.h b/ports/rp2/boards/RPI_PICO2/mpconfigboard.h new file mode 100644 index 0000000000000..4b5eac6eb3ae3 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico2" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1024 * 1024) diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake new file mode 100644 index 0000000000000..6fe039ba51bba --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake new file mode 100644 index 0000000000000..65a97fc3350d1 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/RPI_PICO2/pins.csv b/ports/rp2/boards/RPI_PICO2/pins.csv new file mode 100644 index 0000000000000..16e334026424f --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/pins.csv @@ -0,0 +1,28 @@ +GP0,GPIO0 +GP1,GPIO1 +GP2,GPIO2 +GP3,GPIO3 +GP4,GPIO4 +GP5,GPIO5 +GP6,GPIO6 +GP7,GPIO7 +GP8,GPIO8 +GP9,GPIO9 +GP10,GPIO10 +GP11,GPIO11 +GP12,GPIO12 +GP13,GPIO13 +GP14,GPIO14 +GP15,GPIO15 +GP16,GPIO16 +GP17,GPIO17 +GP18,GPIO18 +GP19,GPIO19 +GP20,GPIO20 +GP21,GPIO21 +GP22,GPIO22 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +LED,GPIO25 From d259bed3d9e7de2ac4b3af6ab1440c5a1d871965 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:34:32 +0100 Subject: [PATCH 05/87] rp2/mpconfigport: Set MCU name for RP2350. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index abe25d7009e1e..bd2dfc4a568f5 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -35,7 +35,16 @@ #include "mpconfigboard.h" // Board and hardware specific configuration +#if PICO_RP2040 #define MICROPY_HW_MCU_NAME "RP2040" +#elif PICO_RP2350 && PICO_ARM +#define MICROPY_HW_MCU_NAME "RP2350" +#elif PICO_RP2350 && PICO_RISCV +#define MICROPY_HW_MCU_NAME "RP2350-RISCV" +#else +#error Unknown MCU +#endif + #ifndef MICROPY_HW_ENABLE_UART_REPL #define MICROPY_HW_ENABLE_UART_REPL (0) // useful if there is no USB #endif From c29c969ac1c5179a70234b8db95d723e4b854d34 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:49:46 +0100 Subject: [PATCH 06/87] rp2: Update custom linker scripts for new pico-sdk. Signed-off-by: Phil Howard Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 2 +- .../rp2/{memmap_mp.ld => memmap_mp_rp2040.ld} | 0 ports/rp2/memmap_mp_rp2350.ld | 313 ++++++++++++++++++ 3 files changed, 314 insertions(+), 1 deletion(-) rename ports/rp2/{memmap_mp.ld => memmap_mp_rp2040.ld} (100%) create mode 100644 ports/rp2/memmap_mp_rp2350.ld diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 904925ae3f5e5..1dee8c599d723 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -523,7 +523,7 @@ endif() # a linker script modification) until we explicitly add macro calls around the function # defs to move them into RAM. if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) - pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp.ld) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_${PICO_PLATFORM}.ld) endif() pico_add_extra_outputs(${MICROPY_TARGET}) diff --git a/ports/rp2/memmap_mp.ld b/ports/rp2/memmap_mp_rp2040.ld similarity index 100% rename from ports/rp2/memmap_mp.ld rename to ports/rp2/memmap_mp_rp2040.ld diff --git a/ports/rp2/memmap_mp_rp2350.ld b/ports/rp2/memmap_mp_rp2350.ld new file mode 100644 index 0000000000000..3d57117a8198e --- /dev/null +++ b/ports/rp2/memmap_mp_rp2350.ld @@ -0,0 +1,313 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 4096k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *libgcc.a:cmse_nonsecure_call.o + /* Change for MicroPython... exclude gc.c, parse.c, vm.c from flash */ + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *gc.c.obj *vm.c.obj *parse.c.obj) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + /* Note the boot2 section is optional, and should be discarded if there is + no reference to it *inside* the binary, as it is not called by the + bootrom. (The bootrom performs a simple best-effort XIP setup and + leaves it to the binary to do anything more sophisticated.) However + there is still a size limit of 256 bytes, to ensure the boot2 can be + stored in boot RAM. + + Really this is a "XIP setup function" -- the name boot2 is historic and + refers to its dual-purpose on RP2040, where it also handled vectoring + from the bootrom into the user image. + */ + + .boot2 : { + __boot2_start__ = .; + *(.boot2) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ <= 256, + "ERROR: Pico second stage bootloader must be no more than 256 bytes in size") + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + *(.srodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + *(.sdata*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + PROVIDE(__global_pointer$ = . + 2K); + *(.sbss*) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + /* Change for MicroPython: don't include this, it increases reported firmware size. + /* . = ORIGIN(RAM) + LENGTH(RAM); */ + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + KEEP(*(.embedded_end_block*)) + PROVIDE(__flash_binary_end = .); + } > FLASH =0xaa + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = __bss_end__ + __micropy_c_heap_size__; + + /* Define start and end of GC heap */ + __GcHeapStart = __StackLimit; /* after the C heap (sbrk limit) */ + __GcHeapEnd = ORIGIN(RAM) + LENGTH(RAM) - __micropy_extra_stack__; + + /* Define start and end of C stack */ + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackBottom = __GcHeapEnd; + PROVIDE(__stack = __StackTop); + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check GC heap is at least 64 KB */ + /* This is half the minimum RAM suggested for full-featured MicroPython. + * This value accounts for large static buffers included in user C or C++ + * modules, which might significantly reduce the available heap but also + * lower demand for memory at runtime. + */ + ASSERT((__GcHeapEnd - __GcHeapStart) > 64*1024, "GcHeap is too small") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") + ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") + + /* todo assert on extra code */ +} + From c3a2bd2520bfdc70c56e2b8a364222431d8b5208 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:24:24 +0100 Subject: [PATCH 07/87] rp2/modmachine: Implement lightsleep for RP2350. Signed-off-by: Damien George --- ports/rp2/modmachine.c | 52 +++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 372c17bcf9d6b..0acae25499996 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -162,6 +162,9 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } clock_stop(clk_adc); + #if PICO_RP2350 + clock_stop(clk_hstx); + #endif // CLK_REF = XOSC clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz); @@ -170,7 +173,9 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz); // CLK_RTC = XOSC / 256 + #if PICO_RP2040 clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, xosc_hz, xosc_hz / 256); + #endif // CLK_PERI = CLK_SYS clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, xosc_hz, xosc_hz); @@ -190,38 +195,63 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif xosc_dormant(); } else { - uint32_t sleep_en0 = clocks_hw->sleep_en0; - uint32_t sleep_en1 = clocks_hw->sleep_en1; bool timer3_enabled = irq_is_enabled(3); - clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + const uint32_t alarm_num = 3; + const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num); if (use_timer_alarm) { // Make sure ALARM3/IRQ3 is enabled on _this_ core - timer_hw->inte |= 1 << 3; if (!timer3_enabled) { - irq_set_enabled(3, true); + irq_set_enabled(irq_num, true); } + hw_set_bits(&timer_hw->inte, 1u << alarm_num); // Use timer alarm to wake. + clocks_hw->sleep_en0 = 0x0; + #if PICO_RP2040 clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; - timer_hw->alarm[3] = timer_hw->timerawl + delay_ms * 1000; + #elif PICO_RP2350 + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS | CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS; + #else + #error Unknown processor + #endif + timer_hw->intr = 1u << alarm_num; // clear any IRQ + timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000; } else { // TODO: Use RTC alarm to wake. - clocks_hw->sleep_en1 = 0; + clocks_hw->sleep_en0 = 0x0; + clocks_hw->sleep_en1 = 0x0; } if (!disable_usb) { clocks_hw->sleep_en0 |= CLOCKS_SLEEP_EN0_CLK_SYS_PLL_USB_BITS; + #if PICO_RP2040 clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_USBCTRL_BITS; + #elif PICO_RP2350 + clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_SYS_USBCTRL_BITS; + #else + #error Unknown processor + #endif } + #if PICO_ARM + // Configure SLEEPDEEP bits on Cortex-M CPUs. + #if PICO_RP2040 scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS; + #elif PICO_RP2350 + scb_hw->scr |= M33_SCR_SLEEPDEEP_BITS; + #else + #error Unknown processor + #endif + #endif + + // Go into low-power mode. __wfi(); - scb_hw->scr &= ~M0PLUS_SCR_SLEEPDEEP_BITS; + if (!timer3_enabled) { - irq_set_enabled(3, false); + irq_set_enabled(irq_num, false); } - clocks_hw->sleep_en0 = sleep_en0; - clocks_hw->sleep_en1 = sleep_en1; + clocks_hw->sleep_en0 |= ~(0u); + clocks_hw->sleep_en1 |= ~(0u); } // Enable ROSC. From e5c098e9fadd7044b80d4620a53235862f940b84 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 3 Jun 2024 16:14:43 +0100 Subject: [PATCH 08/87] rp2/rp2_dma: Generalise DMA for RP2350. Signed-off-by: Damien George --- ports/rp2/rp2_dma.c | 46 +++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 3afcf8b5d63af..78f69e6452758 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -57,23 +57,28 @@ typedef struct _rp2_dma_ctrl_field_t { } rp2_dma_ctrl_field_t; static rp2_dma_ctrl_field_t rp2_dma_ctrl_fields_table[] = { - { MP_QSTR_enable, 0, 1, 0 }, - { MP_QSTR_high_pri, 1, 1, 0 }, - { MP_QSTR_size, 2, 2, 0 }, - { MP_QSTR_inc_read, 4, 1, 0 }, - { MP_QSTR_inc_write, 5, 1, 0 }, - { MP_QSTR_ring_size, 6, 4, 0 }, - { MP_QSTR_ring_sel, 10, 1, 0 }, - { MP_QSTR_chain_to, 11, 4, 0 }, - { MP_QSTR_treq_sel, 15, 6, 0 }, - { MP_QSTR_irq_quiet, 21, 1, 0 }, - { MP_QSTR_bswap, 22, 1, 0 }, - { MP_QSTR_sniff_en, 23, 1, 0 }, - { MP_QSTR_busy, 24, 1, 1 }, - // bits 25 through 28 are reserved - { MP_QSTR_write_err, 29, 1, 0 }, - { MP_QSTR_read_err, 30, 1, 0 }, - { MP_QSTR_ahb_err, 31, 1, 1 }, + { MP_QSTR_enable, DMA_CH0_CTRL_TRIG_EN_LSB, 1, 0 }, + { MP_QSTR_high_pri, DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_LSB, 1, 0 }, + { MP_QSTR_size, DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB, 2, 0 }, + { MP_QSTR_inc_read, DMA_CH0_CTRL_TRIG_INCR_READ_LSB, 1, 0 }, + #if PICO_RP2350 + { MP_QSTR_inc_read_rev, DMA_CH0_CTRL_TRIG_INCR_READ_REV_LSB, 1, 0 }, + #endif + { MP_QSTR_inc_write, DMA_CH0_CTRL_TRIG_INCR_WRITE_LSB, 1, 0 }, + #if PICO_RP2350 + { MP_QSTR_inc_write_rev, DMA_CH0_CTRL_TRIG_INCR_WRITE_REV_LSB, 1, 0 }, + #endif + { MP_QSTR_ring_size, DMA_CH0_CTRL_TRIG_RING_SIZE_LSB, 4, 0 }, + { MP_QSTR_ring_sel, DMA_CH0_CTRL_TRIG_RING_SEL_LSB, 1, 0 }, + { MP_QSTR_chain_to, DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB, 4, 0 }, + { MP_QSTR_treq_sel, DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB, 6, 0 }, + { MP_QSTR_irq_quiet, DMA_CH0_CTRL_TRIG_IRQ_QUIET_LSB, 1, 0 }, + { MP_QSTR_bswap, DMA_CH0_CTRL_TRIG_BSWAP_LSB, 1, 0 }, + { MP_QSTR_sniff_en, DMA_CH0_CTRL_TRIG_SNIFF_EN_LSB, 1, 0 }, + { MP_QSTR_busy, DMA_CH0_CTRL_TRIG_BUSY_LSB, 1, 1 }, + { MP_QSTR_write_err, DMA_CH0_CTRL_TRIG_WRITE_ERROR_LSB, 1, 0 }, + { MP_QSTR_read_err, DMA_CH0_CTRL_TRIG_READ_ERROR_LSB, 1, 0 }, + { MP_QSTR_ahb_err, DMA_CH0_CTRL_TRIG_AHB_ERROR_LSB, 1, 1 }, }; static const uint32_t rp2_dma_ctrl_field_count = MP_ARRAY_SIZE(rp2_dma_ctrl_fields_table); @@ -298,7 +303,12 @@ static mp_obj_t rp2_dma_active(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_dma_active_obj, 1, 2, rp2_dma_active); // Default is quiet, unpaced, read and write incrementing, word transfers, enabled -#define DEFAULT_DMA_CONFIG (1 << 21) | (0x3f << 15) | (1 << 5) | (1 << 4) | (2 << 2) | (1 << 0) +#define DEFAULT_DMA_CONFIG (1 << DMA_CH0_CTRL_TRIG_IRQ_QUIET_LSB) | \ + (DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_INCR_WRITE_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_INCR_READ_LSB) | \ + (2 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_EN_LSB) // DMA.pack_ctrl(...) static mp_obj_t rp2_dma_pack_ctrl(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { From 2931e1389c1afc103419b656ba3b50720d45f266 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 23 May 2024 10:57:14 +0100 Subject: [PATCH 09/87] rp2: Integrate RP2350. Remove references to hardware_rtc for rp2350 Reimplement time functioniality Use hardware_powman instead Define PICO_PLATFORM for RPI_PICO move PICO_TINYUSB_PATH until PICO_PLATFORM is defined PICO_TINYUSB_PATH has to be defined before calling pico_sdk_init Make sure powerman timer is using xosc ports/rp2: Updates for powman changes. Probably another change coming to remove powman and use the new aon timer that supports both rp2040 and rp2350 ports/rps: Use aon_timer module. ports/rp2: Remove rp2350 specific source. ports/rp2: Use correct CMSIS header for rp2350. ports/rp2: Add hardware_resets. ports/rp2: For rp2350 just use aon timer For rp2040 there's a workaround for RTC having only 1s resolution. This is not needed for rp2350 Stop setting PICO_RP2040_B* for rp2350 Only set PICO_RP2040_USB_DEVICE_ENUMERATION_FIX for rp2040 Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 44 ++++++++++-- ports/rp2/boards/RPI_PICO/mpconfigboard.cmake | 1 + ports/rp2/fatfs_port.c | 11 +-- ports/rp2/machine_rtc.c | 69 ++++++++----------- ports/rp2/main.c | 26 +++---- ports/rp2/mbedtls/mbedtls_port.c | 8 +-- ports/rp2/modmachine.c | 1 + ports/rp2/modtime.c | 30 ++++---- ports/rp2/mphalport.c | 24 +++++-- ports/rp2/mpnetworkport.c | 7 ++ ports/rp2/pendsv.c | 7 ++ 11 files changed, 138 insertions(+), 90 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 1dee8c599d723..480a9097448dd 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -191,19 +191,26 @@ set(PICO_SDK_COMPONENTS hardware_pll hardware_pwm hardware_regs - hardware_rtc + hardware_resets hardware_spi hardware_structs hardware_sync + hardware_sync_spin_lock hardware_timer hardware_uart hardware_watchdog hardware_xosc + pico_aon_timer pico_base_headers pico_binary_info pico_bootrom pico_multicore pico_platform + pico_platform_compiler + pico_platform_sections + pico_platform_panic + pico_runtime + pico_runtime_init pico_stdio pico_stdlib pico_sync @@ -224,14 +231,23 @@ pico_add_library(pico_float_micropython) # pico_float_micropython: add pico-sdk float and our libm source files. target_sources(pico_float_micropython INTERFACE - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi.S - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom.c - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_v1_rom_shim.S ${MICROPY_SOURCE_LIB_LIBM} ${MICROPY_SOURCE_LIB_LIBM_SQRT_SW} ${MICROPY_PORT_DIR}/libm_extra.c ) +if(PICO_RP2040) + target_sources(pico_float_micropython INTERFACE + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_v1_rom_shim_rp2040.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_rp2040.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom_rp2040.c + ) +elseif(PICO_RP2350) + target_sources(pico_float_micropython INTERFACE + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S + ) +endif() + # pico_float_micropython: wrap low-level floating-point ops, to call the pico-sdk versions. pico_wrap_function(pico_float_micropython __aeabi_fdiv) pico_wrap_function(pico_float_micropython __aeabi_fmul) @@ -253,7 +269,9 @@ pico_wrap_function(pico_float_micropython __aeabi_ul2f) pico_wrap_function(pico_float_micropython __aeabi_f2iz) pico_wrap_function(pico_float_micropython __aeabi_f2lz) pico_wrap_function(pico_float_micropython __aeabi_f2uiz) -pico_wrap_function(pico_float_micropython __aeabi_f2ulz) +if (PICO_RP2040) + pico_wrap_function(pico_float_micropython __aeabi_f2ulz) +endif() pico_wrap_function(pico_float_micropython __aeabi_f2d) if (MICROPY_PY_LWIP) @@ -505,9 +523,14 @@ target_compile_definitions(${MICROPY_TARGET} PRIVATE PICO_NO_PROGRAM_VERSION_STRING=1 # do it ourselves in main.c MICROPY_BUILD_TYPE="${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} ${CMAKE_BUILD_TYPE}" PICO_NO_BI_STDIO_UART=1 # we call it UART REPL - PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1 ) +if (PICO_RP2040) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1 + ) +endif() + target_link_libraries(${MICROPY_TARGET} ${PICO_SDK_COMPONENTS} ) @@ -523,7 +546,11 @@ endif() # a linker script modification) until we explicitly add macro calls around the function # defs to move them into RAM. if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) - pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_${PICO_PLATFORM}.ld) + if(PICO_RP2040) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2040.ld) + elseif(PICO_RP2350) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2350.ld) + endif() endif() pico_add_extra_outputs(${MICROPY_TARGET}) @@ -542,6 +569,9 @@ foreach(comp ${PICO_SDK_COMPONENTS}) micropy_gather_target_properties(${comp}_headers) endforeach() +set(MICROPY_CPP_FLAGS_EXTRA ${PICO_COMMON_LANG_FLAGS}) +separate_arguments(MICROPY_CPP_FLAGS_EXTRA) + # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) diff --git a/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake index 13269e81e52e5..386bd33285890 100644 --- a/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake +++ b/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake @@ -1,2 +1,3 @@ # cmake file for Raspberry Pi Pico set(PICO_BOARD "pico") +set(PICO_PLATFORM "rp2040") diff --git a/ports/rp2/fatfs_port.c b/ports/rp2/fatfs_port.c index 9706b6fe7dc70..1ce505dd69e3f 100644 --- a/ports/rp2/fatfs_port.c +++ b/ports/rp2/fatfs_port.c @@ -25,10 +25,13 @@ */ #include "lib/oofatfs/ff.h" -#include "hardware/rtc.h" +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" MP_WEAK DWORD get_fattime(void) { - datetime_t t; - rtc_get_datetime(&t); - return ((t.year - 1980) << 25) | ((t.month) << 21) | ((t.day) << 16) | ((t.hour) << 11) | ((t.min) << 5) | (t.sec / 2); + struct timespec ts; + timeutils_struct_time_t tm; + aon_timer_get_time(&ts); + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); + return ((tm.tm_year - 1980) << 25) | ((tm.tm_mon) << 21) | ((tm.tm_mday) << 16) | ((tm.tm_hour) << 11) | ((tm.tm_min) << 5) | (tm.tm_sec / 2); } diff --git a/ports/rp2/machine_rtc.c b/ports/rp2/machine_rtc.c index ce224be711b89..0d8fdef30f322 100644 --- a/ports/rp2/machine_rtc.c +++ b/ports/rp2/machine_rtc.c @@ -30,6 +30,8 @@ #include #include +#include "pico/aon_timer.h" + #include "py/nlr.h" #include "py/obj.h" #include "py/runtime.h" @@ -37,8 +39,6 @@ #include "py/mperrno.h" #include "extmod/modmachine.h" #include "shared/timeutils/timeutils.h" -#include "hardware/rtc.h" -#include "pico/util/datetime.h" typedef struct _machine_rtc_obj_t { mp_obj_base_t base; @@ -50,14 +50,12 @@ static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments mp_arg_check_num(n_args, n_kw, 0, 0, false); - bool r = rtc_running(); + bool r = aon_timer_is_running(); if (!r) { - // This shouldn't happen as rtc_init() is already called in main so - // it's here just in case - rtc_init(); - datetime_t t = { .month = 1, .day = 1 }; - rtc_set_datetime(&t); + // This shouldn't happen. it's here just in case + struct timespec ts = { 0, 0 }; + aon_timer_start(&ts); mp_hal_time_ns_set_from_rtc(); } // return constant object @@ -66,47 +64,36 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { if (n_args == 1) { - bool ret; - datetime_t t; - - ret = rtc_get_datetime(&t); - if (!ret) { - mp_raise_OSError(MP_EIO); - } - + struct timespec ts; + timeutils_struct_time_t tm; + aon_timer_get_time(&ts); + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(t.year), - mp_obj_new_int(t.month), - mp_obj_new_int(t.day), - mp_obj_new_int(t.dotw), - mp_obj_new_int(t.hour), - mp_obj_new_int(t.min), - mp_obj_new_int(t.sec), - mp_obj_new_int(0) + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(0), }; - return mp_obj_new_tuple(8, tuple); } else { mp_obj_t *items; - mp_obj_get_array_fixed_n(args[1], 8, &items); - - datetime_t t = { - .year = mp_obj_get_int(items[0]), - .month = mp_obj_get_int(items[1]), - .day = mp_obj_get_int(items[2]), - .hour = mp_obj_get_int(items[4]), - .min = mp_obj_get_int(items[5]), - .sec = mp_obj_get_int(items[6]), + timeutils_struct_time_t tm = { + .tm_year = mp_obj_get_int(items[0]), + .tm_mon = mp_obj_get_int(items[1]), + .tm_mday = mp_obj_get_int(items[2]), + .tm_hour = mp_obj_get_int(items[4]), + .tm_min = mp_obj_get_int(items[5]), + .tm_sec = mp_obj_get_int(items[6]), }; - // Deliberately ignore the weekday argument and compute the proper value - t.dotw = timeutils_calc_weekday(t.year, t.month, t.day); - - if (!rtc_set_datetime(&t)) { - mp_raise_OSError(MP_EINVAL); - } + struct timespec ts = { 0, 0 }; + ts.tv_sec = timeutils_seconds_since_epoch(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + aon_timer_set_time(&ts); mp_hal_time_ns_set_from_rtc(); - } return mp_const_none; } diff --git a/ports/rp2/main.c b/ports/rp2/main.c index fa5495bf4100d..88f11d63e698d 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -47,11 +47,9 @@ #include "genhdr/mpversion.h" #include "mp_usbd.h" -#include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk #include "pico/stdlib.h" #include "pico/binary_info.h" #include "pico/unique_id.h" -#include "hardware/rtc.h" #include "hardware/structs/rosc.h" #if MICROPY_PY_LWIP #include "lwip/init.h" @@ -60,6 +58,16 @@ #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif +#if PICO_RP2040 +#include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk +#include "hardware/rtc.h" +#elif PICO_RP2350 +#include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" +#else +#error Unknown processor +#endif extern uint8_t __StackTop, __StackBottom; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -103,17 +111,9 @@ int main(int argc, char **argv) { #endif // Start and initialise the RTC - datetime_t t = { - .year = 2021, - .month = 1, - .day = 1, - .dotw = 4, // 0 is Monday, so 4 is Friday - .hour = 0, - .min = 0, - .sec = 0, - }; - rtc_init(); - rtc_set_datetime(&t); + struct timespec ts = { 0, 0 }; + ts.tv_sec = timeutils_seconds_since_epoch(2021, 1, 1, 0, 0, 0); + aon_timer_start(&ts); mp_hal_time_ns_set_from_rtc(); // Initialise stack extents and GC heap. diff --git a/ports/rp2/mbedtls/mbedtls_port.c b/ports/rp2/mbedtls/mbedtls_port.c index 9b1e0d20e1700..521d815d2bedd 100644 --- a/ports/rp2/mbedtls/mbedtls_port.c +++ b/ports/rp2/mbedtls/mbedtls_port.c @@ -29,9 +29,9 @@ #include "mbedtls_config_port.h" -#include "hardware/rtc.h" #include "shared/timeutils/timeutils.h" #include "mbedtls/platform_time.h" +#include "pico/aon_timer.h" extern uint8_t rosc_random_u8(size_t cycles); @@ -44,9 +44,9 @@ int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t } time_t rp2_rtctime_seconds(time_t *timer) { - datetime_t t; - rtc_get_datetime(&t); - return timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec); + struct timespec ts; + aon_timer_get_time(&ts); + return ts.tv_sec; } mbedtls_ms_time_t mbedtls_ms_time(void) { diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 0acae25499996..798078faf6527 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -41,6 +41,7 @@ #include "pico/bootrom.h" #include "pico/stdlib.h" #include "pico/unique_id.h" +#include "pico/runtime_init.h" #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif diff --git a/ports/rp2/modtime.c b/ports/rp2/modtime.c index 21e5cb459b283..7d6dd8fd97112 100644 --- a/ports/rp2/modtime.c +++ b/ports/rp2/modtime.c @@ -26,28 +26,30 @@ #include "py/obj.h" #include "shared/timeutils/timeutils.h" -#include "hardware/rtc.h" +#include "pico/aon_timer.h" // Return the localtime as an 8-tuple. static mp_obj_t mp_time_localtime_get(void) { - datetime_t t; - rtc_get_datetime(&t); + struct timespec ts; + aon_timer_get_time(&ts); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(t.year), - mp_obj_new_int(t.month), - mp_obj_new_int(t.day), - mp_obj_new_int(t.hour), - mp_obj_new_int(t.min), - mp_obj_new_int(t.sec), - mp_obj_new_int(t.dotw), - mp_obj_new_int(timeutils_year_day(t.year, t.month, t.day)), + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_yday), }; return mp_obj_new_tuple(8, tuple); } // Return the number of seconds since the Epoch. static mp_obj_t mp_time_time_get(void) { - datetime_t t; - rtc_get_datetime(&t); - return mp_obj_new_int_from_ull(timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec)); + struct timespec ts; + aon_timer_get_time(&ts); + return mp_obj_new_int_from_ull(ts.tv_sec); } diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index aa5415d6c842d..3fcc011b62676 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -37,16 +37,18 @@ #include "tusb.h" #include "uart.h" #include "hardware/irq.h" -#include "hardware/rtc.h" #include "pico/unique_id.h" +#include "pico/aon_timer.h" #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif +#if PICO_RP2040 // This needs to be added to the result of time_us_64() to get the number of // microseconds since the Epoch. static uint64_t time_us_64_offset_from_epoch; +#endif #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC @@ -145,27 +147,35 @@ void mp_hal_delay_ms(mp_uint_t ms) { } void mp_hal_time_ns_set_from_rtc(void) { + #if PICO_RP2040 // Outstanding RTC register writes need at least two RTC clock cycles to // update. (See RP2040 datasheet section 4.8.4 "Reference clock"). mp_hal_delay_us(44); // Sample RTC and time_us_64() as close together as possible, so the offset // calculated for the latter can be as accurate as possible. - datetime_t t; - rtc_get_datetime(&t); + struct timespec ts; + aon_timer_get_time(&ts); uint64_t us = time_us_64(); - // Calculate the difference between the RTC Epoch seconds and time_us_64(). - uint64_t s = timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec); - time_us_64_offset_from_epoch = (uint64_t)s * 1000000ULL - us; + // Calculate the difference between the RTC Epoch and time_us_64(). + time_us_64_offset_from_epoch = ((uint64_t)ts.tv_sec * 1000000ULL) + ((uint64_t)ts.tv_nsec / 1000ULL) - us; + #endif } uint64_t mp_hal_time_ns(void) { - // The RTC only has seconds resolution, so instead use time_us_64() to get a more + #if PICO_RP2040 + // The RTC probably has limited resolution, so instead use time_us_64() to get a more // precise measure of Epoch time. Both these "clocks" are clocked from the same // source so they remain synchronised, and only differ by a fixed offset (calculated // in mp_hal_time_ns_set_from_rtc). return (time_us_64_offset_from_epoch + time_us_64()) * 1000ULL; + #else + // aon timer has ms resolution + struct timespec ts; + aon_timer_get_time(&ts); + return ((uint64_t)ts.tv_sec * 1000000000ULL) + (uint64_t)ts.tv_nsec; + #endif } // Generate a random locally administered MAC address (LAA) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index e58adb3ba3595..fcc60b3fe5796 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -43,7 +43,14 @@ static soft_timer_entry_t mp_network_soft_timer; #include "lib/cyw43-driver/src/cyw43.h" #include "lib/cyw43-driver/src/cyw43_stats.h" #include "hardware/irq.h" + +#if PICO_RP2040 #include "RP2040.h" // cmsis, for NVIC_SetPriority and PendSV_IRQn +#elif PICO_RP2350 +#include "RP2350.h" // cmsis, for NVIC_SetPriority and PendSV_IRQn +#else +#error Unknown processor +#endif #define CYW43_IRQ_LEVEL GPIO_IRQ_LEVEL_HIGH #define CYW43_SHARED_IRQ_HANDLER_PRIORITY PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 6cfe624c3037f..2f06871b3ac5b 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -28,7 +28,14 @@ #include "py/mpconfig.h" #include "mutex_extra.h" #include "pendsv.h" + +#if PICO_RP2040 #include "RP2040.h" +#elif PICO_RP2350 +#include "RP2350.h" +#else +#error Unknown chip +#endif #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43_stats.h" From 8d73218c7fe0729db8f2a56021674b2890d0aaea Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Jun 2024 16:00:30 +0100 Subject: [PATCH 10/87] rp2/CMakeLists.txt: Add float_conv_m33 for __aeabi_ul2f. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 480a9097448dd..842bd7ac1f5ce 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -245,6 +245,7 @@ if(PICO_RP2040) elseif(PICO_RP2350) target_sources(pico_float_micropython INTERFACE ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_conv_m33.S ) endif() From 49f945831d36d6928432dc8a9854de8f317e96f5 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Jun 2024 16:01:12 +0100 Subject: [PATCH 11/87] py/usermod.cmake: Check target exists. Check a target exists before accessing properties. Usermod_gather_sources would recurse into garbage property names and break. Signed-off-by: Phil Howard --- py/usermod.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index 853276283746d..c814369a4865e 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -5,6 +5,10 @@ function(usermod_gather_sources SOURCES_VARNAME INCLUDE_DIRECTORIES_VARNAME INCL if (NOT ${LIB} IN_LIST ${INCLUDED_VARNAME}) list(APPEND ${INCLUDED_VARNAME} ${LIB}) + if (NOT TARGET ${LIB}) + return() + endif() + # Gather library sources get_target_property(lib_sources ${LIB} INTERFACE_SOURCES) if (lib_sources) From 968086d80bad876a518907bcae2c7e962b7c88a0 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 1 Jul 2024 15:29:57 +0100 Subject: [PATCH 12/87] rp2: Add support for 48-pin RP2350 variant. Changes: - NUM_BANK0_GPIOS > pins defined in pins.csv = hardfault - make-pins.py: NUM_GPIOS is hard-coded in make-pins.py, make it bigger - machine_pin.h: Pin "ID" must be six bits, not five (breaks bit packing?) - mphalport.h/machine_pin.c: open drain mask must be 64bits. Signed-off-by: Phil Howard --- ports/rp2/boards/make-pins.py | 2 +- ports/rp2/machine_pin.c | 2 +- ports/rp2/machine_pin.h | 2 +- ports/rp2/mphalport.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index cbc3424705def..aa754093aa5b1 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -9,7 +9,7 @@ import boardgen # This is NUM_BANK0_GPIOS. Pin indices are 0 to 29 (inclusive). -NUM_GPIOS = 30 +NUM_GPIOS = 48 # Up to 10 additional extended pins (e.g. via the wifi chip). NUM_EXT_GPIOS = 10 diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index de217785690c2..e1a545a09234c 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -89,7 +89,7 @@ static const mp_irq_methods_t machine_pin_irq_methods; extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. -uint32_t machine_pin_open_drain_mask; +uint64_t machine_pin_open_drain_mask; #if MICROPY_HW_PIN_EXT_COUNT static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { diff --git a/ports/rp2/machine_pin.h b/ports/rp2/machine_pin.h index b3349188e8ba1..196132019e609 100644 --- a/ports/rp2/machine_pin.h +++ b/ports/rp2/machine_pin.h @@ -49,7 +49,7 @@ typedef struct _machine_pin_af_obj_t { typedef struct _machine_pin_obj_t { mp_obj_base_t base; qstr name; - uint8_t id : 5; + uint8_t id : 6; #if MICROPY_HW_PIN_EXT_COUNT uint8_t is_ext : 1; uint8_t is_output : 1; diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 16ac4259a6feb..ac1c99264b295 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -115,7 +115,7 @@ static inline mp_uint_t mp_hal_get_cpu_freq(void) { #define MP_HAL_PIN_PULL_UP (1) #define MP_HAL_PIN_PULL_DOWN (2) -extern uint32_t machine_pin_open_drain_mask; +extern uint64_t machine_pin_open_drain_mask; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); From 7d90cf47e2bd5ce78040730e7629934221357774 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 2 Jul 2024 17:39:02 +0100 Subject: [PATCH 13/87] rp2/rp2_pio: Support RP2350A/B in rp2_pio. Add support for 32 and 48 pin variants of RP2350. Add new gpio_base, mirroring the Pico SDK. Signed-off-by: Phil Howard --- ports/rp2/rp2_pio.c | 81 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 0ca91656f84ef..7032c555ce6ff 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -54,7 +54,7 @@ typedef struct _rp2_state_machine_obj_t { PIO pio; uint8_t irq; uint8_t sm; // 0-3 - uint8_t id; // 0-7 + uint8_t id; // 0-7 on RP2040, or 0-11 on RP2350 } rp2_state_machine_obj_t; typedef struct _rp2_state_machine_irq_obj_t { @@ -63,11 +63,11 @@ typedef struct _rp2_state_machine_irq_obj_t { uint8_t trigger; } rp2_state_machine_irq_obj_t; -static const rp2_state_machine_obj_t rp2_state_machine_obj[8]; -static uint8_t rp2_state_machine_initial_pc[8]; +static const rp2_state_machine_obj_t rp2_state_machine_obj[NUM_PIOS * 4]; +static uint8_t rp2_state_machine_initial_pc[NUM_PIOS * 4]; // These masks keep track of PIO instruction memory used by this module. -static uint32_t rp2_pio_instruction_memory_usage_mask[2]; +static uint32_t rp2_pio_instruction_memory_usage_mask[NUM_PIOS]; static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id); static void rp2_state_machine_reset_all(void); @@ -104,8 +104,19 @@ static void pio1_irq0(void) { pio_irq0(pio1); } +#if PICO_RP2350 +static void pio2_irq0(void) { + pio_irq0(pio2); +} +#endif + // Returns the correct irq0 handler wrapper for a given pio static inline irq_handler_t rp2_pio_get_irq_handler(PIO pio) { + #if PICO_RP2350 + if (pio == pio2) { + return pio2_irq0; + } + #endif return pio == pio0 ? pio0_irq0 : pio1_irq0; } @@ -172,6 +183,12 @@ void rp2_pio_deinit(void) { irq_set_enabled(PIO1_IRQ_0, false); irq_remove_handler(PIO1_IRQ_0, pio1_irq0); } + #if PICO_RP2350 + if (irq_get_exclusive_handler(PIO2_IRQ_0) == pio2_irq0) { + irq_set_enabled(PIO2_IRQ_0, false); + irq_remove_handler(PIO2_IRQ_0, pio2_irq0); + } + #endif rp2_state_machine_reset_all(); @@ -180,6 +197,9 @@ void rp2_pio_deinit(void) { // and their PIO programs should remain intact. rp2_pio_remove_all_managed_programs(pio0); rp2_pio_remove_all_managed_programs(pio1); + #if PICO_RP2350 + rp2_pio_remove_all_managed_programs(pio2); + #endif } /******************************************************************************/ @@ -242,9 +262,9 @@ static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_ } static void asm_pio_init_gpio(PIO pio, uint32_t sm, asm_pio_config_t *config) { - uint32_t pinmask = ((1 << config->count) - 1) << config->base; - pio_sm_set_pins_with_mask(pio, sm, config->pinvals << config->base, pinmask); - pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << config->base, pinmask); + uint32_t pinmask = ((1 << config->count) - 1) << (config->base - pio->gpiobase); + pio_sm_set_pins_with_mask(pio, sm, config->pinvals << (config->base - pio->gpiobase), pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << (config->base - pio->gpiobase), pinmask); for (size_t i = 0; i < config->count; ++i) { gpio_set_function(config->base + i, GPIO_FUNC_PIO0 + pio_get_index(pio)); } @@ -258,6 +278,9 @@ static const mp_irq_methods_t rp2_pio_irq_methods; static rp2_pio_obj_t rp2_pio_obj[] = { { { &rp2_pio_type }, pio0, PIO0_IRQ_0 }, { { &rp2_pio_type }, pio1, PIO1_IRQ_0 }, + #if PICO_RP2350 + { { &rp2_pio_type }, pio2, PIO2_IRQ_0 }, + #endif }; static void rp2_pio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -357,6 +380,28 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m } MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_state_machine_obj, 2, rp2_pio_state_machine); +#if PICO_RP2350 +// PIO.gpio_base(0|16) +static mp_obj_t rp2_pio_gpio_base(mp_obj_t self_in, mp_obj_t gpio_base_in) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + + uint8_t gpio_base = mp_obj_get_int(gpio_base_in); + + // TODO check for RP2350B? + // Must be 0 for GPIOs 0 to 31 inclusive, + // or 16 for GPIOs 16 to 48 inclusive. + if (gpio_base != 0 && gpio_base != 16) { + mp_raise_ValueError("invalid GPIO base"); + } + + // Read back with pio->gpiobase + pio_set_gpio_base(self->pio, gpio_base); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(rp2_pio_gpio_base_obj, rp2_pio_gpio_base); +#endif + // PIO.irq(handler=None, trigger=IRQ_SM0|IRQ_SM1|IRQ_SM2|IRQ_SM3, hard=False) static mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_handler, ARG_trigger, ARG_hard }; @@ -414,6 +459,9 @@ static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_pio_irq_obj) }, + #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_IN_LOW), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_IN_HIGH), MP_ROM_INT(1) }, @@ -486,6 +534,12 @@ static const rp2_state_machine_obj_t rp2_state_machine_obj[] = { { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 1, 5 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 2, 6 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 3, 7 }, + #if PICO_RP2350 + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 0, 8 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 1, 9 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 2, 10 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 3, 11 }, + #endif }; static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id) { @@ -605,6 +659,9 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure out pins, if needed. asm_pio_config_t out_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); + if (out_config.base < self->pio->gpiobase) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out_base should be >= gpiobase (%d)"), self->pio->gpiobase); + } if (out_config.base >= 0) { sm_config_set_out_pins(&config, out_config.base, out_config.count); } @@ -612,6 +669,9 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure set pin, if needed. asm_pio_config_t set_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); + if (set_config.base < self->pio->gpiobase) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("set_base should be >= gpiobase (%d)"), self->pio->gpiobase); + } if (set_config.base >= 0) { sm_config_set_set_pins(&config, set_config.base, set_config.count); } @@ -624,6 +684,9 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure sideset pin, if needed. asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); + if (sideset_config.base < self->pio->gpiobase) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("sideset_base should be >= gpiobase (%d)"), self->pio->gpiobase); + } if (sideset_config.base >= 0) { uint32_t count = sideset_config.count; if (config.execctrl & (1 << PIO_SM0_EXECCTRL_SIDE_EN_LSB)) { @@ -951,5 +1014,5 @@ static const mp_irq_methods_t rp2_state_machine_irq_methods = { .info = rp2_state_machine_irq_info, }; -MP_REGISTER_ROOT_POINTER(void *rp2_pio_irq_obj[2]); -MP_REGISTER_ROOT_POINTER(void *rp2_state_machine_irq_obj[8]); +MP_REGISTER_ROOT_POINTER(void *rp2_pio_irq_obj[NUM_PIOS]); +MP_REGISTER_ROOT_POINTER(void *rp2_state_machine_irq_obj[NUM_PIOS * 4]); From 9d1045c595879fd92faecb80e80c3fde00bd5a65 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 8 Jul 2024 15:57:13 +0100 Subject: [PATCH 14/87] rp2: Pass gpio-num into make-pins. NUM_GPIOS amd NUM_EXT_GPIOS is currently hardcoded in this python script. Pass the count in via the new parameters num-gpios and num-ext-gpios. These default to the current values supported by Pico 30/10. This can be changed with PICO_NUM_GPIOS and PICO_NUM_EXT_GPIOS in mpconfigboard.cmake. Without this you will get a build error because NUM_BANK0_GPIOS can be too small. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 10 +++++- .../rp2/boards/RPI_PICO2/mpconfigboard.cmake | 3 ++ ports/rp2/boards/make-pins.py | 35 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 842bd7ac1f5ce..0cbed34f632f8 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -582,6 +582,14 @@ set(GEN_PINS_MKPINS "${MICROPY_PORT_DIR}/boards/make-pins.py") set(GEN_PINS_SRC "${CMAKE_BINARY_DIR}/pins_${MICROPY_BOARD}.c") set(GEN_PINS_HDR "${MICROPY_GENHDR_DIR}/pins.h") +if(NOT PICO_NUM_GPIOS) + set(PICO_NUM_GPIOS 30) +endif() + +if(NOT PICO_NUM_EXT_GPIOS) + set(PICO_NUM_EXT_GPIOS 10) +endif() + if(EXISTS "${MICROPY_BOARD_DIR}/pins.csv") set(GEN_PINS_BOARD_CSV "${MICROPY_BOARD_DIR}/pins.csv") set(GEN_PINS_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}") @@ -594,7 +602,7 @@ target_sources(${MICROPY_TARGET} PRIVATE # Generate pins add_custom_command( OUTPUT ${GEN_PINS_HDR} ${GEN_PINS_SRC} - COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_CSV_ARG} --af-csv ${GEN_PINS_AF_CSV} --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --output-header ${GEN_PINS_HDR} + COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_CSV_ARG} --af-csv ${GEN_PINS_AF_CSV} --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --num-gpios ${PICO_NUM_GPIOS} --num-ext-gpios ${PICO_NUM_EXT_GPIOS} --output-header ${GEN_PINS_HDR} DEPENDS ${GEN_PINS_AF_CSV} ${GEN_PINS_BOARD_CSV} diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake index 80e84eef184b5..48b6545aa3428 100644 --- a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake @@ -1,2 +1,5 @@ # cmake file for Raspberry Pi Pico2 set(PICO_BOARD "pico2") + +# To change the gpio count for QFN-80 +# set(PICO_NUM_GPIOS 48) diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index aa754093aa5b1..06d6847540e0a 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -9,9 +9,9 @@ import boardgen # This is NUM_BANK0_GPIOS. Pin indices are 0 to 29 (inclusive). -NUM_GPIOS = 48 +NUM_GPIOS = None # Up to 10 additional extended pins (e.g. via the wifi chip). -NUM_EXT_GPIOS = 10 +NUM_EXT_GPIOS = None class Rp2Pin(boardgen.Pin): @@ -108,12 +108,6 @@ def __init__(self): enable_af=True, ) - # Pre-define the pins (i.e. don't require them to be listed in pins.csv). - for i in range(NUM_GPIOS): - self.add_cpu_pin("GPIO{}".format(i)) - for i in range(NUM_EXT_GPIOS): - self.add_cpu_pin("EXT_GPIO{}".format(i)) - # Provided by pico-sdk. def cpu_table_size(self): return "NUM_BANK0_GPIOS" @@ -128,6 +122,31 @@ def print_source(self, out_source): super().print_source(out_source) self.print_cpu_locals_dict(out_source) + def extra_args(self, parser): + parser.add_argument("--num-gpios", type=int) + parser.add_argument("--num-ext-gpios", type=int) + + def load_inputs(self, out_source): + global NUM_GPIOS + global NUM_EXT_GPIOS + + # Needed by validate_cpu_pin_name + NUM_GPIOS = self.args.num_gpios + NUM_EXT_GPIOS = self.args.num_ext_gpios + + if NUM_GPIOS is None: + raise boardgen.PinGeneratorError("Please pass num-gpios") + + if NUM_EXT_GPIOS is None: + NUM_EXT_GPIOS = 0 + # Pre-define the pins (i.e. don't require them to be listed in pins.csv). + for i in range(NUM_GPIOS): + self.add_cpu_pin("GPIO{}".format(i)) + for i in range(NUM_EXT_GPIOS): + self.add_cpu_pin("EXT_GPIO{}".format(i)) + + super().load_inputs(out_source) + if __name__ == "__main__": Rp2PinGenerator().main() From 9de998ad5ca19956c771d74bb1edcc760e50ccef Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 9 Jul 2024 19:01:12 +0100 Subject: [PATCH 15/87] rp2: Fix RP2040 build issues. Signed-off-by: Damien George --- ports/rp2/main.c | 5 ++--- ports/rp2/rp2_pio.c | 35 ++++++++++++++++++----------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 88f11d63e698d..4fc6808987c57 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -60,14 +60,13 @@ #endif #if PICO_RP2040 #include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#include "hardware/rtc.h" #elif PICO_RP2350 #include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#include "pico/aon_timer.h" -#include "shared/timeutils/timeutils.h" #else #error Unknown processor #endif +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" extern uint8_t __StackTop, __StackBottom; extern uint8_t __GcHeapStart, __GcHeapEnd; diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 7032c555ce6ff..2f14ee690da2d 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -104,7 +104,7 @@ static void pio1_irq0(void) { pio_irq0(pio1); } -#if PICO_RP2350 +#if NUM_PIOS >= 3 static void pio2_irq0(void) { pio_irq0(pio2); } @@ -112,7 +112,7 @@ static void pio2_irq0(void) { // Returns the correct irq0 handler wrapper for a given pio static inline irq_handler_t rp2_pio_get_irq_handler(PIO pio) { - #if PICO_RP2350 + #if NUM_PIOS >= 3 if (pio == pio2) { return pio2_irq0; } @@ -183,7 +183,7 @@ void rp2_pio_deinit(void) { irq_set_enabled(PIO1_IRQ_0, false); irq_remove_handler(PIO1_IRQ_0, pio1_irq0); } - #if PICO_RP2350 + #if NUM_PIOS >= 3 if (irq_get_exclusive_handler(PIO2_IRQ_0) == pio2_irq0) { irq_set_enabled(PIO2_IRQ_0, false); irq_remove_handler(PIO2_IRQ_0, pio2_irq0); @@ -197,7 +197,7 @@ void rp2_pio_deinit(void) { // and their PIO programs should remain intact. rp2_pio_remove_all_managed_programs(pio0); rp2_pio_remove_all_managed_programs(pio1); - #if PICO_RP2350 + #if NUM_PIOS >= 3 rp2_pio_remove_all_managed_programs(pio2); #endif } @@ -262,9 +262,10 @@ static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_ } static void asm_pio_init_gpio(PIO pio, uint32_t sm, asm_pio_config_t *config) { - uint32_t pinmask = ((1 << config->count) - 1) << (config->base - pio->gpiobase); - pio_sm_set_pins_with_mask(pio, sm, config->pinvals << (config->base - pio->gpiobase), pinmask); - pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << (config->base - pio->gpiobase), pinmask); + + uint32_t pinmask = ((1 << config->count) - 1) << (config->base - pio_get_gpio_base(pio)); + pio_sm_set_pins_with_mask(pio, sm, config->pinvals << (config->base - pio_get_gpio_base(pio)), pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << (config->base - pio_get_gpio_base(pio)), pinmask); for (size_t i = 0; i < config->count; ++i) { gpio_set_function(config->base + i, GPIO_FUNC_PIO0 + pio_get_index(pio)); } @@ -278,7 +279,7 @@ static const mp_irq_methods_t rp2_pio_irq_methods; static rp2_pio_obj_t rp2_pio_obj[] = { { { &rp2_pio_type }, pio0, PIO0_IRQ_0 }, { { &rp2_pio_type }, pio1, PIO1_IRQ_0 }, - #if PICO_RP2350 + #if NUM_PIOS >= 3 { { &rp2_pio_type }, pio2, PIO2_IRQ_0 }, #endif }; @@ -380,7 +381,7 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m } MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_state_machine_obj, 2, rp2_pio_state_machine); -#if PICO_RP2350 +#if PICO_PIO_VERSION > 0 // PIO.gpio_base(0|16) static mp_obj_t rp2_pio_gpio_base(mp_obj_t self_in, mp_obj_t gpio_base_in) { rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -459,7 +460,7 @@ static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_pio_irq_obj) }, - #if PICO_RP2350 + #if PICO_PIO_VERSION > 0 { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, #endif @@ -534,7 +535,7 @@ static const rp2_state_machine_obj_t rp2_state_machine_obj[] = { { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 1, 5 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 2, 6 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 3, 7 }, - #if PICO_RP2350 + #if NUM_PIOS >= 3 { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 0, 8 }, { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 1, 9 }, { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 2, 10 }, @@ -659,8 +660,8 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure out pins, if needed. asm_pio_config_t out_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); - if (out_config.base < self->pio->gpiobase) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out_base should be >= gpiobase (%d)"), self->pio->gpiobase); + if (out_config.base < pio_get_gpio_base(self->pio)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); } if (out_config.base >= 0) { sm_config_set_out_pins(&config, out_config.base, out_config.count); @@ -669,8 +670,8 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure set pin, if needed. asm_pio_config_t set_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); - if (set_config.base < self->pio->gpiobase) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("set_base should be >= gpiobase (%d)"), self->pio->gpiobase); + if (set_config.base < pio_get_gpio_base(self->pio)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("set_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); } if (set_config.base >= 0) { sm_config_set_set_pins(&config, set_config.base, set_config.count); @@ -684,8 +685,8 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure sideset pin, if needed. asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); - if (sideset_config.base < self->pio->gpiobase) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("sideset_base should be >= gpiobase (%d)"), self->pio->gpiobase); + if (sideset_config.base < pio_get_gpio_base(self->pio)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("sideset_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); } if (sideset_config.base >= 0) { uint32_t count = sideset_config.count; From b2238054156d50a114f5380757661597bdcec3ac Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 10 Jul 2024 12:44:59 +0100 Subject: [PATCH 16/87] rp2/machine_pin: Allow gpios >=32 to work. Signed-off-by: Damien George --- ports/rp2/machine_pin.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index e1a545a09234c..1569b2696c6f8 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -298,7 +298,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin af: %d"), af); } gpio_set_function(self->id, af); - machine_pin_open_drain_mask &= ~(1 << self->id); + machine_pin_open_drain_mask &= ~(1ULL << self->id); } } @@ -380,7 +380,7 @@ static mp_obj_t machine_pin_low(mp_obj_t self_in) { } else if (GPIO_IS_OPEN_DRAIN(self->id)) { gpio_set_dir(self->id, GPIO_OUT); } else { - gpio_clr_mask(1u << self->id); + gpio_clr_mask64(1ULL << self->id); } return mp_const_none; } @@ -396,7 +396,7 @@ static mp_obj_t machine_pin_high(mp_obj_t self_in) { } else if (GPIO_IS_OPEN_DRAIN(self->id)) { gpio_set_dir(self->id, GPIO_IN); } else { - gpio_set_mask(1u << self->id); + gpio_set_mask64(1ULL << self->id); } return mp_const_none; } @@ -417,7 +417,7 @@ static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { gpio_set_dir(self->id, GPIO_OUT); } } else { - gpio_xor_mask(1u << self->id); + gpio_xor_mask64(1ULL << self->id); } return mp_const_none; } From 3d84f0623e681b286dae7022b8bb3a561f8c1e97 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 10 Jul 2024 14:38:17 +0100 Subject: [PATCH 17/87] rp2/rp2_pio: Raise error if pio_set_gpio_base fails. Signed-off-by: Damien George --- ports/rp2/rp2_pio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 2f14ee690da2d..ef060a34b17d5 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -396,7 +396,9 @@ static mp_obj_t rp2_pio_gpio_base(mp_obj_t self_in, mp_obj_t gpio_base_in) { } // Read back with pio->gpiobase - pio_set_gpio_base(self->pio, gpio_base); + if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { + mp_raise_ValueError("failed to set pio gpio base"); + } return mp_const_none; } From 1ebf9ecc5431f93331f638d3fc705aa6104ae8ef Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 8 Jul 2024 16:22:56 +0100 Subject: [PATCH 18/87] rp2/machine_pin: Fix pin irq for pins > 32. Fix the gpio_irq function so that it looks at all six iobank0_hw->intr[n] registers, for up to 48 IOs. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 1569b2696c6f8..f11f4190f48c6 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -87,6 +87,7 @@ typedef struct _machine_pin_irq_obj_t { static const mp_irq_methods_t machine_pin_irq_methods; extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; +static const int num_intr_regs = sizeof(iobank0_hw->intr) / sizeof(iobank0_hw->intr[0]); // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. uint64_t machine_pin_open_drain_mask; @@ -100,7 +101,7 @@ static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { #endif static void gpio_irq(void) { - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < num_intr_regs; ++i) { uint32_t intr = iobank0_hw->intr[i]; if (intr) { for (int j = 0; j < 8; ++j) { From e952692b68411780326aeaefc1fbb748696156b4 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 10 Jul 2024 16:01:16 +0100 Subject: [PATCH 19/87] rp2/clocks_extra: Update runtime_clocks_init. Note: This might be a tempoarary measure until we might fix this properly to avoid copying code. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 3 +- ports/rp2/clocks_extra.c | 68 +++++++++++++++++++++++++++++++--------- ports/rp2/clocks_extra.h | 2 +- ports/rp2/modmachine.c | 2 +- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 0cbed34f632f8..18244c63c78c4 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -181,6 +181,7 @@ set(PICO_SDK_COMPONENTS cmsis_core hardware_adc hardware_base + hardware_boot_lock hardware_clocks hardware_dma hardware_flash @@ -485,7 +486,7 @@ target_compile_options(${MICROPY_TARGET} PRIVATE target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--defsym=__micropy_c_heap_size__=${MICROPY_C_HEAP_SIZE} -Wl,--wrap=dcd_event_handler - -Wl,--wrap=clocks_init + -Wl,--wrap=runtime_init_clocks ) # Apply optimisations to performance-critical source code. diff --git a/ports/rp2/clocks_extra.c b/ports/rp2/clocks_extra.c index e2c97962cb43d..73def24b80455 100644 --- a/ports/rp2/clocks_extra.c +++ b/ports/rp2/clocks_extra.c @@ -13,22 +13,36 @@ #include "hardware/xosc.h" #include "hardware/irq.h" #include "hardware/gpio.h" +#include "hardware/ticks.h" +#if PICO_RP2040 +// The RTC clock frequency is 48MHz divided by power of 2 (to ensure an integer +// division ratio will be used in the clocks block). A divisor of 1024 generates +// an RTC clock tick of 46875Hz. This frequency is relatively close to the +// customary 32 or 32.768kHz 'slow clock' crystals and provides good timing resolution. #define RTC_CLOCK_FREQ_HZ (USB_CLK_KHZ * KHZ / 1024) +#endif + +static void start_all_ticks(void) { + uint32_t cycles = clock_get_hz(clk_ref) / MHZ; + // Note RP2040 has a single tick generator in the watchdog which serves + // watchdog, system timer and M0+ SysTick; The tick generator is clocked from clk_ref + // but is now adapted by the hardware_ticks library for compatibility with RP2350 + // npte: hardware_ticks library now provides an adapter for RP2040 + + for (int i = 0; i < (int)TICK_COUNT; ++i) { + tick_start((tick_gen_num_t)i, cycles); + } +} // Wrap the SDK's clocks_init() function to save code size -void __wrap_clocks_init(void) { - clocks_init_optional_usb(true); +void __wrap_runtime_init_clocks(void) { + runtime_init_clocks_optional_usb(true); } -// Copy of clocks_init() from pico-sdk, with USB +// Copy of runtime_init_clocks() from pico-sdk, with USB // PLL and clock init made optional (for light sleep wakeup). -void clocks_init_optional_usb(bool init_usb) { - // Start tick in watchdog, the argument is in 'cycles per microsecond' i.e. MHz - watchdog_start_tick(XOSC_KHZ / KHZ); - - // Modification: removed FPGA check here - +void runtime_init_clocks_optional_usb(bool init_usb) { // Disable resus that may be enabled from previous software clocks_hw->resus.ctrl = 0; @@ -46,14 +60,26 @@ void clocks_init_optional_usb(bool init_usb) { } /// \tag::pll_init[] - pll_init(pll_sys, PLL_COMMON_REFDIV, PLL_SYS_VCO_FREQ_KHZ * KHZ, PLL_SYS_POSTDIV1, PLL_SYS_POSTDIV2); + pll_init(pll_sys, PLL_COMMON_REFDIV, PLL_SYS_VCO_FREQ_HZ, PLL_SYS_POSTDIV1, PLL_SYS_POSTDIV2); if (init_usb) { - pll_init(pll_usb, PLL_COMMON_REFDIV, PLL_USB_VCO_FREQ_KHZ * KHZ, PLL_USB_POSTDIV1, PLL_USB_POSTDIV2); + pll_init(pll_usb, PLL_COMMON_REFDIV, PLL_USB_VCO_FREQ_HZ, PLL_USB_POSTDIV1, PLL_USB_POSTDIV2); } /// \end::pll_init[] // Configure clocks - // CLK_REF = XOSC (usually) 12MHz / 1 = 12MHz + + // todo amy, what is this N1,2,4 meant to mean? + // RP2040 CLK_REF = XOSC (usually) 12MHz / 1 = 12MHz + // RP2350 CLK_REF = XOSC (XOSC_MHZ) / N (1,2,4) = 12MHz + + // clk_ref aux select is 0 because: + // + // - RP2040: no aux mux on clk_ref, so this field is don't-care. + // + // - RP2350: there is an aux mux, but we are selecting one of the + // non-aux inputs to the glitchless mux, so the aux select doesn't + // matter. The value of 0 here happens to be the sys PLL. + clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, // No aux mux @@ -85,18 +111,32 @@ void clocks_init_optional_usb(bool init_usb) { USB_CLK_KHZ * KHZ, USB_CLK_KHZ * KHZ); + #if HAS_RP2040_RTC // CLK RTC = PLL USB 48MHz / 1024 = 46875Hz clock_configure(clk_rtc, 0, // No GLMUX CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, USB_CLK_KHZ * KHZ, RTC_CLOCK_FREQ_HZ); + #endif - // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable - // Normally choose clk_sys or clk_usb + // CLK PERI = clk_sys. Used as reference clock for UART and SPI serial. clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, SYS_CLK_KHZ * KHZ, SYS_CLK_KHZ * KHZ); + + #if PICO_RP2350 + // CLK_HSTX = clk_sys. Transmit bit clock for the HSTX peripheral. + clock_configure(clk_hstx, + 0, + CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLK_SYS, + SYS_CLK_KHZ * KHZ, + SYS_CLK_KHZ * KHZ); + #endif + + // Finally, all clocks are configured so start the ticks + // The ticks use clk_ref so now that is configured we can start them + start_all_ticks(); } diff --git a/ports/rp2/clocks_extra.h b/ports/rp2/clocks_extra.h index 40f77f53bd850..7d630e5088f5c 100644 --- a/ports/rp2/clocks_extra.h +++ b/ports/rp2/clocks_extra.h @@ -28,6 +28,6 @@ #include "hardware/clocks.h" -void clocks_init_optional_usb(bool init_usb); +void runtime_init_clocks_optional_usb(bool init_usb); #endif // MICROPY_INCLUDED_RP2_CLOCKS_EXTRA_H diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 798078faf6527..2faf0bc6f8713 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -259,7 +259,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; // Bring back all clocks. - clocks_init_optional_usb(disable_usb); + runtime_init_clocks_optional_usb(disable_usb); MICROPY_END_ATOMIC_SECTION(my_interrupts); } From c7e71060521a20f04c530011bd8599ab4df72ead Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 5 Aug 2024 22:02:13 +0100 Subject: [PATCH 20/87] rp2/machine_adc: Add ADC support for RP2350B. Signed-off-by: Damien George --- ports/rp2/machine_adc.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ports/rp2/machine_adc.c b/ports/rp2/machine_adc.c index 3594f4b124022..abf20025c2fbc 100644 --- a/ports/rp2/machine_adc.c +++ b/ports/rp2/machine_adc.c @@ -31,9 +31,11 @@ #include "hardware/adc.h" #include "machine_pin.h" -#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= 26 && (gpio) <= 29) -#define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - 26) -#define ADC_CHANNEL_TEMPSENSOR (4) +#define ADC_CHANNEL_COUNT (NUM_ADC_CHANNELS - 1) + +#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= ADC_BASE_PIN && (gpio) < (ADC_BASE_PIN + ADC_CHANNEL_COUNT)) +#define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - ADC_BASE_PIN) + static uint16_t adc_config_and_read_u16(uint32_t channel) { adc_select_input(channel); @@ -47,7 +49,7 @@ static uint16_t adc_config_and_read_u16(uint32_t channel) { // MicroPython bindings for machine.ADC #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS \ - { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_CHANNEL_TEMPSENSOR) }, \ + { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_TEMPERATURE_CHANNEL_NUM) }, \ typedef struct _machine_adc_obj_t { mp_obj_base_t base; @@ -75,7 +77,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args if (mp_obj_is_int(source)) { channel = mp_obj_get_int(source); - if (!(channel >= 0 && channel <= ADC_CHANNEL_TEMPSENSOR)) { + if (!(channel >= 0 && channel < ADC_CHANNEL_COUNT)) { // Not a valid ADC channel, fallback to searching for a pin. channel = -1; } @@ -116,7 +118,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args adc_gpio_init(pin->id); channel = ADC_CHANNEL_FROM_GPIO(pin->id); } - } else if (channel == ADC_CHANNEL_TEMPSENSOR) { + } else if (channel == ADC_TEMPERATURE_CHANNEL_NUM) { // Enable temperature sensor. adc_set_temp_sensor_enabled(1); } From cef1b3c2af5e1a8aecad146192e14a38ebab6c99 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:34:22 +1000 Subject: [PATCH 21/87] rp2: Update to support RP2350-RISCV. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 41 ++++++++++++++++++++++++++++++++++++---- ports/rp2/main.c | 6 +++--- ports/rp2/pendsv.c | 14 +++++++++++--- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 18244c63c78c4..36b9b284cf7f1 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -93,6 +93,14 @@ include(${MICROPY_DIR}/py/usermod.cmake) add_executable(${MICROPY_TARGET}) +# Provide a C-level definitions of PICO_ARM. +# (The pico-sdk already defines PICO_RISCV when it's enabled.) +if(PICO_ARM) + target_compile_definitions(pico_platform_headers INTERFACE + PICO_ARM=1 + ) +endif() + set(MICROPY_QSTRDEFS_PORT ${MICROPY_PORT_DIR}/qstrdefsport.h ) @@ -108,7 +116,6 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/netutils/trace.c ${MICROPY_DIR}/shared/readline/readline.c - ${MICROPY_DIR}/shared/runtime/gchelper_thumb1.s ${MICROPY_DIR}/shared/runtime/gchelper_native.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c ${MICROPY_DIR}/shared/runtime/mpirq.c @@ -123,6 +130,16 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) +if(PICO_ARM) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_thumb1.s + ) +elseif(PICO_RISCV) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s + ) +endif() + set(MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/bus/softspi.c ${MICROPY_DIR}/drivers/dht/dht.c @@ -178,7 +195,6 @@ set(MICROPY_SOURCE_QSTR ) set(PICO_SDK_COMPONENTS - cmsis_core hardware_adc hardware_base hardware_boot_lock @@ -222,6 +238,17 @@ set(PICO_SDK_COMPONENTS tinyusb_device ) +if(PICO_ARM) + list(APPEND PICO_SDK_COMPONENTS + cmsis_core + ) +elseif(PICO_RISCV) + list(APPEND PICO_SDK_COMPONENTS + hardware_hazard3 + hardware_riscv + ) +endif() + # Use our custom pico_float_micropython float implementation. This is needed for two reasons: # - to fix inf handling in pico-sdk's __wrap___aeabi_fadd(); # - so we can use our own libm functions, to fix inaccuracies in the pico-sdk versions. @@ -243,7 +270,7 @@ if(PICO_RP2040) ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_rp2040.S ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom_rp2040.c ) -elseif(PICO_RP2350) +elseif(PICO_RP2350 AND PICO_ARM) target_sources(pico_float_micropython INTERFACE ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_conv_m33.S @@ -489,6 +516,12 @@ target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--wrap=runtime_init_clocks ) +if(PICO_RP2350) + target_link_options(${MICROPY_TARGET} PRIVATE + -Wl,--defsym=__micropy_extra_stack__=4096 + ) +endif() + # Apply optimisations to performance-critical source code. set_source_files_properties( ${MICROPY_PY_DIR}/map.c @@ -557,7 +590,7 @@ endif() pico_add_extra_outputs(${MICROPY_TARGET}) -pico_find_compiler(PICO_COMPILER_SIZE ${PICO_GCC_TRIPLE}-size) +pico_find_compiler_with_triples(PICO_COMPILER_SIZE "${PICO_GCC_TRIPLE}" size) add_custom_command(TARGET ${MICROPY_TARGET} POST_BUILD diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 4fc6808987c57..47cf35deee23d 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -60,10 +60,8 @@ #endif #if PICO_RP2040 #include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#elif PICO_RP2350 +#elif PICO_RP2350 && PICO_ARM #include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#else -#error Unknown processor #endif #include "pico/aon_timer.h" #include "shared/timeutils/timeutils.h" @@ -82,7 +80,9 @@ bi_decl(bi_program_feature_group_with_flags(BINARY_INFO_TAG_MICROPYTHON, int main(int argc, char **argv) { // This is a tickless port, interrupts should always trigger SEV. + #if PICO_ARM SCB->SCR |= SCB_SCR_SEVONPEND_Msk; + #endif pendsv_init(); soft_timer_init(); diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 2f06871b3ac5b..905a5aa162ec5 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -31,10 +31,10 @@ #if PICO_RP2040 #include "RP2040.h" -#elif PICO_RP2350 +#elif PICO_RP2350 && PICO_ARM #include "RP2350.h" -#else -#error Unknown chip +#elif PICO_RISCV +#include "pico/aon_timer.h" #endif #if MICROPY_PY_NETWORK_CYW43 @@ -43,6 +43,8 @@ static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; +void PendSV_Handler(void); + // Using the nowait variant here as softtimer updates PendSV from the loop of mp_wfe_or_timeout(), // where we don't want the CPU event bit to be set. static recursive_mutex_nowait_t pendsv_mutex; @@ -75,10 +77,16 @@ void pendsv_resume(void) { void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { pendsv_dispatch_table[slot] = f; if (pendsv_mutex.mutex.enter_count == 0) { + #if PICO_ARM // There is a race here where other core calls pendsv_suspend() before // ISR can execute, but dispatch will happen later when other core // calls pendsv_resume(). SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + #elif PICO_RISCV + struct timespec ts; + aon_timer_get_time(&ts); + aon_timer_enable_alarm(&ts, PendSV_Handler, false); + #endif } else { #if MICROPY_PY_NETWORK_CYW43 CYW43_STAT_INC(PENDSV_DISABLED_COUNT); From dec2561c534929b99cc16f5776a2e67405aac4cf Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:33:47 +1000 Subject: [PATCH 22/87] rp2/mpconfigport: Enable RV32I native emitter on RISCV variants. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index bd2dfc4a568f5..b81a218ab5bb6 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -78,10 +78,16 @@ // MicroPython emitters #define MICROPY_PERSISTENT_CODE_LOAD (1) +#if PICO_ARM #define MICROPY_EMIT_THUMB (1) -#define MICROPY_EMIT_THUMB_ARMV7M (0) #define MICROPY_EMIT_INLINE_THUMB (1) +#if PICO_RP2040 +#define MICROPY_EMIT_THUMB_ARMV7M (0) #define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#endif +#elif PICO_RISCV +#define MICROPY_EMIT_RV32 (1) +#endif // Optimisations #define MICROPY_OPT_COMPUTED_GOTO (1) From b42ad6242843f3bbc244c67f71273c1495fbfe75 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:35:23 +1000 Subject: [PATCH 23/87] rp2/Makefile: Allow CMAKE_ARGS to be set by user. Signed-off-by: Damien George --- ports/rp2/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index b6e8852169217..f950bc7b90898 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -30,7 +30,7 @@ endif $(VERBOSE)MAKESILENT = -s -CMAKE_ARGS = -DMICROPY_BOARD=$(BOARD) -DMICROPY_BOARD_DIR="$(abspath $(BOARD_DIR))" +CMAKE_ARGS += -DMICROPY_BOARD=$(BOARD) -DMICROPY_BOARD_DIR="$(abspath $(BOARD_DIR))" ifdef USER_C_MODULES CMAKE_ARGS += -DUSER_C_MODULES=${USER_C_MODULES} From 1d5f792df25dd8c1e60143670ed3ebe2946bcb18 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 5 Aug 2024 13:47:25 -0600 Subject: [PATCH 24/87] rp2/machine_bitstream: Set SysTick reset value. Signed-off-by: Damien George --- ports/rp2/machine_bitstream.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/rp2/machine_bitstream.c b/ports/rp2/machine_bitstream.c index b65ec02147b40..8538bcbaade7c 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -48,6 +48,10 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } } mp_hal_pin_output(pin); + + // Set systick reset value + systick_hw->rvr = 0x00FFFFFF; + // Enable the systick counter, source CPU clock. systick_hw->csr = 5; From 19251f765a80901a53ec60e7a9e5b14d5ff829a7 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Tue, 6 Aug 2024 16:13:57 -0600 Subject: [PATCH 25/87] rp2/machine_uart: Allow new TX/RX pins on RP2350. Signed-off-by: Damien George --- ports/rp2/machine_uart.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c index d9e97685f1f25..412875cb1649b 100644 --- a/ports/rp2/machine_uart.c +++ b/ports/rp2/machine_uart.c @@ -69,8 +69,13 @@ #define MAX_BUFFER_SIZE (32766) #define IS_VALID_PERIPH(uart, pin) (((((pin) + 4) & 8) >> 3) == (uart)) +#if PICO_RP2350 +#define IS_VALID_TX(uart, pin) (((pin) & 1) == 0 && IS_VALID_PERIPH(uart, pin)) +#define IS_VALID_RX(uart, pin) (((pin) & 1) == 1 && IS_VALID_PERIPH(uart, pin)) +#else #define IS_VALID_TX(uart, pin) (((pin) & 3) == 0 && IS_VALID_PERIPH(uart, pin)) #define IS_VALID_RX(uart, pin) (((pin) & 3) == 1 && IS_VALID_PERIPH(uart, pin)) +#endif #define IS_VALID_CTS(uart, pin) (((pin) & 3) == 2 && IS_VALID_PERIPH(uart, pin)) #define IS_VALID_RTS(uart, pin) (((pin) & 3) == 3 && IS_VALID_PERIPH(uart, pin)) @@ -372,8 +377,8 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, __dsb(); // make sure UARTLCR_H register is written to uart_set_fifo_enabled(self->uart, true); __dsb(); // make sure UARTLCR_H register is written to - gpio_set_function(self->tx, GPIO_FUNC_UART); - gpio_set_function(self->rx, GPIO_FUNC_UART); + gpio_set_function(self->tx, UART_FUNCSEL_NUM(self->uart, self->tx)); + gpio_set_function(self->rx, UART_FUNCSEL_NUM(self->uart, self->rx)); if (self->invert & UART_INVERT_RX) { gpio_set_inover(self->rx, GPIO_OVERRIDE_INVERT); } From 6b4054c18985d4de84632f65dd2d7e0bf4c7b044 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 22:26:14 +1000 Subject: [PATCH 26/87] rp2/mphalport: Implement mp_hal_ticks_cpu for RISCV using mcycle. Signed-off-by: Damien George --- ports/rp2/mphalport.c | 11 +++++++++++ ports/rp2/mphalport.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 3fcc011b62676..3f50151620a02 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -125,6 +125,17 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { return did_write ? ret : 0; } +#if PICO_RISCV +__attribute__((naked)) mp_uint_t mp_hal_ticks_cpu(void) { + __asm volatile ( + "li a0, 4\n" // mask value to uninhibit mcycle counter + "csrw mcountinhibit, a0\n" // uninhibit mcycle counter + "csrr a0, mcycle\n" // get mcycle counter + "ret\n" + ); +} +#endif + void mp_hal_delay_us(mp_uint_t us) { // Avoid calling sleep_us() and invoking the alarm pool by splitting long // sleeps into an optional longer sleep and a shorter busy-wait diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index ac1c99264b295..25a472dfbdda4 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -92,11 +92,15 @@ static inline mp_uint_t mp_hal_ticks_ms(void) { return to_ms_since_boot(get_absolute_time()); } +#if PICO_ARM static inline mp_uint_t mp_hal_ticks_cpu(void) { // ticks_cpu() is defined as using the highest-resolution timing source // in the system. This is usually a CPU clock, but doesn't have to be. return time_us_32(); } +#elif PICO_RISCV +mp_uint_t mp_hal_ticks_cpu(void); +#endif static inline mp_uint_t mp_hal_get_cpu_freq(void) { return clock_get_hz(clk_sys); From d99bd34213f034c25788f99d3aff2eb38ca746e1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 22:26:37 +1000 Subject: [PATCH 27/87] rp2/machine_bitstream: Implement bitstream for RISC-V using mcycle. Signed-off-by: Damien George --- ports/rp2/machine_bitstream.c | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_bitstream.c b/ports/rp2/machine_bitstream.c index 8538bcbaade7c..ec21beed35c56 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -34,6 +34,25 @@ #define MP_HAL_BITSTREAM_NS_OVERHEAD (9) +#if PICO_RISCV + +__attribute__((naked)) void mcycle_init(void) { + __asm volatile ( + "li a0, 4\n" + "csrw mcountinhibit, a0\n" + "ret\n" + ); +} + +__attribute__((naked)) uint32_t mcycle_get(void) { + __asm volatile ( + "csrr a0, mcycle\n" + "ret\n" + ); +} + +#endif + void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { uint32_t fcpu_mhz = mp_hal_get_cpu_freq() / 1000000; // Convert ns to clock ticks [high_time_0, period_0, high_time_1, period_1]. @@ -49,14 +68,16 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } mp_hal_pin_output(pin); + uint32_t irq_state = mp_hal_quiet_timing_enter(); + + #if PICO_ARM + // Set systick reset value systick_hw->rvr = 0x00FFFFFF; // Enable the systick counter, source CPU clock. systick_hw->csr = 5; - uint32_t irq_state = mp_hal_quiet_timing_enter(); - for (size_t i = 0; i < len; ++i) { uint8_t b = buf[i]; for (size_t j = 0; j < 8; ++j) { @@ -72,6 +93,27 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } } + #elif PICO_RISCV + + mcycle_init(); + + for (size_t i = 0; i < len; ++i) { + uint8_t b = buf[i]; + for (size_t j = 0; j < 8; ++j) { + uint32_t *t = &timing_ns[b >> 6 & 2]; + uint32_t start_ticks = mcycle_get(); + mp_hal_pin_high(pin); + while ((mcycle_get() - start_ticks) < t[0]) { + } + b <<= 1; + mp_hal_pin_low(pin); + while ((mcycle_get() - start_ticks) < t[1]) { + } + } + } + + #endif + mp_hal_quiet_timing_exit(irq_state); } From b10eec392f375adfbc737b677ff32018813f5f49 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 Aug 2024 17:03:22 +1000 Subject: [PATCH 28/87] tests/ports/rp2: Update DMA test for higher freq CPU. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index 2459213f4c014..f9bd1c5fbf9ae 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -62,7 +62,8 @@ def run_and_time_dma(dma): dma.count = len(dest) // 4 dma.ctrl = dma.pack_ctrl() dt = run_and_time_dma(dma) -print(70 <= dt <= 110) +expected_dt = 90 * 120000000 // machine.freq() +print(abs(dt - expected_dt) <= 20) print(dest[:8], dest[-8:]) dma.close() From 8e42463e4aef8a2b7345026e7b5fbf8b7fcad589 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 9 Aug 2024 11:25:01 +1000 Subject: [PATCH 29/87] tests/ports/rp2: Add simple UART test. Signed-off-by: Damien George --- tests/ports/rp2/rp2_uart.py | 10 ++++++++++ tests/ports/rp2/rp2_uart.py.exp | 1 + 2 files changed, 11 insertions(+) create mode 100644 tests/ports/rp2/rp2_uart.py create mode 100644 tests/ports/rp2/rp2_uart.py.exp diff --git a/tests/ports/rp2/rp2_uart.py b/tests/ports/rp2/rp2_uart.py new file mode 100644 index 0000000000000..44410fc92611c --- /dev/null +++ b/tests/ports/rp2/rp2_uart.py @@ -0,0 +1,10 @@ +# Test construction of machine.UART objects. + +import sys +from machine import UART + +print(UART(0, tx=0, rx=1)) + +if "RP2350" in "sys.implementation._machine": + # Test that UART can be constructed using other tx/rx pins. + UART(0, tx=2, rx=3) diff --git a/tests/ports/rp2/rp2_uart.py.exp b/tests/ports/rp2/rp2_uart.py.exp new file mode 100644 index 0000000000000..4594de5dc78f2 --- /dev/null +++ b/tests/ports/rp2/rp2_uart.py.exp @@ -0,0 +1 @@ +UART(0, baudrate=115200, bits=8, parity=None, stop=1, tx=0, rx=1, txbuf=256, rxbuf=256, timeout=0, timeout_char=1, invert=None) From d06bf7131b58faf75c2a6faaf6ea9bd56b64ff87 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 9 Aug 2024 12:32:48 +1000 Subject: [PATCH 30/87] tools/ci.sh: Add RPI_PICO2 to CI. Signed-off-by: Damien George --- tools/ci.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index 03b6bf59aed4a..1f86c3ed0a7da 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -316,6 +316,8 @@ function ci_rp2_build { make ${MAKEOPTS} -C ports/rp2 make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W submodules make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W USER_C_MODULES=../../examples/usercmodule/micropython.cmake + make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 submodules + make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO submodules make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO From cedb004102dddcc581907e90265833f5bba47eb8 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 6 Aug 2024 19:49:40 +0100 Subject: [PATCH 31/87] ports/rp2: PSRAM support. Add PSRAM support with auto detection. Performs a best-effort attempt to detect attached PSRAM, configure it and *add* it to the MicroPython heap. If PSRAM is not present, should fall back to use internal RAM. Introduce two new port/board defines: * MICROPY_HW_ENABLE_PSRAM to enable PSRAM. * MICROPY_HW_PSRAM_CS_PIN to define the chip-select pin. Changes: ports/rp2/rp2_psram.c/h: Add new PSRAM module. ports/rp2/main.c: Add optional PSRAM support. ports/rp2/CMakeLists.txt: Include rp2_psram.c. ports/rp2/rp2_flash.c: Add buffered write to avoid reads from PSRAM. ports/rp2/mpconfigport.h: Enable MICROPY_GC_SPLIT_HEAP for boards that set MICROPY_HW_ENABLE_PSRAM. Co-authored-by: Kirk Benell Co-authored-by: Mike Bell Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/main.c | 8 ++ ports/rp2/mpconfigport.h | 3 + ports/rp2/rp2_flash.c | 48 +++++++++-- ports/rp2/rp2_psram.c | 180 +++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_psram.h | 11 +++ 6 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 ports/rp2/rp2_psram.c create mode 100644 ports/rp2/rp2_psram.h diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 36b9b284cf7f1..2012a937b7124 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -164,6 +164,7 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c + rp2_psram.c rp2_dma.c uart.c usbd.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 47cf35deee23d..aa248a68a00d2 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -26,6 +26,7 @@ #include +#include "rp2_psram.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -117,7 +118,14 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); + gc_init(&__GcHeapStart, &__GcHeapEnd); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); + if (psram_size) { + gc_add((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + } + #endif #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index b81a218ab5bb6..7fd1d2a7fb84a 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -73,6 +73,9 @@ // Memory allocation policies #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#ifdef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_SPLIT_HEAP (1) +#endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index c1acb54e75748..722bf5c0b76c0 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -26,6 +26,7 @@ #include +#include "rp2_psram.h" #include "py/mphal.h" #include "py/runtime.h" #include "extmod/vfs.h" @@ -76,10 +77,21 @@ static uint32_t begin_critical_flash_section(void) { if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_start_blocking(); } - return save_and_disable_interrupts(); + uint32_t state = save_and_disable_interrupts(); + + // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM + uint8_t *maintenance_ptr = (uint8_t *)XIP_MAINTENANCE_BASE; + for (int i = 1; i < 16 * 1024; i += 8) { + maintenance_ptr[i] = 0; + } + + return state; } static void end_critical_flash_section(uint32_t state) { + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); @@ -145,11 +157,16 @@ static mp_obj_t rp2_flash_readblocks(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_flash_readblocks_obj, 3, 4, rp2_flash_readblocks); +static inline size_t min_size(size_t a, size_t b) { + return a < b ? a : b; +} + static mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { rp2_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); uint32_t offset = mp_obj_get_int(args[1]) * BLOCK_SIZE_BYTES; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + if (n_args == 3) { mp_uint_t atomic_state = begin_critical_flash_section(); flash_range_erase(self->flash_base + offset, bufinfo.len); @@ -159,10 +176,31 @@ static mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { } else { offset += mp_obj_get_int(args[3]); } - mp_uint_t atomic_state = begin_critical_flash_section(); - flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); - end_critical_flash_section(atomic_state); - mp_event_handle_nowait(); + + if ((uintptr_t)bufinfo.buf >= SRAM_BASE) { + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); + end_critical_flash_section(atomic_state); + mp_event_handle_nowait(); + } else { + size_t bytes_left = bufinfo.len; + size_t bytes_offset = 0; + static uint8_t copy_buffer[BLOCK_SIZE_BYTES] = {0}; + + while (bytes_left) { + memcpy(copy_buffer, bufinfo.buf + bytes_offset, min_size(bytes_left, BLOCK_SIZE_BYTES)); + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset + bytes_offset, copy_buffer, min_size(bytes_left, BLOCK_SIZE_BYTES)); + end_critical_flash_section(atomic_state); + bytes_offset += BLOCK_SIZE_BYTES; + if (bytes_left <= BLOCK_SIZE_BYTES) { + break; + } + bytes_left -= BLOCK_SIZE_BYTES; + mp_event_handle_nowait(); + } + } + // TODO check return value return mp_const_none; } diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c new file mode 100644 index 0000000000000..07fd28c49ca8e --- /dev/null +++ b/ports/rp2/rp2_psram.c @@ -0,0 +1,180 @@ +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip_ctrl.h" +#include "hardware/sync.h" +#include "rp2_psram.h" + + +void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { + ; + } + + // For > 133 MHz + qmi_hw->m[0].timing = 0x40000202; + + // For <= 133 MHz + // qmi_hw->m[0].timing = 0x40000101; + + // Force a read through XIP to ensure the timing is applied + volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; + (void)*ptr; +} + +size_t __no_inline_not_in_flash_func(psram_detect)() { + int psram_size = 0; + + uint32_t intr_stash = save_and_disable_interrupts(); + + // Try and read the PSRAM ID via direct_csr. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // Exit out of QMI in case we've inited already + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + + // Transmit as quad. + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + (void)qmi_hw->direct_rx; + + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + + // Read the id + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + uint8_t kgd = 0; + uint8_t eid = 0; + + for (size_t i = 0; i < 7; i++) + { + if (i == 0) { + qmi_hw->direct_tx = 0x9f; + } else { + qmi_hw->direct_tx = 0xff; + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + if (i == 5) { + kgd = qmi_hw->direct_rx; + } else if (i == 6) { + eid = qmi_hw->direct_rx; + } else { + (void)qmi_hw->direct_rx; + } + } + + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + if (kgd == 0x5D) { + psram_size = 1024 * 1024; // 1 MiB + uint8_t size_id = eid >> 5; + if (eid == 0x26 || size_id == 2) { + psram_size *= 8; // 8 MiB + } else if (size_id == 0) { + psram_size *= 2; // 2 MiB + } else if (size_id == 1) { + psram_size *= 4; // 4 MiB + } + } + + restore_interrupts(intr_stash); + return psram_size; +} + +size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { + gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + return 0; + } + + psram_set_qmi_timing(); + + // Enable direct mode, PSRAM CS, clkdiv of 10. + qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ + QMI_DIRECT_CSR_EN_BITS | \ + QMI_DIRECT_CSR_AUTO_CS1N_BITS; + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Enable QPI mode on the PSRAM + const uint CMD_QPI_EN = 0x35; + qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; + + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + #if 0 + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 240MHz + // - Min deselect assumes a sys clock speed <= 305MHz + // - Clkdiv of 2 is OK up to 266MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 30 << QMI_M1_TIMING_MAX_SELECT_LSB | + 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 3 << QMI_M1_TIMING_RXDELAY_LSB | + 2 << QMI_M1_TIMING_CLKDIV_LSB; + #else + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 138MHz + // - Clkdiv of 1 is OK up to 133MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 2 << QMI_M1_TIMING_RXDELAY_LSB | + 1 << QMI_M1_TIMING_CLKDIV_LSB; + #endif + + // Set PSRAM commands and formats + qmi_hw->m[1].rfmt = + QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ + QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ + QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ + 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; + + qmi_hw->m[1].rcmd = 0xEB; + + qmi_hw->m[1].wfmt = + QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ + QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ + QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; + + qmi_hw->m[1].wcmd = 0x38; + + // Disable direct mode + qmi_hw->direct_csr = 0; + + // Enable writes to PSRAM + hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); + + // TODO: Detect PSRAM ID and size + return psram_size; +} diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h new file mode 100644 index 0000000000000..cd791602cdd68 --- /dev/null +++ b/ports/rp2/rp2_psram.h @@ -0,0 +1,11 @@ +#include "pico/stdlib.h" + +#ifndef MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H +#define MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H + +#define PSRAM_LOCATION _u(0x11000000) + +extern void psram_set_qmi_timing(); +extern size_t psram_init(uint cs_pin); + +#endif From fd867f8cfb48fc706d79d2a8d4cdeebb14fb5737 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 10:16:55 +0100 Subject: [PATCH 32/87] ports/rp2: PSRAM: Fix RP2040/Pico build. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 7 ++++++- ports/rp2/rp2_flash.c | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 2012a937b7124..83cfe40d957b9 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -164,7 +164,6 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c - rp2_psram.c rp2_dma.c uart.c usbd.c @@ -173,6 +172,12 @@ set(MICROPY_SOURCE_PORT ${CMAKE_BINARY_DIR}/pins_${MICROPY_BOARD}.c ) +if(PICO_RP2350) + list(APPEND MICROPY_SOURCE_PORT + rp2_psram.c + ) +endif() + set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_PY} ${MICROPY_DIR}/shared/readline/readline.c diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 722bf5c0b76c0..4386986011db2 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -79,11 +79,13 @@ static uint32_t begin_critical_flash_section(void) { } uint32_t state = save_and_disable_interrupts(); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM uint8_t *maintenance_ptr = (uint8_t *)XIP_MAINTENANCE_BASE; for (int i = 1; i < 16 * 1024; i += 8) { maintenance_ptr[i] = 0; } + #endif return state; } From c945805f6fb75d8b410fcc29bb1fc7108d45320d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:10 +0100 Subject: [PATCH 33/87] ports/rp2: Re-init PSRAM on CPU freq change. Signed-off-by: Phil Howard --- ports/rp2/modmachine.c | 4 +++ ports/rp2/rp2_psram.c | 60 ++++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 2faf0bc6f8713..1fb6bc6df9d8e 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -31,6 +31,7 @@ #include "mp_usbd.h" #include "modmachine.h" #include "uart.h" +#include "rp2_psram.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -115,6 +116,9 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { setup_default_uart(); mp_uart_init(); #endif + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif } static void mp_machine_idle(void) { diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 07fd28c49ca8e..90c370c86a55e 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -1,6 +1,7 @@ #include "hardware/structs/ioqspi.h" #include "hardware/structs/qmi.h" #include "hardware/structs/xip_ctrl.h" +#include "hardware/clocks.h" #include "hardware/sync.h" #include "rp2_psram.h" @@ -11,11 +12,13 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { ; } - // For > 133 MHz - qmi_hw->m[0].timing = 0x40000202; - - // For <= 133 MHz - // qmi_hw->m[0].timing = 0x40000101; + if (clock_get_hz(clk_sys) > 133000000) { + // For > 133 MHz + qmi_hw->m[0].timing = 0x40000202; + } else { + // For <= 133 MHz + qmi_hw->m[0].timing = 0x40000101; + } // Force a read through XIP to ensure the timing is applied volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; @@ -123,29 +126,29 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { ; } - #if 0 - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 240MHz - // - Min deselect assumes a sys clock speed <= 305MHz - // - Clkdiv of 2 is OK up to 266MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 30 << QMI_M1_TIMING_MAX_SELECT_LSB | - 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 3 << QMI_M1_TIMING_RXDELAY_LSB | - 2 << QMI_M1_TIMING_CLKDIV_LSB; - #else - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 138MHz - // - Clkdiv of 1 is OK up to 133MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 2 << QMI_M1_TIMING_RXDELAY_LSB | - 1 << QMI_M1_TIMING_CLKDIV_LSB; - #endif + if (clock_get_hz(clk_sys) >= 120000000) { + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 305MHz + // - Clkdiv of 2 is OK up to 266MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 3 << QMI_M1_TIMING_RXDELAY_LSB | + 2 << QMI_M1_TIMING_CLKDIV_LSB; + } else { + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 138MHz + // - Clkdiv of 1 is OK up to 133MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 2 << QMI_M1_TIMING_RXDELAY_LSB | + 1 << QMI_M1_TIMING_CLKDIV_LSB; + } // Set PSRAM commands and formats qmi_hw->m[1].rfmt = @@ -175,6 +178,5 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { // Enable writes to PSRAM hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); - // TODO: Detect PSRAM ID and size return psram_size; } From 2a10aa6a2adbb111f7dfb78ae565f123be5d4fb6 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:33 +0100 Subject: [PATCH 34/87] ports/rp2: Make split-heap optional. My tests found issues when PSRAM is combined with the existing RAM in a split-heap configuration. Since this option is not enabled by default on RP2 I have changed it to be optional. PSRAM will be used exclusively if MICROPY_GC_SPLIT_HEAP == 0, it will be added to RAM if MICROPY_GC_SPLIT_HEAP == 1, and the system will fall back to RAM only if it's not detected. Signed-off-by: Phil Howard --- ports/rp2/main.c | 10 +++++++++- ports/rp2/mpconfigport.h | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index aa248a68a00d2..b5da1e82e35f6 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -119,12 +119,20 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); - gc_init(&__GcHeapStart, &__GcHeapEnd); #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); if (psram_size) { + #if MICROPY_GC_SPLIT_HEAP + gc_init(&__GcHeapStart, &__GcHeapEnd); gc_add((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + #else + gc_init((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + #endif + } else { + gc_init(&__GcHeapStart, &__GcHeapEnd); } + #else + gc_init(&__GcHeapStart, &__GcHeapEnd); #endif #if MICROPY_PY_LWIP diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 7fd1d2a7fb84a..8ac984f04a457 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -73,8 +73,8 @@ // Memory allocation policies #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t -#ifdef MICROPY_HW_ENABLE_PSRAM -#define MICROPY_GC_SPLIT_HEAP (1) +#ifndef MICROPY_GC_SPLIT_HEAP +#define MICROPY_GC_SPLIT_HEAP (0) // whether PSRAM is added to or replaces the heap #endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) From 4cb9a1c677e703a1b65cfce6e6a79801ff8bd276 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 17:34:04 +0100 Subject: [PATCH 35/87] ports/rp2: Compute QMI timing based on system clock. Signed-off-by: Mike Bell --- ports/rp2/rp2_psram.c | 65 ++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 90c370c86a55e..e3da848d09265 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -12,13 +12,15 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { ; } - if (clock_get_hz(clk_sys) > 133000000) { - // For > 133 MHz - qmi_hw->m[0].timing = 0x40000202; - } else { - // For <= 133 MHz - qmi_hw->m[0].timing = 0x40000101; - } + // Use the minimum divisor assuming a 133MHz flash. + // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the + // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. + const int max_flash_freq = 133000000; + const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq; + const int rxdelay = divisor; + qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; // Force a read through XIP to ensure the timing is applied volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; @@ -126,30 +128,35 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { ; } - if (clock_get_hz(clk_sys) >= 120000000) { - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 305MHz - // - Clkdiv of 2 is OK up to 266MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 3 << QMI_M1_TIMING_RXDELAY_LSB | - 2 << QMI_M1_TIMING_CLKDIV_LSB; - } else { - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 138MHz - // - Clkdiv of 1 is OK up to 133MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 2 << QMI_M1_TIMING_RXDELAY_LSB | - 1 << QMI_M1_TIMING_CLKDIV_LSB; + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const int max_psram_freq = 133000000; + const int clock_hz = clock_get_hz(clk_sys); + int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if (divisor == 1 && clock_hz > 100000000) { + divisor = 2; + } + int rxdelay = divisor; + if (clock_hz / divisor > 100000000) { + rxdelay += 1; } + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + const int clock_period_fs = 1000000000000000ll / clock_hz; + const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 + const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; + + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + max_select << QMI_M1_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + // Set PSRAM commands and formats qmi_hw->m[1].rfmt = QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ From 2f05b877fdfc84b72e8c4c75c203cd866b4acf25 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 17:45:15 +0100 Subject: [PATCH 36/87] ports/rp2: Fix garbage collection with large heap. Signed-off-by: Mike Bell --- ports/rp2/mpconfigport.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8ac984f04a457..3e45dfc8ebd25 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -71,8 +71,16 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) #endif +#ifndef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_HW_ENABLE_PSRAM (0) +#endif + // Memory allocation policies +#if MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#else #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#endif #ifndef MICROPY_GC_SPLIT_HEAP #define MICROPY_GC_SPLIT_HEAP (0) // whether PSRAM is added to or replaces the heap #endif From 2be1fdfa279ceeb8abb3d562798b4d6fe684dfbe Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 20:52:34 +0100 Subject: [PATCH 37/87] ports/rp2: Remove flash timing control from PSRAM. Signed-off-by: Mike Bell --- ports/rp2/rp2_psram.c | 23 ----------------------- ports/rp2/rp2_psram.h | 1 - 2 files changed, 24 deletions(-) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index e3da848d09265..30f87adaf4122 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -6,27 +6,6 @@ #include "rp2_psram.h" -void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { - // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) - while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { - ; - } - - // Use the minimum divisor assuming a 133MHz flash. - // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the - // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. - const int max_flash_freq = 133000000; - const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq; - const int rxdelay = divisor; - qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | - rxdelay << QMI_M1_TIMING_RXDELAY_LSB | - divisor << QMI_M1_TIMING_CLKDIV_LSB; - - // Force a read through XIP to ensure the timing is applied - volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; - (void)*ptr; -} - size_t __no_inline_not_in_flash_func(psram_detect)() { int psram_size = 0; @@ -110,8 +89,6 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { return 0; } - psram_set_qmi_timing(); - // Enable direct mode, PSRAM CS, clkdiv of 10. qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ QMI_DIRECT_CSR_EN_BITS | \ diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h index cd791602cdd68..718e7a1ea9e76 100644 --- a/ports/rp2/rp2_psram.h +++ b/ports/rp2/rp2_psram.h @@ -5,7 +5,6 @@ #define PSRAM_LOCATION _u(0x11000000) -extern void psram_set_qmi_timing(); extern size_t psram_init(uint cs_pin); #endif From 40ee10eefadcd725df7d976c59190d07b5580ebb Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 21:39:05 +0100 Subject: [PATCH 38/87] ports/rp2: Set flash divisor appropriately. Signed-off-by: Mike Bell --- ports/rp2/main.c | 4 +++ ports/rp2/modmachine.c | 12 ++++++++ ports/rp2/rp2_flash.c | 69 ++++++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_flash.h | 7 +++++ 4 files changed, 92 insertions(+) create mode 100644 ports/rp2/rp2_flash.h diff --git a/ports/rp2/main.c b/ports/rp2/main.c index b5da1e82e35f6..da36e7a721d97 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -27,6 +27,7 @@ #include #include "rp2_psram.h" +#include "rp2_flash.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -91,6 +92,9 @@ int main(int argc, char **argv) { // Set the MCU frequency and as a side effect the peripheral clock to 48 MHz. set_sys_clock_khz(125000, false); + // Set the flash divisor to an appropriate value + rp2_flash_set_timing(); + #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) setup_default_uart(); diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 1fb6bc6df9d8e..7e9881b6c5206 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -32,6 +32,7 @@ #include "modmachine.h" #include "uart.h" #include "rp2_psram.h" +#include "rp2_flash.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -95,6 +96,11 @@ static mp_obj_t mp_machine_get_freq(void) { static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_int_t freq = mp_obj_get_int(args[0]); + + // If necessary, increase the flash divider before increasing the clock speed + const int old_freq = clock_get_hz(clk_sys); + rp2_flash_set_timing_for_freq(MAX(freq, old_freq)); + if (!set_sys_clock_khz(freq / 1000, false)) { mp_raise_ValueError(MP_ERROR_TEXT("cannot change frequency")); } @@ -112,6 +118,12 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } } } + + // If clock speed was reduced, maybe we can reduce the flash divider + if (freq < old_freq) { + rp2_flash_set_timing_for_freq(freq); + } + #if MICROPY_HW_ENABLE_UART_REPL setup_default_uart(); mp_uart_init(); diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 4386986011db2..45f2ec70e1017 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -33,6 +33,12 @@ #include "modrp2.h" #include "hardware/flash.h" #include "pico/binary_info.h" +#ifdef PICO_RP2350 +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#else +#include "hardware/structs/ssi.h" +#endif #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) @@ -71,6 +77,48 @@ bi_decl(bi_block_device( BINARY_INFO_BLOCK_DEV_FLAG_WRITE | BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); +// Function to set the flash divisor to the correct divisor, assumes interrupts disabled +// and core1 locked out if relevant. +static void __no_inline_not_in_flash_func(rp2_flash_set_timing_internal)(int clock_hz) { + + // Use the minimum divisor assuming a 133MHz flash. + const int max_flash_freq = 133000000; + int divisor = (clock_hz + max_flash_freq - 1) / max_flash_freq; + + #if PICO_RP2350 + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { + ; + } + + // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the + // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. + const int rxdelay = divisor; + qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + + // Force a read through XIP to ensure the timing is applied + volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; + (void)*ptr; + #else + // RP2040 SSI hardware only supports even divisors + if (divisor & 1) { + divisor += 1; + } + + // Wait for SSI not busy + while (ssi_hw->sr & SSI_SR_BUSY_BITS) { + ; + } + + // Disable, set the new divisor, and re-enable + hw_clear_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + ssi_hw->baudr = divisor; + hw_set_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + #endif +} + // Flash erase and write must run with interrupts disabled and the other core suspended, // because the XIP bit gets disabled. static uint32_t begin_critical_flash_section(void) { @@ -94,6 +142,7 @@ static void end_critical_flash_section(uint32_t state) { #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); @@ -250,3 +299,23 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, rp2_flash_make_new, locals_dict, &rp2_flash_locals_dict ); + +// Modify the flash timing. Ensure flash access is suspended while +// the timings are altered. +void rp2_flash_set_timing_for_freq(int clock_hz) { + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_start_blocking(); + } + uint32_t state = save_and_disable_interrupts(); + + rp2_flash_set_timing_internal(clock_hz); + + restore_interrupts(state); + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_end_blocking(); + } +} + +void rp2_flash_set_timing() { + rp2_flash_set_timing_for_freq(clock_get_hz(clk_sys)); +} diff --git a/ports/rp2/rp2_flash.h b/ports/rp2/rp2_flash.h new file mode 100644 index 0000000000000..d5cf3ba2acac0 --- /dev/null +++ b/ports/rp2/rp2_flash.h @@ -0,0 +1,7 @@ +#ifndef MICROPY_INCLUDED_RP2_MACHINE_FLASH_H +#define MICROPY_INCLUDED_RP2_MACHINE_FLASH_H + +extern void rp2_flash_set_timing_for_freq(int clock_hz); +extern void rp2_flash_set_timing(); + +#endif From 4b7b1af74d24dbcbd27058e4f985780bb218dafc Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Thu, 15 Aug 2024 14:01:57 +0100 Subject: [PATCH 39/87] ports/rp2: Reset flash timing before PSRAM timing. Signed-off-by: Mike Bell --- ports/rp2/rp2_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 45f2ec70e1017..eae219f49ea9e 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -139,10 +139,10 @@ static uint32_t begin_critical_flash_section(void) { } static void end_critical_flash_section(uint32_t state) { + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif - rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); From 870d858a3cb6488ae7e509f4841e7c5f978f0c7b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 15 Aug 2024 16:18:09 +0100 Subject: [PATCH 40/87] ports/rp2: Add RP2350 slices to machine_pwm. Signed-off-by: Phil Howard --- ports/rp2/machine_pwm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_pwm.c b/ports/rp2/machine_pwm.c index 4c0bc2c103243..fd20be5ec2f6f 100644 --- a/ports/rp2/machine_pwm.c +++ b/ports/rp2/machine_pwm.c @@ -67,10 +67,20 @@ static machine_pwm_obj_t machine_pwm_obj[] = { {{&machine_pwm_type}, 6, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, {{&machine_pwm_type}, 7, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, {{&machine_pwm_type}, 7, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + #if NUM_PWM_SLICES == 12 + {{&machine_pwm_type}, 8, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 8, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 9, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 9, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 10, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 10, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 11, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 11, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + #endif }; static bool defer_start; -static bool slice_freq_set[8]; +static bool slice_freq_set[NUM_PWM_SLICES]; static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq); static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16); @@ -155,7 +165,7 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Stop all active slices. void machine_pwm_deinit_all(void) { - for (int i = 0; i < 8; i++) { + for (int i = 0; i < NUM_PWM_SLICES; i++) { slice_freq_set[i] = false; pwm_set_enabled(machine_pwm_obj[i].slice, false); } From 684cb188e28df08343e1153f8879d3bb66a58717 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Wed, 28 Aug 2024 11:50:41 -0600 Subject: [PATCH 41/87] adding board def files for the SparkFun ProMicro RP2350 --- .../SPARKFUN_PROMICRO_RP2350/board.json | 21 +++++++++++++++++++ .../mpconfigboard.cmake | 5 +++++ .../SPARKFUN_PROMICRO_RP2350/mpconfigboard.h | 18 ++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100755 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json create mode 100755 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake create mode 100755 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json new file mode 100755 index 0000000000000..15630b39e99b4 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "JST-SH", + "RGB LED", + "USB-C" + ], + "images": [ + "17745-SparkFun_Thing_Plus_-_RP2040-01a.jpg" + ], + "mcu": "rp2350", + "product": "Pro Micro RP2350", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/24870", + "vendor": "Sparkfun" +} \ No newline at end of file diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake new file mode 100755 index 0000000000000..b1682bdd01fa0 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake @@ -0,0 +1,5 @@ +# cmake file for SparkFun Pro Micro RP2350 +set(PICO_BOARD "sparkfun_promicro_rp2350") + +# To change the gpio count for QFN-80 +# set(PICO_NUM_GPIOS 48) \ No newline at end of file diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h new file mode 100755 index 0000000000000..903d343ff5cf1 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h @@ -0,0 +1,18 @@ +// https://www.sparkfun.com/products/17717 + +#define MICROPY_HW_BOARD_NAME "SparkFun Pro Micro RP2350" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1024 * 1024) + +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0039) + +#define MICROPY_HW_UART1_TX (8) +#define MICROPY_HW_UART1_RX (9) +#define MICROPY_HW_UART1_CTS (10) +#define MICROPY_HW_UART1_RTS (11) + +#define MICROPY_HW_PSRAM_CS_PIN (19) + +#define MICROPY_HW_ENABLE_PSRAM (1) + +// NeoPixel data GPIO25, power not toggleable From 4e814d1806091116faa527f6c61602c8031aa3c2 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Wed, 28 Aug 2024 15:56:03 -0600 Subject: [PATCH 42/87] added freq for psram, and a few comments --- ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h index 903d343ff5cf1..124bb83c45c08 100755 --- a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h @@ -11,8 +11,14 @@ #define MICROPY_HW_UART1_CTS (10) #define MICROPY_HW_UART1_RTS (11) -#define MICROPY_HW_PSRAM_CS_PIN (19) +// PSRAM things + +// Enable PSRAM #define MICROPY_HW_ENABLE_PSRAM (1) +// CS Pin for the boards PSRAM +#define MICROPY_HW_PSRAM_CS_PIN (19) +// PSRAM max frequency is based on VDD (see datasheet) - SparkFun Pro Micro RP2350 uses 3.3V => 109MHz +#define MICROPY_HW_PSRAM_MAX_SCK_HZ (109*1000*1000) // NeoPixel data GPIO25, power not toggleable From b60484ac63afd047b50c05e3c9e0fca696458973 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Wed, 28 Aug 2024 15:57:32 -0600 Subject: [PATCH 43/87] updated PSRAM logic based on work at SparkFun; cleaned up *magic numbers*, added comments and detailed settings to support implementation overview/undestanding --- ports/rp2/main.c | 4 +- ports/rp2/modmachine.c | 4 +- ports/rp2/rp2_flash.c | 5 +- ports/rp2/rp2_psram.c | 236 ++++++++++++++++++++++++++++++----------- ports/rp2/rp2_psram.h | 28 ++++- 5 files changed, 209 insertions(+), 68 deletions(-) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index da36e7a721d97..9d28421cdea9b 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -123,8 +123,8 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); - #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM - size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM && defined(MICROPY_HW_PSRAM_MAX_SCK_HZ) + size_t psram_size = rp2_psram_init(MICROPY_HW_PSRAM_CS_PIN, MICROPY_HW_PSRAM_MAX_SCK_HZ); if (psram_size) { #if MICROPY_GC_SPLIT_HEAP gc_init(&__GcHeapStart, &__GcHeapEnd); diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 7e9881b6c5206..117ba6193ae25 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -128,8 +128,8 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { setup_default_uart(); mp_uart_init(); #endif - #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM - psram_init(MICROPY_HW_PSRAM_CS_PIN); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM && defined(MICROPY_HW_PSRAM_MAX_SCK_HZ) + rp2_psram_set_timing(MICROPY_HW_PSRAM_MAX_SCK_HZ); #endif } diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index eae219f49ea9e..9280dc5f8ecc2 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -140,8 +140,9 @@ static uint32_t begin_critical_flash_section(void) { static void end_critical_flash_section(uint32_t state) { rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); - #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM - psram_init(MICROPY_HW_PSRAM_CS_PIN); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM && defined(MICROPY_HW_PSRAM_MAX_SCK_HZ) + // update timing for PSRAM + rp2_psram_set_timing(MICROPY_HW_PSRAM_MAX_SCK_HZ); #endif restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 30f87adaf4122..4696a233ab6f9 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -1,3 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Damien P. George + * + * 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 "hardware/structs/ioqspi.h" #include "hardware/structs/qmi.h" #include "hardware/structs/xip_ctrl.h" @@ -5,8 +31,49 @@ #include "hardware/sync.h" #include "rp2_psram.h" +// Notes on the PSRAM IC supported +// +// Most boards seem to support a variant of the following PSRAM IC: +// +// apmemory APS6404L-3SQR-ZR +// https://www.mouser.com/ProductDetail/AP-Memory/APS6404L-3SQR-ZR?qs=IS%252B4QmGtzzpDOdsCIglviw%3D%3D +// +// The origin of this logic is from the Circuit Python code that was downloaded from: +// https://github.com/raspberrypi/pico-sdk-rp2350/issues/12#issuecomment-2055274428 +// +// The returnedID for the above IC +const uint8_t PSRAM_ID = 0x5D; + +// From the above datasheet, PSRAM SPI command codes + +const uint8_t PSRAM_CMD_QUAD_END = 0xF5; +const uint8_t PSRAM_CMD_QUAD_ENABLE = 0x35; +const uint8_t PSRAM_CMD_READ_ID = 0x9F; +const uint8_t PSRAM_CMD_RSTEN = 0x66; +const uint8_t PSRAM_CMD_RST = 0x99; +const uint8_t PSRAM_CMD_QUAD_READ = 0xEB; +const uint8_t PSRAM_CMD_QUAD_WRITE = 0x38; +const uint8_t PSRAM_CMD_NOOP = 0xFF; + + +// For PSRAM timing calculations - to use int math, we work in femto seconds (fs) (1e-15), +// NOTE: This idea is from micro python work on psram.. + +#define SEC_TO_FS 1000000000000000ll + +// max select pulse width = 8us => 8e6 ns => 8000 ns => 8000 * 1e6 fs => 8000e6 fs +// Additionally, the MAX select is in units of 64 clock cycles - will use a constant that +// takes this into account - so 8000e6 fs / 64 = 125e6 fs +const uint32_t PSRAM_MAX_SELECT_FS64 = 125000000; + +// min deselect pulse width = 18ns => 18 * 1e6 fs => 18e6 fs +const uint32_t PSRAM_MIN_DESELECT_FS = 18000000; + +// Define the divisor limit for at 100MHz +#define PSRAM_DIVISOR_LIMIT 100000000 size_t __no_inline_not_in_flash_func(psram_detect)() { + int psram_size = 0; uint32_t intr_stash = save_and_disable_interrupts(); @@ -23,8 +90,8 @@ size_t __no_inline_not_in_flash_func(psram_detect)() { // Exit out of QMI in case we've inited already qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; - // Transmit as quad. - qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; + // Transmit the command to exit QPI quad mode - read ID as standard SPI + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | PSRAM_CMD_QUAD_END; while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { } @@ -35,15 +102,15 @@ size_t __no_inline_not_in_flash_func(psram_detect)() { // Read the id qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; - uint8_t kgd = 0; - uint8_t eid = 0; + uint8_t kgd = 0; // known good die + uint8_t eid = 0; // density/size for (size_t i = 0; i < 7; i++) { if (i == 0) { - qmi_hw->direct_tx = 0x9f; + qmi_hw->direct_tx = PSRAM_CMD_READ_ID; } else { - qmi_hw->direct_tx = 0xff; + qmi_hw->direct_tx = PSRAM_CMD_NOOP; } while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { @@ -64,7 +131,7 @@ size_t __no_inline_not_in_flash_func(psram_detect)() { // Disable direct csr. qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); - if (kgd == 0x5D) { + if (kgd == PSRAM_ID) { psram_size = 1024 * 1024; // 1 MiB uint8_t size_id = eid >> 5; if (eid == 0x26 || size_id == 2) { @@ -80,84 +147,131 @@ size_t __no_inline_not_in_flash_func(psram_detect)() { return psram_size; } -size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { - gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); - - size_t psram_size = psram_detect(); - - if (!psram_size) { - return 0; - } - - // Enable direct mode, PSRAM CS, clkdiv of 10. - qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ - QMI_DIRECT_CSR_EN_BITS | \ - QMI_DIRECT_CSR_AUTO_CS1N_BITS; - while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { - ; - } - - // Enable QPI mode on the PSRAM - const uint CMD_QPI_EN = 0x35; - qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; - - while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { - ; - } +//----------------------------------------------------------------------------- +/// @brief Update the PSRAM timing configuration based on system clock +/// +/// @param max_psram_freq The maximum frequency the PSRAM can operate at. +// +// Note: Made this into a fuction incase it's needed to be called if the system clock changes. +// + void __no_inline_not_in_flash_func(rp2_psram_set_timing)(const uint32_t max_psram_freq) +{ // Set PSRAM timing for APS6404 // + // Note: The max SCK frequency of the APS6404 depends on the VDD voltage supplied. + // The max frequency is 133MHz for VDD = 3.0V, 109MHz for VDD = 3.3V + // See the pages 1 of the above datasheet for more details. Since this depends on + // the board design, this is a parameter passed to this function. + // // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). - const int max_psram_freq = 133000000; - const int clock_hz = clock_get_hz(clk_sys); - int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; - if (divisor == 1 && clock_hz > 100000000) { + + const uint32_t clock_hz = clock_get_hz(clk_sys); + uint8_t divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if (divisor == 1 && clock_hz > PSRAM_DIVISOR_LIMIT) { divisor = 2; } - int rxdelay = divisor; - if (clock_hz / divisor > 100000000) { + uint8_t rxdelay = divisor; + if (clock_hz / divisor > PSRAM_DIVISOR_LIMIT) { rxdelay += 1; } - // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // clock period in femto seconds + const uint32_t clock_period_fs = SEC_TO_FS / clock_hz; + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // So maxFS / (64 * clock_period_fs) = max_select = PSRAM_MAX_SELECT_FS64/clock_period_fs + const uint8_t max_select = PSRAM_MAX_SELECT_FS64 / clock_period_fs; // 125 = 8000ns / 64 + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). - const int clock_period_fs = 1000000000000000ll / clock_hz; - const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 - const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; + const uint8_t min_deselect = (PSRAM_MIN_DESELECT_FS + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | // Break between pages. max_select << QMI_M1_TIMING_MAX_SELECT_LSB | min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | rxdelay << QMI_M1_TIMING_RXDELAY_LSB | divisor << QMI_M1_TIMING_CLKDIV_LSB; +} + +size_t __no_inline_not_in_flash_func(rp2_psram_init)(uint cs_pin, const uint32_t max_psram_freq) { + + gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + return 0; + } + + // After the PSRAM detect operation, and this is startup, do a RESETEN, RESET and quad enable + + // Enable QPI mode on the PSRAM - use "direct mode" to send the command + // From the flash boot ASM code (in the SDK): + // Need to use direct serial mode to send SR commands. Choose a + // conservative direct-mode divisor (5 MHz at 150 MHz clk_sys) + // since the XIP-mode divisor may be unsafe without an RX delay. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + // RESETEN, RESET and quad enable + for (uint8_t i = 0; i < 3; i++) + { + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + if (i == 0) + qmi_hw->direct_tx = PSRAM_CMD_RSTEN; + else if (i == 1) + qmi_hw->direct_tx = PSRAM_CMD_RST; + else + qmi_hw->direct_tx = PSRAM_CMD_QUAD_ENABLE; + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) + { + } + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + for (size_t j = 0; j < 20; j++) + asm("nop"); + + (void)qmi_hw->direct_rx; + } + + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + // setup the PSRAM timing + rp2_psram_set_timing(max_psram_freq); // Set PSRAM commands and formats qmi_hw->m[1].rfmt = - QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ - QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ - QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ - QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ - QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ - QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ - 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; + (QMI_M1_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M1_RFMT_PREFIX_WIDTH_LSB | + QMI_M1_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M1_RFMT_ADDR_WIDTH_LSB | + QMI_M1_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M1_RFMT_SUFFIX_WIDTH_LSB | + QMI_M1_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M1_RFMT_DUMMY_WIDTH_LSB | + QMI_M1_RFMT_DATA_WIDTH_VALUE_Q << QMI_M1_RFMT_DATA_WIDTH_LSB | + QMI_M1_RFMT_PREFIX_LEN_VALUE_8 << QMI_M1_RFMT_PREFIX_LEN_LSB | + QMI_M1_RFMT_DUMMY_LEN_VALUE_24 << QMI_M1_RFMT_DUMMY_LEN_LSB | + QMI_M1_RFMT_SUFFIX_LEN_VALUE_NONE << QMI_M1_RFMT_SUFFIX_LEN_LSB ); - qmi_hw->m[1].rcmd = 0xEB; + qmi_hw->m[1].rcmd = PSRAM_CMD_QUAD_READ << QMI_M1_RCMD_PREFIX_LSB; qmi_hw->m[1].wfmt = - QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ - QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ - QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ - QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ - QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ - QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; - - qmi_hw->m[1].wcmd = 0x38; - - // Disable direct mode - qmi_hw->direct_csr = 0; + ( QMI_M1_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M1_WFMT_PREFIX_WIDTH_LSB | + QMI_M1_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M1_WFMT_ADDR_WIDTH_LSB | + QMI_M1_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M1_WFMT_SUFFIX_WIDTH_LSB | + QMI_M1_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M1_WFMT_DUMMY_WIDTH_LSB | + QMI_M1_WFMT_DUMMY_LEN_VALUE_NONE << QMI_M1_WFMT_DUMMY_LEN_LSB | + QMI_M1_WFMT_DATA_WIDTH_VALUE_Q << QMI_M1_WFMT_DATA_WIDTH_LSB | + QMI_M1_WFMT_PREFIX_LEN_VALUE_8 << QMI_M1_WFMT_PREFIX_LEN_LSB | + QMI_M1_WFMT_SUFFIX_LEN_VALUE_NONE << QMI_M1_WFMT_SUFFIX_LEN_LSB ); + + qmi_hw->m[1].wcmd = PSRAM_CMD_QUAD_WRITE << QMI_M1_WCMD_PREFIX_LSB; // Enable writes to PSRAM hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h index 718e7a1ea9e76..dd558bcbe04a7 100644 --- a/ports/rp2/rp2_psram.h +++ b/ports/rp2/rp2_psram.h @@ -1,3 +1,28 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Damien P. George + * + * 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 "pico/stdlib.h" #ifndef MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H @@ -5,6 +30,7 @@ #define PSRAM_LOCATION _u(0x11000000) -extern size_t psram_init(uint cs_pin); +extern size_t rp2_psram_init(uint cs_pin, const uint32_t max_psram_freq); +extern void rp2_psram_set_timing(const uint32_t max_psram_freq); #endif From 9969d3fb4c577b929fa0815ec274872c35d21bf7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:26:27 +1000 Subject: [PATCH 44/87] lib/pico-sdk: Update to new rp2350 version. Signed-off-by: Damien George --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index 6a7db34ff6334..efe2103f9b284 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit 6a7db34ff63345a7badec79ebea3aaef1712f374 +Subproject commit efe2103f9b28458a1615ff096054479743ade236 From 1f2d23c091c3181ac16c258250350a630cc22885 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:26:45 +1000 Subject: [PATCH 45/87] lib/tinyusb: Update to support RP2350. Signed-off-by: Damien George --- lib/tinyusb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tinyusb b/lib/tinyusb index d10b65ada4be7..4232642899362 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit d10b65ada4be7d5754b3128e80a9b4db72bdb23f +Subproject commit 4232642899362fa5e9cf0dc59bad6f1f6d32c563 From e1fa6c23376d4a59c1cd1fc94f1f3f7a41a88a8a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 Aug 2024 14:20:35 +1000 Subject: [PATCH 46/87] shared/tinyusb: Use new persistent-tx-fifo configure interface. The old configuration option has been removed from TinyUSB. Signed-off-by: Damien George --- shared/tinyusb/mp_usbd.h | 10 +++++++++- shared/tinyusb/mp_usbd_runtime.c | 6 ++++-- shared/tinyusb/tusb_config.h | 1 - 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 31234566b6ca4..5c8f2a6095f30 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -40,6 +40,13 @@ #include "device/dcd.h" #endif +// Initialise TinyUSB device. +static inline void mp_usbd_init_tud(void) { + tusb_init(); + tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; + tud_cdc_configure_fifo(&cfg); +} + // Run the TinyUSB device task void mp_usbd_task(void); @@ -125,7 +132,8 @@ inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd static inline void mp_usbd_init(void) { // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() - tusb_init(); + // which is called in the below helper function. + mp_usbd_init_tud(); } #endif diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index fe28a4a727791..608aa74d244f6 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -428,8 +428,10 @@ void mp_usbd_init(void) { } if (need_usb) { - tusb_init(); // Safe to call redundantly - tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected + // The following will call tusb_init(), which is safe to call redundantly. + mp_usbd_init_tud(); + // Reconnect if mp_usbd_deinit() has disconnected. + tud_connect(); } } diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index d0c9f89ec600c..0cc5ef03985ef 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -83,7 +83,6 @@ #ifndef CFG_TUD_CDC_TX_BUFSIZE #define CFG_TUD_CDC_TX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) #endif -#define CFG_TUD_CDC_PERSISTENT_TX_BUFF (1) #endif // MSC Configuration From 8805abbf5201bbd37688e55324ab1cb5b47a5b09 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:21:56 +0100 Subject: [PATCH 47/87] rp2/boards: Add RPI_PICO2 board. Signed-off-by: Damien George --- ports/rp2/boards/RPI_PICO2/board.json | 22 +++++++++++++++ .../rp2/boards/RPI_PICO2/mpconfigboard.cmake | 2 ++ ports/rp2/boards/RPI_PICO2/mpconfigboard.h | 3 ++ .../boards/RPI_PICO2/mpconfigvariant.cmake | 1 + .../RPI_PICO2/mpconfigvariant_RISCV.cmake | 1 + ports/rp2/boards/RPI_PICO2/pins.csv | 28 +++++++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 ports/rp2/boards/RPI_PICO2/board.json create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigboard.h create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/RPI_PICO2/pins.csv diff --git a/ports/rp2/boards/RPI_PICO2/board.json b/ports/rp2/boards/RPI_PICO2/board.json new file mode 100644 index 0000000000000..8f3e4bde71a2e --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "USB" + ], + "images": [ + "rp2-pico2.jpg" + ], + "mcu": "rp2350", + "product": "Pico 2", + "thumbnail": "", + "url": "https://www.raspberrypi.com/products/raspberry-pi-pico-2/", + "variants": { + "RISCV": "RISC-V CPU mode" + }, + "vendor": "Raspberry Pi" +} diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake new file mode 100644 index 0000000000000..80e84eef184b5 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake @@ -0,0 +1,2 @@ +# cmake file for Raspberry Pi Pico2 +set(PICO_BOARD "pico2") diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.h b/ports/rp2/boards/RPI_PICO2/mpconfigboard.h new file mode 100644 index 0000000000000..4b5eac6eb3ae3 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico2" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1024 * 1024) diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake new file mode 100644 index 0000000000000..6fe039ba51bba --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake new file mode 100644 index 0000000000000..65a97fc3350d1 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/RPI_PICO2/pins.csv b/ports/rp2/boards/RPI_PICO2/pins.csv new file mode 100644 index 0000000000000..16e334026424f --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2/pins.csv @@ -0,0 +1,28 @@ +GP0,GPIO0 +GP1,GPIO1 +GP2,GPIO2 +GP3,GPIO3 +GP4,GPIO4 +GP5,GPIO5 +GP6,GPIO6 +GP7,GPIO7 +GP8,GPIO8 +GP9,GPIO9 +GP10,GPIO10 +GP11,GPIO11 +GP12,GPIO12 +GP13,GPIO13 +GP14,GPIO14 +GP15,GPIO15 +GP16,GPIO16 +GP17,GPIO17 +GP18,GPIO18 +GP19,GPIO19 +GP20,GPIO20 +GP21,GPIO21 +GP22,GPIO22 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +LED,GPIO25 From fb2f955273eb0f5db94904649c7de80f90166840 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:34:32 +0100 Subject: [PATCH 48/87] rp2/mpconfigport: Set MCU name for RP2350. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 49acafe239b9d..297455a9f9abb 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -35,7 +35,16 @@ #include "mpconfigboard.h" // Board and hardware specific configuration +#if PICO_RP2040 #define MICROPY_HW_MCU_NAME "RP2040" +#elif PICO_RP2350 && PICO_ARM +#define MICROPY_HW_MCU_NAME "RP2350" +#elif PICO_RP2350 && PICO_RISCV +#define MICROPY_HW_MCU_NAME "RP2350-RISCV" +#else +#error Unknown MCU +#endif + #ifndef MICROPY_HW_ENABLE_UART_REPL #define MICROPY_HW_ENABLE_UART_REPL (0) // useful if there is no USB #endif From 8231d6e0fed67cda88e717a6f8e007b4d7791e2f Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:49:46 +0100 Subject: [PATCH 49/87] rp2: Update custom linker scripts for new pico-sdk. Signed-off-by: Phil Howard Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 2 +- .../rp2/{memmap_mp.ld => memmap_mp_rp2040.ld} | 0 ports/rp2/memmap_mp_rp2350.ld | 313 ++++++++++++++++++ 3 files changed, 314 insertions(+), 1 deletion(-) rename ports/rp2/{memmap_mp.ld => memmap_mp_rp2040.ld} (100%) create mode 100644 ports/rp2/memmap_mp_rp2350.ld diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 904925ae3f5e5..1dee8c599d723 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -523,7 +523,7 @@ endif() # a linker script modification) until we explicitly add macro calls around the function # defs to move them into RAM. if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) - pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp.ld) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_${PICO_PLATFORM}.ld) endif() pico_add_extra_outputs(${MICROPY_TARGET}) diff --git a/ports/rp2/memmap_mp.ld b/ports/rp2/memmap_mp_rp2040.ld similarity index 100% rename from ports/rp2/memmap_mp.ld rename to ports/rp2/memmap_mp_rp2040.ld diff --git a/ports/rp2/memmap_mp_rp2350.ld b/ports/rp2/memmap_mp_rp2350.ld new file mode 100644 index 0000000000000..3d57117a8198e --- /dev/null +++ b/ports/rp2/memmap_mp_rp2350.ld @@ -0,0 +1,313 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 4096k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *libgcc.a:cmse_nonsecure_call.o + /* Change for MicroPython... exclude gc.c, parse.c, vm.c from flash */ + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *gc.c.obj *vm.c.obj *parse.c.obj) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + /* Note the boot2 section is optional, and should be discarded if there is + no reference to it *inside* the binary, as it is not called by the + bootrom. (The bootrom performs a simple best-effort XIP setup and + leaves it to the binary to do anything more sophisticated.) However + there is still a size limit of 256 bytes, to ensure the boot2 can be + stored in boot RAM. + + Really this is a "XIP setup function" -- the name boot2 is historic and + refers to its dual-purpose on RP2040, where it also handled vectoring + from the bootrom into the user image. + */ + + .boot2 : { + __boot2_start__ = .; + *(.boot2) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ <= 256, + "ERROR: Pico second stage bootloader must be no more than 256 bytes in size") + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + *(.srodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + *(.sdata*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + PROVIDE(__global_pointer$ = . + 2K); + *(.sbss*) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + /* Change for MicroPython: don't include this, it increases reported firmware size. + /* . = ORIGIN(RAM) + LENGTH(RAM); */ + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + KEEP(*(.embedded_end_block*)) + PROVIDE(__flash_binary_end = .); + } > FLASH =0xaa + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = __bss_end__ + __micropy_c_heap_size__; + + /* Define start and end of GC heap */ + __GcHeapStart = __StackLimit; /* after the C heap (sbrk limit) */ + __GcHeapEnd = ORIGIN(RAM) + LENGTH(RAM) - __micropy_extra_stack__; + + /* Define start and end of C stack */ + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackBottom = __GcHeapEnd; + PROVIDE(__stack = __StackTop); + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check GC heap is at least 64 KB */ + /* This is half the minimum RAM suggested for full-featured MicroPython. + * This value accounts for large static buffers included in user C or C++ + * modules, which might significantly reduce the available heap but also + * lower demand for memory at runtime. + */ + ASSERT((__GcHeapEnd - __GcHeapStart) > 64*1024, "GcHeap is too small") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") + ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") + + /* todo assert on extra code */ +} + From e10d3b2ba0cf094ee65bb4bb6b7386ead6b3f2c5 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 22 May 2024 15:24:24 +0100 Subject: [PATCH 50/87] rp2/modmachine: Implement lightsleep for RP2350. Signed-off-by: Damien George --- ports/rp2/modmachine.c | 52 +++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 372c17bcf9d6b..0acae25499996 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -162,6 +162,9 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } clock_stop(clk_adc); + #if PICO_RP2350 + clock_stop(clk_hstx); + #endif // CLK_REF = XOSC clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz); @@ -170,7 +173,9 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz); // CLK_RTC = XOSC / 256 + #if PICO_RP2040 clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, xosc_hz, xosc_hz / 256); + #endif // CLK_PERI = CLK_SYS clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, xosc_hz, xosc_hz); @@ -190,38 +195,63 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif xosc_dormant(); } else { - uint32_t sleep_en0 = clocks_hw->sleep_en0; - uint32_t sleep_en1 = clocks_hw->sleep_en1; bool timer3_enabled = irq_is_enabled(3); - clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + const uint32_t alarm_num = 3; + const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num); if (use_timer_alarm) { // Make sure ALARM3/IRQ3 is enabled on _this_ core - timer_hw->inte |= 1 << 3; if (!timer3_enabled) { - irq_set_enabled(3, true); + irq_set_enabled(irq_num, true); } + hw_set_bits(&timer_hw->inte, 1u << alarm_num); // Use timer alarm to wake. + clocks_hw->sleep_en0 = 0x0; + #if PICO_RP2040 clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; - timer_hw->alarm[3] = timer_hw->timerawl + delay_ms * 1000; + #elif PICO_RP2350 + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS | CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS; + #else + #error Unknown processor + #endif + timer_hw->intr = 1u << alarm_num; // clear any IRQ + timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000; } else { // TODO: Use RTC alarm to wake. - clocks_hw->sleep_en1 = 0; + clocks_hw->sleep_en0 = 0x0; + clocks_hw->sleep_en1 = 0x0; } if (!disable_usb) { clocks_hw->sleep_en0 |= CLOCKS_SLEEP_EN0_CLK_SYS_PLL_USB_BITS; + #if PICO_RP2040 clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_USBCTRL_BITS; + #elif PICO_RP2350 + clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_SYS_USBCTRL_BITS; + #else + #error Unknown processor + #endif } + #if PICO_ARM + // Configure SLEEPDEEP bits on Cortex-M CPUs. + #if PICO_RP2040 scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS; + #elif PICO_RP2350 + scb_hw->scr |= M33_SCR_SLEEPDEEP_BITS; + #else + #error Unknown processor + #endif + #endif + + // Go into low-power mode. __wfi(); - scb_hw->scr &= ~M0PLUS_SCR_SLEEPDEEP_BITS; + if (!timer3_enabled) { - irq_set_enabled(3, false); + irq_set_enabled(irq_num, false); } - clocks_hw->sleep_en0 = sleep_en0; - clocks_hw->sleep_en1 = sleep_en1; + clocks_hw->sleep_en0 |= ~(0u); + clocks_hw->sleep_en1 |= ~(0u); } // Enable ROSC. From 5d7c56deaef5ad0858798258a031871a238f9398 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 3 Jun 2024 16:14:43 +0100 Subject: [PATCH 51/87] rp2/rp2_dma: Generalise DMA for RP2350. Signed-off-by: Damien George --- ports/rp2/rp2_dma.c | 46 +++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 3afcf8b5d63af..78f69e6452758 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -57,23 +57,28 @@ typedef struct _rp2_dma_ctrl_field_t { } rp2_dma_ctrl_field_t; static rp2_dma_ctrl_field_t rp2_dma_ctrl_fields_table[] = { - { MP_QSTR_enable, 0, 1, 0 }, - { MP_QSTR_high_pri, 1, 1, 0 }, - { MP_QSTR_size, 2, 2, 0 }, - { MP_QSTR_inc_read, 4, 1, 0 }, - { MP_QSTR_inc_write, 5, 1, 0 }, - { MP_QSTR_ring_size, 6, 4, 0 }, - { MP_QSTR_ring_sel, 10, 1, 0 }, - { MP_QSTR_chain_to, 11, 4, 0 }, - { MP_QSTR_treq_sel, 15, 6, 0 }, - { MP_QSTR_irq_quiet, 21, 1, 0 }, - { MP_QSTR_bswap, 22, 1, 0 }, - { MP_QSTR_sniff_en, 23, 1, 0 }, - { MP_QSTR_busy, 24, 1, 1 }, - // bits 25 through 28 are reserved - { MP_QSTR_write_err, 29, 1, 0 }, - { MP_QSTR_read_err, 30, 1, 0 }, - { MP_QSTR_ahb_err, 31, 1, 1 }, + { MP_QSTR_enable, DMA_CH0_CTRL_TRIG_EN_LSB, 1, 0 }, + { MP_QSTR_high_pri, DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_LSB, 1, 0 }, + { MP_QSTR_size, DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB, 2, 0 }, + { MP_QSTR_inc_read, DMA_CH0_CTRL_TRIG_INCR_READ_LSB, 1, 0 }, + #if PICO_RP2350 + { MP_QSTR_inc_read_rev, DMA_CH0_CTRL_TRIG_INCR_READ_REV_LSB, 1, 0 }, + #endif + { MP_QSTR_inc_write, DMA_CH0_CTRL_TRIG_INCR_WRITE_LSB, 1, 0 }, + #if PICO_RP2350 + { MP_QSTR_inc_write_rev, DMA_CH0_CTRL_TRIG_INCR_WRITE_REV_LSB, 1, 0 }, + #endif + { MP_QSTR_ring_size, DMA_CH0_CTRL_TRIG_RING_SIZE_LSB, 4, 0 }, + { MP_QSTR_ring_sel, DMA_CH0_CTRL_TRIG_RING_SEL_LSB, 1, 0 }, + { MP_QSTR_chain_to, DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB, 4, 0 }, + { MP_QSTR_treq_sel, DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB, 6, 0 }, + { MP_QSTR_irq_quiet, DMA_CH0_CTRL_TRIG_IRQ_QUIET_LSB, 1, 0 }, + { MP_QSTR_bswap, DMA_CH0_CTRL_TRIG_BSWAP_LSB, 1, 0 }, + { MP_QSTR_sniff_en, DMA_CH0_CTRL_TRIG_SNIFF_EN_LSB, 1, 0 }, + { MP_QSTR_busy, DMA_CH0_CTRL_TRIG_BUSY_LSB, 1, 1 }, + { MP_QSTR_write_err, DMA_CH0_CTRL_TRIG_WRITE_ERROR_LSB, 1, 0 }, + { MP_QSTR_read_err, DMA_CH0_CTRL_TRIG_READ_ERROR_LSB, 1, 0 }, + { MP_QSTR_ahb_err, DMA_CH0_CTRL_TRIG_AHB_ERROR_LSB, 1, 1 }, }; static const uint32_t rp2_dma_ctrl_field_count = MP_ARRAY_SIZE(rp2_dma_ctrl_fields_table); @@ -298,7 +303,12 @@ static mp_obj_t rp2_dma_active(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_dma_active_obj, 1, 2, rp2_dma_active); // Default is quiet, unpaced, read and write incrementing, word transfers, enabled -#define DEFAULT_DMA_CONFIG (1 << 21) | (0x3f << 15) | (1 << 5) | (1 << 4) | (2 << 2) | (1 << 0) +#define DEFAULT_DMA_CONFIG (1 << DMA_CH0_CTRL_TRIG_IRQ_QUIET_LSB) | \ + (DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_INCR_WRITE_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_INCR_READ_LSB) | \ + (2 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB) | \ + (1 << DMA_CH0_CTRL_TRIG_EN_LSB) // DMA.pack_ctrl(...) static mp_obj_t rp2_dma_pack_ctrl(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { From ee6805ee18893d6ff16fa9611fe109e1ee04d433 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 23 May 2024 10:57:14 +0100 Subject: [PATCH 52/87] rp2: Integrate RP2350. Remove references to hardware_rtc for rp2350 Reimplement time functioniality Use hardware_powman instead Define PICO_PLATFORM for RPI_PICO move PICO_TINYUSB_PATH until PICO_PLATFORM is defined PICO_TINYUSB_PATH has to be defined before calling pico_sdk_init Make sure powerman timer is using xosc ports/rp2: Updates for powman changes. Probably another change coming to remove powman and use the new aon timer that supports both rp2040 and rp2350 ports/rps: Use aon_timer module. ports/rp2: Remove rp2350 specific source. ports/rp2: Use correct CMSIS header for rp2350. ports/rp2: Add hardware_resets. ports/rp2: For rp2350 just use aon timer For rp2040 there's a workaround for RTC having only 1s resolution. This is not needed for rp2350 Stop setting PICO_RP2040_B* for rp2350 Only set PICO_RP2040_USB_DEVICE_ENUMERATION_FIX for rp2040 Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 44 ++++++++++-- ports/rp2/boards/RPI_PICO/mpconfigboard.cmake | 1 + ports/rp2/fatfs_port.c | 11 +-- ports/rp2/machine_rtc.c | 69 ++++++++----------- ports/rp2/main.c | 26 +++---- ports/rp2/mbedtls/mbedtls_port.c | 8 +-- ports/rp2/modmachine.c | 1 + ports/rp2/modtime.c | 30 ++++---- ports/rp2/mphalport.c | 24 +++++-- ports/rp2/mpnetworkport.c | 7 ++ ports/rp2/pendsv.c | 7 ++ 11 files changed, 138 insertions(+), 90 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 1dee8c599d723..480a9097448dd 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -191,19 +191,26 @@ set(PICO_SDK_COMPONENTS hardware_pll hardware_pwm hardware_regs - hardware_rtc + hardware_resets hardware_spi hardware_structs hardware_sync + hardware_sync_spin_lock hardware_timer hardware_uart hardware_watchdog hardware_xosc + pico_aon_timer pico_base_headers pico_binary_info pico_bootrom pico_multicore pico_platform + pico_platform_compiler + pico_platform_sections + pico_platform_panic + pico_runtime + pico_runtime_init pico_stdio pico_stdlib pico_sync @@ -224,14 +231,23 @@ pico_add_library(pico_float_micropython) # pico_float_micropython: add pico-sdk float and our libm source files. target_sources(pico_float_micropython INTERFACE - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi.S - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom.c - ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_v1_rom_shim.S ${MICROPY_SOURCE_LIB_LIBM} ${MICROPY_SOURCE_LIB_LIBM_SQRT_SW} ${MICROPY_PORT_DIR}/libm_extra.c ) +if(PICO_RP2040) + target_sources(pico_float_micropython INTERFACE + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_v1_rom_shim_rp2040.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_rp2040.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom_rp2040.c + ) +elseif(PICO_RP2350) + target_sources(pico_float_micropython INTERFACE + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S + ) +endif() + # pico_float_micropython: wrap low-level floating-point ops, to call the pico-sdk versions. pico_wrap_function(pico_float_micropython __aeabi_fdiv) pico_wrap_function(pico_float_micropython __aeabi_fmul) @@ -253,7 +269,9 @@ pico_wrap_function(pico_float_micropython __aeabi_ul2f) pico_wrap_function(pico_float_micropython __aeabi_f2iz) pico_wrap_function(pico_float_micropython __aeabi_f2lz) pico_wrap_function(pico_float_micropython __aeabi_f2uiz) -pico_wrap_function(pico_float_micropython __aeabi_f2ulz) +if (PICO_RP2040) + pico_wrap_function(pico_float_micropython __aeabi_f2ulz) +endif() pico_wrap_function(pico_float_micropython __aeabi_f2d) if (MICROPY_PY_LWIP) @@ -505,9 +523,14 @@ target_compile_definitions(${MICROPY_TARGET} PRIVATE PICO_NO_PROGRAM_VERSION_STRING=1 # do it ourselves in main.c MICROPY_BUILD_TYPE="${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} ${CMAKE_BUILD_TYPE}" PICO_NO_BI_STDIO_UART=1 # we call it UART REPL - PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1 ) +if (PICO_RP2040) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1 + ) +endif() + target_link_libraries(${MICROPY_TARGET} ${PICO_SDK_COMPONENTS} ) @@ -523,7 +546,11 @@ endif() # a linker script modification) until we explicitly add macro calls around the function # defs to move them into RAM. if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) - pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_${PICO_PLATFORM}.ld) + if(PICO_RP2040) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2040.ld) + elseif(PICO_RP2350) + pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2350.ld) + endif() endif() pico_add_extra_outputs(${MICROPY_TARGET}) @@ -542,6 +569,9 @@ foreach(comp ${PICO_SDK_COMPONENTS}) micropy_gather_target_properties(${comp}_headers) endforeach() +set(MICROPY_CPP_FLAGS_EXTRA ${PICO_COMMON_LANG_FLAGS}) +separate_arguments(MICROPY_CPP_FLAGS_EXTRA) + # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) diff --git a/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake index 13269e81e52e5..386bd33285890 100644 --- a/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake +++ b/ports/rp2/boards/RPI_PICO/mpconfigboard.cmake @@ -1,2 +1,3 @@ # cmake file for Raspberry Pi Pico set(PICO_BOARD "pico") +set(PICO_PLATFORM "rp2040") diff --git a/ports/rp2/fatfs_port.c b/ports/rp2/fatfs_port.c index 9706b6fe7dc70..1ce505dd69e3f 100644 --- a/ports/rp2/fatfs_port.c +++ b/ports/rp2/fatfs_port.c @@ -25,10 +25,13 @@ */ #include "lib/oofatfs/ff.h" -#include "hardware/rtc.h" +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" MP_WEAK DWORD get_fattime(void) { - datetime_t t; - rtc_get_datetime(&t); - return ((t.year - 1980) << 25) | ((t.month) << 21) | ((t.day) << 16) | ((t.hour) << 11) | ((t.min) << 5) | (t.sec / 2); + struct timespec ts; + timeutils_struct_time_t tm; + aon_timer_get_time(&ts); + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); + return ((tm.tm_year - 1980) << 25) | ((tm.tm_mon) << 21) | ((tm.tm_mday) << 16) | ((tm.tm_hour) << 11) | ((tm.tm_min) << 5) | (tm.tm_sec / 2); } diff --git a/ports/rp2/machine_rtc.c b/ports/rp2/machine_rtc.c index ce224be711b89..0d8fdef30f322 100644 --- a/ports/rp2/machine_rtc.c +++ b/ports/rp2/machine_rtc.c @@ -30,6 +30,8 @@ #include #include +#include "pico/aon_timer.h" + #include "py/nlr.h" #include "py/obj.h" #include "py/runtime.h" @@ -37,8 +39,6 @@ #include "py/mperrno.h" #include "extmod/modmachine.h" #include "shared/timeutils/timeutils.h" -#include "hardware/rtc.h" -#include "pico/util/datetime.h" typedef struct _machine_rtc_obj_t { mp_obj_base_t base; @@ -50,14 +50,12 @@ static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments mp_arg_check_num(n_args, n_kw, 0, 0, false); - bool r = rtc_running(); + bool r = aon_timer_is_running(); if (!r) { - // This shouldn't happen as rtc_init() is already called in main so - // it's here just in case - rtc_init(); - datetime_t t = { .month = 1, .day = 1 }; - rtc_set_datetime(&t); + // This shouldn't happen. it's here just in case + struct timespec ts = { 0, 0 }; + aon_timer_start(&ts); mp_hal_time_ns_set_from_rtc(); } // return constant object @@ -66,47 +64,36 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { if (n_args == 1) { - bool ret; - datetime_t t; - - ret = rtc_get_datetime(&t); - if (!ret) { - mp_raise_OSError(MP_EIO); - } - + struct timespec ts; + timeutils_struct_time_t tm; + aon_timer_get_time(&ts); + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(t.year), - mp_obj_new_int(t.month), - mp_obj_new_int(t.day), - mp_obj_new_int(t.dotw), - mp_obj_new_int(t.hour), - mp_obj_new_int(t.min), - mp_obj_new_int(t.sec), - mp_obj_new_int(0) + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(0), }; - return mp_obj_new_tuple(8, tuple); } else { mp_obj_t *items; - mp_obj_get_array_fixed_n(args[1], 8, &items); - - datetime_t t = { - .year = mp_obj_get_int(items[0]), - .month = mp_obj_get_int(items[1]), - .day = mp_obj_get_int(items[2]), - .hour = mp_obj_get_int(items[4]), - .min = mp_obj_get_int(items[5]), - .sec = mp_obj_get_int(items[6]), + timeutils_struct_time_t tm = { + .tm_year = mp_obj_get_int(items[0]), + .tm_mon = mp_obj_get_int(items[1]), + .tm_mday = mp_obj_get_int(items[2]), + .tm_hour = mp_obj_get_int(items[4]), + .tm_min = mp_obj_get_int(items[5]), + .tm_sec = mp_obj_get_int(items[6]), }; - // Deliberately ignore the weekday argument and compute the proper value - t.dotw = timeutils_calc_weekday(t.year, t.month, t.day); - - if (!rtc_set_datetime(&t)) { - mp_raise_OSError(MP_EINVAL); - } + struct timespec ts = { 0, 0 }; + ts.tv_sec = timeutils_seconds_since_epoch(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + aon_timer_set_time(&ts); mp_hal_time_ns_set_from_rtc(); - } return mp_const_none; } diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 98417e8da6b0b..2b1f97ffd8914 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -47,11 +47,9 @@ #include "genhdr/mpversion.h" #include "mp_usbd.h" -#include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk #include "pico/stdlib.h" #include "pico/binary_info.h" #include "pico/unique_id.h" -#include "hardware/rtc.h" #include "hardware/structs/rosc.h" #if MICROPY_PY_LWIP #include "lwip/init.h" @@ -60,6 +58,16 @@ #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif +#if PICO_RP2040 +#include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk +#include "hardware/rtc.h" +#elif PICO_RP2350 +#include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" +#else +#error Unknown processor +#endif extern uint8_t __StackTop, __StackBottom; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -103,17 +111,9 @@ int main(int argc, char **argv) { #endif // Start and initialise the RTC - datetime_t t = { - .year = 2021, - .month = 1, - .day = 1, - .dotw = 4, // 0 is Monday, so 4 is Friday - .hour = 0, - .min = 0, - .sec = 0, - }; - rtc_init(); - rtc_set_datetime(&t); + struct timespec ts = { 0, 0 }; + ts.tv_sec = timeutils_seconds_since_epoch(2021, 1, 1, 0, 0, 0); + aon_timer_start(&ts); mp_hal_time_ns_set_from_rtc(); // Initialise stack extents and GC heap. diff --git a/ports/rp2/mbedtls/mbedtls_port.c b/ports/rp2/mbedtls/mbedtls_port.c index 9b1e0d20e1700..521d815d2bedd 100644 --- a/ports/rp2/mbedtls/mbedtls_port.c +++ b/ports/rp2/mbedtls/mbedtls_port.c @@ -29,9 +29,9 @@ #include "mbedtls_config_port.h" -#include "hardware/rtc.h" #include "shared/timeutils/timeutils.h" #include "mbedtls/platform_time.h" +#include "pico/aon_timer.h" extern uint8_t rosc_random_u8(size_t cycles); @@ -44,9 +44,9 @@ int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t } time_t rp2_rtctime_seconds(time_t *timer) { - datetime_t t; - rtc_get_datetime(&t); - return timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec); + struct timespec ts; + aon_timer_get_time(&ts); + return ts.tv_sec; } mbedtls_ms_time_t mbedtls_ms_time(void) { diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 0acae25499996..798078faf6527 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -41,6 +41,7 @@ #include "pico/bootrom.h" #include "pico/stdlib.h" #include "pico/unique_id.h" +#include "pico/runtime_init.h" #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif diff --git a/ports/rp2/modtime.c b/ports/rp2/modtime.c index 21e5cb459b283..7d6dd8fd97112 100644 --- a/ports/rp2/modtime.c +++ b/ports/rp2/modtime.c @@ -26,28 +26,30 @@ #include "py/obj.h" #include "shared/timeutils/timeutils.h" -#include "hardware/rtc.h" +#include "pico/aon_timer.h" // Return the localtime as an 8-tuple. static mp_obj_t mp_time_localtime_get(void) { - datetime_t t; - rtc_get_datetime(&t); + struct timespec ts; + aon_timer_get_time(&ts); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(t.year), - mp_obj_new_int(t.month), - mp_obj_new_int(t.day), - mp_obj_new_int(t.hour), - mp_obj_new_int(t.min), - mp_obj_new_int(t.sec), - mp_obj_new_int(t.dotw), - mp_obj_new_int(timeutils_year_day(t.year, t.month, t.day)), + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_yday), }; return mp_obj_new_tuple(8, tuple); } // Return the number of seconds since the Epoch. static mp_obj_t mp_time_time_get(void) { - datetime_t t; - rtc_get_datetime(&t); - return mp_obj_new_int_from_ull(timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec)); + struct timespec ts; + aon_timer_get_time(&ts); + return mp_obj_new_int_from_ull(ts.tv_sec); } diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index aa5415d6c842d..3fcc011b62676 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -37,16 +37,18 @@ #include "tusb.h" #include "uart.h" #include "hardware/irq.h" -#include "hardware/rtc.h" #include "pico/unique_id.h" +#include "pico/aon_timer.h" #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #endif +#if PICO_RP2040 // This needs to be added to the result of time_us_64() to get the number of // microseconds since the Epoch. static uint64_t time_us_64_offset_from_epoch; +#endif #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC @@ -145,27 +147,35 @@ void mp_hal_delay_ms(mp_uint_t ms) { } void mp_hal_time_ns_set_from_rtc(void) { + #if PICO_RP2040 // Outstanding RTC register writes need at least two RTC clock cycles to // update. (See RP2040 datasheet section 4.8.4 "Reference clock"). mp_hal_delay_us(44); // Sample RTC and time_us_64() as close together as possible, so the offset // calculated for the latter can be as accurate as possible. - datetime_t t; - rtc_get_datetime(&t); + struct timespec ts; + aon_timer_get_time(&ts); uint64_t us = time_us_64(); - // Calculate the difference between the RTC Epoch seconds and time_us_64(). - uint64_t s = timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec); - time_us_64_offset_from_epoch = (uint64_t)s * 1000000ULL - us; + // Calculate the difference between the RTC Epoch and time_us_64(). + time_us_64_offset_from_epoch = ((uint64_t)ts.tv_sec * 1000000ULL) + ((uint64_t)ts.tv_nsec / 1000ULL) - us; + #endif } uint64_t mp_hal_time_ns(void) { - // The RTC only has seconds resolution, so instead use time_us_64() to get a more + #if PICO_RP2040 + // The RTC probably has limited resolution, so instead use time_us_64() to get a more // precise measure of Epoch time. Both these "clocks" are clocked from the same // source so they remain synchronised, and only differ by a fixed offset (calculated // in mp_hal_time_ns_set_from_rtc). return (time_us_64_offset_from_epoch + time_us_64()) * 1000ULL; + #else + // aon timer has ms resolution + struct timespec ts; + aon_timer_get_time(&ts); + return ((uint64_t)ts.tv_sec * 1000000000ULL) + (uint64_t)ts.tv_nsec; + #endif } // Generate a random locally administered MAC address (LAA) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index e58adb3ba3595..fcc60b3fe5796 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -43,7 +43,14 @@ static soft_timer_entry_t mp_network_soft_timer; #include "lib/cyw43-driver/src/cyw43.h" #include "lib/cyw43-driver/src/cyw43_stats.h" #include "hardware/irq.h" + +#if PICO_RP2040 #include "RP2040.h" // cmsis, for NVIC_SetPriority and PendSV_IRQn +#elif PICO_RP2350 +#include "RP2350.h" // cmsis, for NVIC_SetPriority and PendSV_IRQn +#else +#error Unknown processor +#endif #define CYW43_IRQ_LEVEL GPIO_IRQ_LEVEL_HIGH #define CYW43_SHARED_IRQ_HANDLER_PRIORITY PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 6cfe624c3037f..2f06871b3ac5b 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -28,7 +28,14 @@ #include "py/mpconfig.h" #include "mutex_extra.h" #include "pendsv.h" + +#if PICO_RP2040 #include "RP2040.h" +#elif PICO_RP2350 +#include "RP2350.h" +#else +#error Unknown chip +#endif #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43_stats.h" From e14d9ca5b9409a6e9a5704be91f8986f5e9bbbb3 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Jun 2024 16:00:30 +0100 Subject: [PATCH 53/87] rp2/CMakeLists.txt: Add float_conv_m33 for __aeabi_ul2f. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 480a9097448dd..842bd7ac1f5ce 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -245,6 +245,7 @@ if(PICO_RP2040) elseif(PICO_RP2350) target_sources(pico_float_micropython INTERFACE ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_conv_m33.S ) endif() From cef36918f00db96450be8e64d13b32d2b00ae7c2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Jun 2024 16:01:12 +0100 Subject: [PATCH 54/87] py/usermod.cmake: Check target exists. Check a target exists before accessing properties. Usermod_gather_sources would recurse into garbage property names and break. Signed-off-by: Phil Howard --- py/usermod.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index 853276283746d..c814369a4865e 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -5,6 +5,10 @@ function(usermod_gather_sources SOURCES_VARNAME INCLUDE_DIRECTORIES_VARNAME INCL if (NOT ${LIB} IN_LIST ${INCLUDED_VARNAME}) list(APPEND ${INCLUDED_VARNAME} ${LIB}) + if (NOT TARGET ${LIB}) + return() + endif() + # Gather library sources get_target_property(lib_sources ${LIB} INTERFACE_SOURCES) if (lib_sources) From 5f99e4c7c5967e007dc5c4856bc7f68b74a9a20a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 1 Jul 2024 15:29:57 +0100 Subject: [PATCH 55/87] rp2: Add support for 48-pin RP2350 variant. Changes: - NUM_BANK0_GPIOS > pins defined in pins.csv = hardfault - make-pins.py: NUM_GPIOS is hard-coded in make-pins.py, make it bigger - machine_pin.h: Pin "ID" must be six bits, not five (breaks bit packing?) - mphalport.h/machine_pin.c: open drain mask must be 64bits. Signed-off-by: Phil Howard --- ports/rp2/boards/make-pins.py | 2 +- ports/rp2/machine_pin.c | 2 +- ports/rp2/machine_pin.h | 2 +- ports/rp2/mphalport.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index cbc3424705def..aa754093aa5b1 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -9,7 +9,7 @@ import boardgen # This is NUM_BANK0_GPIOS. Pin indices are 0 to 29 (inclusive). -NUM_GPIOS = 30 +NUM_GPIOS = 48 # Up to 10 additional extended pins (e.g. via the wifi chip). NUM_EXT_GPIOS = 10 diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index de217785690c2..e1a545a09234c 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -89,7 +89,7 @@ static const mp_irq_methods_t machine_pin_irq_methods; extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. -uint32_t machine_pin_open_drain_mask; +uint64_t machine_pin_open_drain_mask; #if MICROPY_HW_PIN_EXT_COUNT static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { diff --git a/ports/rp2/machine_pin.h b/ports/rp2/machine_pin.h index b3349188e8ba1..196132019e609 100644 --- a/ports/rp2/machine_pin.h +++ b/ports/rp2/machine_pin.h @@ -49,7 +49,7 @@ typedef struct _machine_pin_af_obj_t { typedef struct _machine_pin_obj_t { mp_obj_base_t base; qstr name; - uint8_t id : 5; + uint8_t id : 6; #if MICROPY_HW_PIN_EXT_COUNT uint8_t is_ext : 1; uint8_t is_output : 1; diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 16ac4259a6feb..ac1c99264b295 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -115,7 +115,7 @@ static inline mp_uint_t mp_hal_get_cpu_freq(void) { #define MP_HAL_PIN_PULL_UP (1) #define MP_HAL_PIN_PULL_DOWN (2) -extern uint32_t machine_pin_open_drain_mask; +extern uint64_t machine_pin_open_drain_mask; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); From 0486a4cf3a71317fb60388e80d7504962068db50 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 2 Jul 2024 17:39:02 +0100 Subject: [PATCH 56/87] rp2/rp2_pio: Add support for RP2350A/B in rp2_pio. Add support for 32 and 48 pin variants of RP2350. Add new gpio_base, mirroring the Pico SDK. Signed-off-by: Phil Howard Signed-off-by: Damien George --- docs/library/rp2.PIO.rst | 11 +++++ ports/rp2/rp2_pio.c | 91 ++++++++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/docs/library/rp2.PIO.rst b/docs/library/rp2.PIO.rst index e0675af1e9a2c..f922456c8c483 100644 --- a/docs/library/rp2.PIO.rst +++ b/docs/library/rp2.PIO.rst @@ -27,6 +27,17 @@ Constructors Methods ------- +.. method:: PIO.gpio_base([base]) + + Query and optionally set the current GPIO base for this PIO instance. + + If an argument is given then it must be a pin (or integer corresponding to a pin + number), restricted to either GPIO0 or GPIO16. The GPIO base will then be set to + that pin. Setting the GPIO base must be done before any programs are added or state + machines created. + + Returns the current GPIO base pin. + .. method:: PIO.add_program(program) Add the *program* to the instruction memory of this PIO instance. diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 0ca91656f84ef..8e91c1d8fb328 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -54,7 +54,7 @@ typedef struct _rp2_state_machine_obj_t { PIO pio; uint8_t irq; uint8_t sm; // 0-3 - uint8_t id; // 0-7 + uint8_t id; // 0-7 on RP2040, or 0-11 on RP2350 } rp2_state_machine_obj_t; typedef struct _rp2_state_machine_irq_obj_t { @@ -63,11 +63,11 @@ typedef struct _rp2_state_machine_irq_obj_t { uint8_t trigger; } rp2_state_machine_irq_obj_t; -static const rp2_state_machine_obj_t rp2_state_machine_obj[8]; -static uint8_t rp2_state_machine_initial_pc[8]; +static const rp2_state_machine_obj_t rp2_state_machine_obj[NUM_PIOS * 4]; +static uint8_t rp2_state_machine_initial_pc[NUM_PIOS * 4]; // These masks keep track of PIO instruction memory used by this module. -static uint32_t rp2_pio_instruction_memory_usage_mask[2]; +static uint32_t rp2_pio_instruction_memory_usage_mask[NUM_PIOS]; static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id); static void rp2_state_machine_reset_all(void); @@ -104,8 +104,19 @@ static void pio1_irq0(void) { pio_irq0(pio1); } +#if NUM_PIOS >= 3 +static void pio2_irq0(void) { + pio_irq0(pio2); +} +#endif + // Returns the correct irq0 handler wrapper for a given pio static inline irq_handler_t rp2_pio_get_irq_handler(PIO pio) { + #if NUM_PIOS >= 3 + if (pio == pio2) { + return pio2_irq0; + } + #endif return pio == pio0 ? pio0_irq0 : pio1_irq0; } @@ -172,6 +183,12 @@ void rp2_pio_deinit(void) { irq_set_enabled(PIO1_IRQ_0, false); irq_remove_handler(PIO1_IRQ_0, pio1_irq0); } + #if NUM_PIOS >= 3 + if (irq_get_exclusive_handler(PIO2_IRQ_0) == pio2_irq0) { + irq_set_enabled(PIO2_IRQ_0, false); + irq_remove_handler(PIO2_IRQ_0, pio2_irq0); + } + #endif rp2_state_machine_reset_all(); @@ -180,6 +197,9 @@ void rp2_pio_deinit(void) { // and their PIO programs should remain intact. rp2_pio_remove_all_managed_programs(pio0); rp2_pio_remove_all_managed_programs(pio1); + #if NUM_PIOS >= 3 + rp2_pio_remove_all_managed_programs(pio2); + #endif } /******************************************************************************/ @@ -212,7 +232,7 @@ static void asm_pio_override_shiftctrl(mp_obj_t arg, uint32_t bits, uint32_t lsb } } -static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_base, asm_pio_config_t *config) { +static void asm_pio_get_pins(PIO pio, const char *type, mp_obj_t prog_pins, mp_obj_t arg_base, asm_pio_config_t *config) { if (prog_pins != mp_const_none) { // The PIO program specified pins for initialisation on out/set/sideset. if (mp_obj_is_integer(prog_pins)) { @@ -238,13 +258,21 @@ static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_ if (arg_base != mp_const_none) { // The instantiation of the PIO program specified a base pin. config->base = mp_hal_get_pin_obj(arg_base); + + #if PICO_PIO_USE_GPIO_BASE + // Check the base is within range of the configured gpio_base. + uint gpio_base = pio_get_gpio_base(pio); + if ((gpio_base == 0 && config->base >= 32) || (gpio_base == 16 && config->base < 16)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%s_base not within gpio_base range"), type); + } + #endif } } static void asm_pio_init_gpio(PIO pio, uint32_t sm, asm_pio_config_t *config) { - uint32_t pinmask = ((1 << config->count) - 1) << config->base; - pio_sm_set_pins_with_mask(pio, sm, config->pinvals << config->base, pinmask); - pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << config->base, pinmask); + uint32_t pinmask = ((1 << config->count) - 1) << (config->base - pio_get_gpio_base(pio)); + pio_sm_set_pins_with_mask(pio, sm, config->pinvals << (config->base - pio_get_gpio_base(pio)), pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << (config->base - pio_get_gpio_base(pio)), pinmask); for (size_t i = 0; i < config->count; ++i) { gpio_set_function(config->base + i, GPIO_FUNC_PIO0 + pio_get_index(pio)); } @@ -258,6 +286,9 @@ static const mp_irq_methods_t rp2_pio_irq_methods; static rp2_pio_obj_t rp2_pio_obj[] = { { { &rp2_pio_type }, pio0, PIO0_IRQ_0 }, { { &rp2_pio_type }, pio1, PIO1_IRQ_0 }, + #if NUM_PIOS >= 3 + { { &rp2_pio_type }, pio2, PIO2_IRQ_0 }, + #endif }; static void rp2_pio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -357,6 +388,31 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m } MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_state_machine_obj, 2, rp2_pio_state_machine); +#if PICO_PIO_USE_GPIO_BASE +// PIO.gpio_base([base]) +static mp_obj_t rp2_pio_gpio_base(size_t n_args, const mp_obj_t *args) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args > 1) { + // Set gpio_base value. + uint gpio_base = mp_hal_get_pin_obj(args[1]); + + // Must be 0 for GPIOs 0 to 31 inclusive, or 16 for GPIOs 16 to 48 inclusive. + if (!(gpio_base == 0 || gpio_base == 16)) { + mp_raise_ValueError("invalid GPIO base"); + } + + if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { + mp_raise_OSError(MP_EINVAL); + } + } + + // Return current gpio_base value. + return pio_get_gpio_base(self->pio) == 0 ? pin_GPIO0 : pin_GPIO16; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_pio_gpio_base_obj, 1, 2, rp2_pio_gpio_base); +#endif + // PIO.irq(handler=None, trigger=IRQ_SM0|IRQ_SM1|IRQ_SM2|IRQ_SM3, hard=False) static mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_handler, ARG_trigger, ARG_hard }; @@ -410,6 +466,9 @@ static mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k static MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_irq_obj, 1, rp2_pio_irq); static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { + #if PICO_PIO_USE_GPIO_BASE + { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_add_program), MP_ROM_PTR(&rp2_pio_add_program_obj) }, { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, @@ -486,6 +545,12 @@ static const rp2_state_machine_obj_t rp2_state_machine_obj[] = { { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 1, 5 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 2, 6 }, { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 3, 7 }, + #if NUM_PIOS >= 3 + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 0, 8 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 1, 9 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 2, 10 }, + { { &rp2_state_machine_type }, pio2, PIO2_IRQ_0, 3, 11 }, + #endif }; static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id) { @@ -604,14 +669,14 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure out pins, if needed. asm_pio_config_t out_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); + asm_pio_get_pins(self->pio, "out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); if (out_config.base >= 0) { sm_config_set_out_pins(&config, out_config.base, out_config.count); } // Configure set pin, if needed. asm_pio_config_t set_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); + asm_pio_get_pins(self->pio, "set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); if (set_config.base >= 0) { sm_config_set_set_pins(&config, set_config.base, set_config.count); } @@ -623,7 +688,7 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure sideset pin, if needed. asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); + asm_pio_get_pins(self->pio, "sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); if (sideset_config.base >= 0) { uint32_t count = sideset_config.count; if (config.execctrl & (1 << PIO_SM0_EXECCTRL_SIDE_EN_LSB)) { @@ -951,5 +1016,5 @@ static const mp_irq_methods_t rp2_state_machine_irq_methods = { .info = rp2_state_machine_irq_info, }; -MP_REGISTER_ROOT_POINTER(void *rp2_pio_irq_obj[2]); -MP_REGISTER_ROOT_POINTER(void *rp2_state_machine_irq_obj[8]); +MP_REGISTER_ROOT_POINTER(void *rp2_pio_irq_obj[NUM_PIOS]); +MP_REGISTER_ROOT_POINTER(void *rp2_state_machine_irq_obj[NUM_PIOS * 4]); From f168c09f53f4655056e61fb6ca3bcc502b2026c3 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 8 Jul 2024 15:57:13 +0100 Subject: [PATCH 57/87] rp2: Pass gpio-num into make-pins. NUM_GPIOS amd NUM_EXT_GPIOS is currently hardcoded in this python script. Pass the count in via the new parameters num-gpios and num-ext-gpios. These default to the current values supported by Pico 30/10. This can be changed with PICO_NUM_GPIOS and PICO_NUM_EXT_GPIOS in mpconfigboard.cmake. Without this you will get a build error because NUM_BANK0_GPIOS can be too small. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 10 +++++- .../rp2/boards/RPI_PICO2/mpconfigboard.cmake | 3 ++ ports/rp2/boards/make-pins.py | 35 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 842bd7ac1f5ce..0cbed34f632f8 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -582,6 +582,14 @@ set(GEN_PINS_MKPINS "${MICROPY_PORT_DIR}/boards/make-pins.py") set(GEN_PINS_SRC "${CMAKE_BINARY_DIR}/pins_${MICROPY_BOARD}.c") set(GEN_PINS_HDR "${MICROPY_GENHDR_DIR}/pins.h") +if(NOT PICO_NUM_GPIOS) + set(PICO_NUM_GPIOS 30) +endif() + +if(NOT PICO_NUM_EXT_GPIOS) + set(PICO_NUM_EXT_GPIOS 10) +endif() + if(EXISTS "${MICROPY_BOARD_DIR}/pins.csv") set(GEN_PINS_BOARD_CSV "${MICROPY_BOARD_DIR}/pins.csv") set(GEN_PINS_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}") @@ -594,7 +602,7 @@ target_sources(${MICROPY_TARGET} PRIVATE # Generate pins add_custom_command( OUTPUT ${GEN_PINS_HDR} ${GEN_PINS_SRC} - COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_CSV_ARG} --af-csv ${GEN_PINS_AF_CSV} --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --output-header ${GEN_PINS_HDR} + COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_CSV_ARG} --af-csv ${GEN_PINS_AF_CSV} --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --num-gpios ${PICO_NUM_GPIOS} --num-ext-gpios ${PICO_NUM_EXT_GPIOS} --output-header ${GEN_PINS_HDR} DEPENDS ${GEN_PINS_AF_CSV} ${GEN_PINS_BOARD_CSV} diff --git a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake index 80e84eef184b5..48b6545aa3428 100644 --- a/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake +++ b/ports/rp2/boards/RPI_PICO2/mpconfigboard.cmake @@ -1,2 +1,5 @@ # cmake file for Raspberry Pi Pico2 set(PICO_BOARD "pico2") + +# To change the gpio count for QFN-80 +# set(PICO_NUM_GPIOS 48) diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index aa754093aa5b1..06d6847540e0a 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -9,9 +9,9 @@ import boardgen # This is NUM_BANK0_GPIOS. Pin indices are 0 to 29 (inclusive). -NUM_GPIOS = 48 +NUM_GPIOS = None # Up to 10 additional extended pins (e.g. via the wifi chip). -NUM_EXT_GPIOS = 10 +NUM_EXT_GPIOS = None class Rp2Pin(boardgen.Pin): @@ -108,12 +108,6 @@ def __init__(self): enable_af=True, ) - # Pre-define the pins (i.e. don't require them to be listed in pins.csv). - for i in range(NUM_GPIOS): - self.add_cpu_pin("GPIO{}".format(i)) - for i in range(NUM_EXT_GPIOS): - self.add_cpu_pin("EXT_GPIO{}".format(i)) - # Provided by pico-sdk. def cpu_table_size(self): return "NUM_BANK0_GPIOS" @@ -128,6 +122,31 @@ def print_source(self, out_source): super().print_source(out_source) self.print_cpu_locals_dict(out_source) + def extra_args(self, parser): + parser.add_argument("--num-gpios", type=int) + parser.add_argument("--num-ext-gpios", type=int) + + def load_inputs(self, out_source): + global NUM_GPIOS + global NUM_EXT_GPIOS + + # Needed by validate_cpu_pin_name + NUM_GPIOS = self.args.num_gpios + NUM_EXT_GPIOS = self.args.num_ext_gpios + + if NUM_GPIOS is None: + raise boardgen.PinGeneratorError("Please pass num-gpios") + + if NUM_EXT_GPIOS is None: + NUM_EXT_GPIOS = 0 + # Pre-define the pins (i.e. don't require them to be listed in pins.csv). + for i in range(NUM_GPIOS): + self.add_cpu_pin("GPIO{}".format(i)) + for i in range(NUM_EXT_GPIOS): + self.add_cpu_pin("EXT_GPIO{}".format(i)) + + super().load_inputs(out_source) + if __name__ == "__main__": Rp2PinGenerator().main() From 805af3063d5d730b2ad2fc0af580c7ffead3a37d Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 9 Jul 2024 19:01:12 +0100 Subject: [PATCH 58/87] rp2: Fix RP2040 build issues. Signed-off-by: Damien George --- ports/rp2/main.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 2b1f97ffd8914..61c366c7f999a 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -60,14 +60,13 @@ #endif #if PICO_RP2040 #include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#include "hardware/rtc.h" #elif PICO_RP2350 #include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#include "pico/aon_timer.h" -#include "shared/timeutils/timeutils.h" #else #error Unknown processor #endif +#include "pico/aon_timer.h" +#include "shared/timeutils/timeutils.h" extern uint8_t __StackTop, __StackBottom; extern uint8_t __GcHeapStart, __GcHeapEnd; From e856e80295c50f31b032c009ddb0bd64611089a1 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 10 Jul 2024 12:44:59 +0100 Subject: [PATCH 59/87] rp2/machine_pin: Allow gpios >=32 to work. Signed-off-by: Damien George --- ports/rp2/machine_pin.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index e1a545a09234c..1569b2696c6f8 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -298,7 +298,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin af: %d"), af); } gpio_set_function(self->id, af); - machine_pin_open_drain_mask &= ~(1 << self->id); + machine_pin_open_drain_mask &= ~(1ULL << self->id); } } @@ -380,7 +380,7 @@ static mp_obj_t machine_pin_low(mp_obj_t self_in) { } else if (GPIO_IS_OPEN_DRAIN(self->id)) { gpio_set_dir(self->id, GPIO_OUT); } else { - gpio_clr_mask(1u << self->id); + gpio_clr_mask64(1ULL << self->id); } return mp_const_none; } @@ -396,7 +396,7 @@ static mp_obj_t machine_pin_high(mp_obj_t self_in) { } else if (GPIO_IS_OPEN_DRAIN(self->id)) { gpio_set_dir(self->id, GPIO_IN); } else { - gpio_set_mask(1u << self->id); + gpio_set_mask64(1ULL << self->id); } return mp_const_none; } @@ -417,7 +417,7 @@ static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { gpio_set_dir(self->id, GPIO_OUT); } } else { - gpio_xor_mask(1u << self->id); + gpio_xor_mask64(1ULL << self->id); } return mp_const_none; } From a7f92d42e514b6647f4827cfa8c44b51a8b4e74f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 8 Jul 2024 16:22:56 +0100 Subject: [PATCH 60/87] rp2/machine_pin: Fix pin irq for pins > 32. Fix the gpio_irq function so that it looks at all six iobank0_hw->intr[n] registers, for up to 48 IOs. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 1569b2696c6f8..f11f4190f48c6 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -87,6 +87,7 @@ typedef struct _machine_pin_irq_obj_t { static const mp_irq_methods_t machine_pin_irq_methods; extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; +static const int num_intr_regs = sizeof(iobank0_hw->intr) / sizeof(iobank0_hw->intr[0]); // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. uint64_t machine_pin_open_drain_mask; @@ -100,7 +101,7 @@ static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { #endif static void gpio_irq(void) { - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < num_intr_regs; ++i) { uint32_t intr = iobank0_hw->intr[i]; if (intr) { for (int j = 0; j < 8; ++j) { From 606e8c24d0a2a50f9ec4da108708cc81da93a58f Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 10 Jul 2024 16:01:16 +0100 Subject: [PATCH 61/87] rp2/clocks_extra: Update runtime_clocks_init. Note: This might be a tempoarary measure until we might fix this properly to avoid copying code. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 3 +- ports/rp2/clocks_extra.c | 68 +++++++++++++++++++++++++++++++--------- ports/rp2/clocks_extra.h | 2 +- ports/rp2/modmachine.c | 2 +- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 0cbed34f632f8..18244c63c78c4 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -181,6 +181,7 @@ set(PICO_SDK_COMPONENTS cmsis_core hardware_adc hardware_base + hardware_boot_lock hardware_clocks hardware_dma hardware_flash @@ -485,7 +486,7 @@ target_compile_options(${MICROPY_TARGET} PRIVATE target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--defsym=__micropy_c_heap_size__=${MICROPY_C_HEAP_SIZE} -Wl,--wrap=dcd_event_handler - -Wl,--wrap=clocks_init + -Wl,--wrap=runtime_init_clocks ) # Apply optimisations to performance-critical source code. diff --git a/ports/rp2/clocks_extra.c b/ports/rp2/clocks_extra.c index e2c97962cb43d..73def24b80455 100644 --- a/ports/rp2/clocks_extra.c +++ b/ports/rp2/clocks_extra.c @@ -13,22 +13,36 @@ #include "hardware/xosc.h" #include "hardware/irq.h" #include "hardware/gpio.h" +#include "hardware/ticks.h" +#if PICO_RP2040 +// The RTC clock frequency is 48MHz divided by power of 2 (to ensure an integer +// division ratio will be used in the clocks block). A divisor of 1024 generates +// an RTC clock tick of 46875Hz. This frequency is relatively close to the +// customary 32 or 32.768kHz 'slow clock' crystals and provides good timing resolution. #define RTC_CLOCK_FREQ_HZ (USB_CLK_KHZ * KHZ / 1024) +#endif + +static void start_all_ticks(void) { + uint32_t cycles = clock_get_hz(clk_ref) / MHZ; + // Note RP2040 has a single tick generator in the watchdog which serves + // watchdog, system timer and M0+ SysTick; The tick generator is clocked from clk_ref + // but is now adapted by the hardware_ticks library for compatibility with RP2350 + // npte: hardware_ticks library now provides an adapter for RP2040 + + for (int i = 0; i < (int)TICK_COUNT; ++i) { + tick_start((tick_gen_num_t)i, cycles); + } +} // Wrap the SDK's clocks_init() function to save code size -void __wrap_clocks_init(void) { - clocks_init_optional_usb(true); +void __wrap_runtime_init_clocks(void) { + runtime_init_clocks_optional_usb(true); } -// Copy of clocks_init() from pico-sdk, with USB +// Copy of runtime_init_clocks() from pico-sdk, with USB // PLL and clock init made optional (for light sleep wakeup). -void clocks_init_optional_usb(bool init_usb) { - // Start tick in watchdog, the argument is in 'cycles per microsecond' i.e. MHz - watchdog_start_tick(XOSC_KHZ / KHZ); - - // Modification: removed FPGA check here - +void runtime_init_clocks_optional_usb(bool init_usb) { // Disable resus that may be enabled from previous software clocks_hw->resus.ctrl = 0; @@ -46,14 +60,26 @@ void clocks_init_optional_usb(bool init_usb) { } /// \tag::pll_init[] - pll_init(pll_sys, PLL_COMMON_REFDIV, PLL_SYS_VCO_FREQ_KHZ * KHZ, PLL_SYS_POSTDIV1, PLL_SYS_POSTDIV2); + pll_init(pll_sys, PLL_COMMON_REFDIV, PLL_SYS_VCO_FREQ_HZ, PLL_SYS_POSTDIV1, PLL_SYS_POSTDIV2); if (init_usb) { - pll_init(pll_usb, PLL_COMMON_REFDIV, PLL_USB_VCO_FREQ_KHZ * KHZ, PLL_USB_POSTDIV1, PLL_USB_POSTDIV2); + pll_init(pll_usb, PLL_COMMON_REFDIV, PLL_USB_VCO_FREQ_HZ, PLL_USB_POSTDIV1, PLL_USB_POSTDIV2); } /// \end::pll_init[] // Configure clocks - // CLK_REF = XOSC (usually) 12MHz / 1 = 12MHz + + // todo amy, what is this N1,2,4 meant to mean? + // RP2040 CLK_REF = XOSC (usually) 12MHz / 1 = 12MHz + // RP2350 CLK_REF = XOSC (XOSC_MHZ) / N (1,2,4) = 12MHz + + // clk_ref aux select is 0 because: + // + // - RP2040: no aux mux on clk_ref, so this field is don't-care. + // + // - RP2350: there is an aux mux, but we are selecting one of the + // non-aux inputs to the glitchless mux, so the aux select doesn't + // matter. The value of 0 here happens to be the sys PLL. + clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, // No aux mux @@ -85,18 +111,32 @@ void clocks_init_optional_usb(bool init_usb) { USB_CLK_KHZ * KHZ, USB_CLK_KHZ * KHZ); + #if HAS_RP2040_RTC // CLK RTC = PLL USB 48MHz / 1024 = 46875Hz clock_configure(clk_rtc, 0, // No GLMUX CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, USB_CLK_KHZ * KHZ, RTC_CLOCK_FREQ_HZ); + #endif - // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable - // Normally choose clk_sys or clk_usb + // CLK PERI = clk_sys. Used as reference clock for UART and SPI serial. clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, SYS_CLK_KHZ * KHZ, SYS_CLK_KHZ * KHZ); + + #if PICO_RP2350 + // CLK_HSTX = clk_sys. Transmit bit clock for the HSTX peripheral. + clock_configure(clk_hstx, + 0, + CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLK_SYS, + SYS_CLK_KHZ * KHZ, + SYS_CLK_KHZ * KHZ); + #endif + + // Finally, all clocks are configured so start the ticks + // The ticks use clk_ref so now that is configured we can start them + start_all_ticks(); } diff --git a/ports/rp2/clocks_extra.h b/ports/rp2/clocks_extra.h index 40f77f53bd850..7d630e5088f5c 100644 --- a/ports/rp2/clocks_extra.h +++ b/ports/rp2/clocks_extra.h @@ -28,6 +28,6 @@ #include "hardware/clocks.h" -void clocks_init_optional_usb(bool init_usb); +void runtime_init_clocks_optional_usb(bool init_usb); #endif // MICROPY_INCLUDED_RP2_CLOCKS_EXTRA_H diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 798078faf6527..2faf0bc6f8713 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -259,7 +259,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; // Bring back all clocks. - clocks_init_optional_usb(disable_usb); + runtime_init_clocks_optional_usb(disable_usb); MICROPY_END_ATOMIC_SECTION(my_interrupts); } From 141a7c1de4b47d6a1c4cd3a6532f07e9a0c3aa1f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 5 Aug 2024 22:02:13 +0100 Subject: [PATCH 62/87] rp2/machine_adc: Add ADC support for RP2350B. Signed-off-by: Damien George --- ports/rp2/machine_adc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ports/rp2/machine_adc.c b/ports/rp2/machine_adc.c index 3594f4b124022..40c216d59bc8d 100644 --- a/ports/rp2/machine_adc.c +++ b/ports/rp2/machine_adc.c @@ -31,9 +31,8 @@ #include "hardware/adc.h" #include "machine_pin.h" -#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= 26 && (gpio) <= 29) -#define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - 26) -#define ADC_CHANNEL_TEMPSENSOR (4) +#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= ADC_BASE_PIN && (gpio) < (ADC_BASE_PIN + NUM_ADC_CHANNELS)) +#define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - ADC_BASE_PIN) static uint16_t adc_config_and_read_u16(uint32_t channel) { adc_select_input(channel); @@ -47,7 +46,7 @@ static uint16_t adc_config_and_read_u16(uint32_t channel) { // MicroPython bindings for machine.ADC #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS \ - { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_CHANNEL_TEMPSENSOR) }, \ + { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_TEMPERATURE_CHANNEL_NUM) }, \ typedef struct _machine_adc_obj_t { mp_obj_base_t base; @@ -75,7 +74,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args if (mp_obj_is_int(source)) { channel = mp_obj_get_int(source); - if (!(channel >= 0 && channel <= ADC_CHANNEL_TEMPSENSOR)) { + if (!(channel >= 0 && channel < NUM_ADC_CHANNELS)) { // Not a valid ADC channel, fallback to searching for a pin. channel = -1; } @@ -116,7 +115,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args adc_gpio_init(pin->id); channel = ADC_CHANNEL_FROM_GPIO(pin->id); } - } else if (channel == ADC_CHANNEL_TEMPSENSOR) { + } else if (channel == ADC_TEMPERATURE_CHANNEL_NUM) { // Enable temperature sensor. adc_set_temp_sensor_enabled(1); } From cc214b1e39f105170d216a20c5394e354635d302 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:34:22 +1000 Subject: [PATCH 63/87] rp2: Update to support RP2350-RISCV. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 41 ++++++++++++++++++++++++++++++++++++---- ports/rp2/main.c | 6 +++--- ports/rp2/pendsv.c | 14 +++++++++++--- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 18244c63c78c4..36b9b284cf7f1 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -93,6 +93,14 @@ include(${MICROPY_DIR}/py/usermod.cmake) add_executable(${MICROPY_TARGET}) +# Provide a C-level definitions of PICO_ARM. +# (The pico-sdk already defines PICO_RISCV when it's enabled.) +if(PICO_ARM) + target_compile_definitions(pico_platform_headers INTERFACE + PICO_ARM=1 + ) +endif() + set(MICROPY_QSTRDEFS_PORT ${MICROPY_PORT_DIR}/qstrdefsport.h ) @@ -108,7 +116,6 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/netutils/trace.c ${MICROPY_DIR}/shared/readline/readline.c - ${MICROPY_DIR}/shared/runtime/gchelper_thumb1.s ${MICROPY_DIR}/shared/runtime/gchelper_native.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c ${MICROPY_DIR}/shared/runtime/mpirq.c @@ -123,6 +130,16 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) +if(PICO_ARM) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_thumb1.s + ) +elseif(PICO_RISCV) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s + ) +endif() + set(MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/bus/softspi.c ${MICROPY_DIR}/drivers/dht/dht.c @@ -178,7 +195,6 @@ set(MICROPY_SOURCE_QSTR ) set(PICO_SDK_COMPONENTS - cmsis_core hardware_adc hardware_base hardware_boot_lock @@ -222,6 +238,17 @@ set(PICO_SDK_COMPONENTS tinyusb_device ) +if(PICO_ARM) + list(APPEND PICO_SDK_COMPONENTS + cmsis_core + ) +elseif(PICO_RISCV) + list(APPEND PICO_SDK_COMPONENTS + hardware_hazard3 + hardware_riscv + ) +endif() + # Use our custom pico_float_micropython float implementation. This is needed for two reasons: # - to fix inf handling in pico-sdk's __wrap___aeabi_fadd(); # - so we can use our own libm functions, to fix inaccuracies in the pico-sdk versions. @@ -243,7 +270,7 @@ if(PICO_RP2040) ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_rp2040.S ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_init_rom_rp2040.c ) -elseif(PICO_RP2350) +elseif(PICO_RP2350 AND PICO_ARM) target_sources(pico_float_micropython INTERFACE ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_aeabi_dcp.S ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_conv_m33.S @@ -489,6 +516,12 @@ target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--wrap=runtime_init_clocks ) +if(PICO_RP2350) + target_link_options(${MICROPY_TARGET} PRIVATE + -Wl,--defsym=__micropy_extra_stack__=4096 + ) +endif() + # Apply optimisations to performance-critical source code. set_source_files_properties( ${MICROPY_PY_DIR}/map.c @@ -557,7 +590,7 @@ endif() pico_add_extra_outputs(${MICROPY_TARGET}) -pico_find_compiler(PICO_COMPILER_SIZE ${PICO_GCC_TRIPLE}-size) +pico_find_compiler_with_triples(PICO_COMPILER_SIZE "${PICO_GCC_TRIPLE}" size) add_custom_command(TARGET ${MICROPY_TARGET} POST_BUILD diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 61c366c7f999a..0aef74be246f2 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -60,10 +60,8 @@ #endif #if PICO_RP2040 #include "RP2040.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#elif PICO_RP2350 +#elif PICO_RP2350 && PICO_ARM #include "RP2350.h" // cmsis, for PendSV_IRQn and SCB/SCB_SCR_SEVONPEND_Msk -#else -#error Unknown processor #endif #include "pico/aon_timer.h" #include "shared/timeutils/timeutils.h" @@ -82,7 +80,9 @@ bi_decl(bi_program_feature_group_with_flags(BINARY_INFO_TAG_MICROPYTHON, int main(int argc, char **argv) { // This is a tickless port, interrupts should always trigger SEV. + #if PICO_ARM SCB->SCR |= SCB_SCR_SEVONPEND_Msk; + #endif pendsv_init(); soft_timer_init(); diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 2f06871b3ac5b..905a5aa162ec5 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -31,10 +31,10 @@ #if PICO_RP2040 #include "RP2040.h" -#elif PICO_RP2350 +#elif PICO_RP2350 && PICO_ARM #include "RP2350.h" -#else -#error Unknown chip +#elif PICO_RISCV +#include "pico/aon_timer.h" #endif #if MICROPY_PY_NETWORK_CYW43 @@ -43,6 +43,8 @@ static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; +void PendSV_Handler(void); + // Using the nowait variant here as softtimer updates PendSV from the loop of mp_wfe_or_timeout(), // where we don't want the CPU event bit to be set. static recursive_mutex_nowait_t pendsv_mutex; @@ -75,10 +77,16 @@ void pendsv_resume(void) { void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { pendsv_dispatch_table[slot] = f; if (pendsv_mutex.mutex.enter_count == 0) { + #if PICO_ARM // There is a race here where other core calls pendsv_suspend() before // ISR can execute, but dispatch will happen later when other core // calls pendsv_resume(). SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + #elif PICO_RISCV + struct timespec ts; + aon_timer_get_time(&ts); + aon_timer_enable_alarm(&ts, PendSV_Handler, false); + #endif } else { #if MICROPY_PY_NETWORK_CYW43 CYW43_STAT_INC(PENDSV_DISABLED_COUNT); From c056e803c68b2d7591c943d6063fd3b2042f9102 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:33:47 +1000 Subject: [PATCH 64/87] rp2/mpconfigport: Enable RV32I native emitter on RISCV variants. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 297455a9f9abb..3f3e71cceb1c2 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -78,10 +78,16 @@ // MicroPython emitters #define MICROPY_PERSISTENT_CODE_LOAD (1) +#if PICO_ARM #define MICROPY_EMIT_THUMB (1) -#define MICROPY_EMIT_THUMB_ARMV7M (0) #define MICROPY_EMIT_INLINE_THUMB (1) +#if PICO_RP2040 +#define MICROPY_EMIT_THUMB_ARMV7M (0) #define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#endif +#elif PICO_RISCV +#define MICROPY_EMIT_RV32 (1) +#endif // Optimisations #define MICROPY_OPT_COMPUTED_GOTO (1) From 12758123385dd9103f62ef61db27bd9e4dfdbdae Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 14:35:23 +1000 Subject: [PATCH 65/87] rp2/Makefile: Allow CMAKE_ARGS to be set by user. Signed-off-by: Damien George --- ports/rp2/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index b6e8852169217..f950bc7b90898 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -30,7 +30,7 @@ endif $(VERBOSE)MAKESILENT = -s -CMAKE_ARGS = -DMICROPY_BOARD=$(BOARD) -DMICROPY_BOARD_DIR="$(abspath $(BOARD_DIR))" +CMAKE_ARGS += -DMICROPY_BOARD=$(BOARD) -DMICROPY_BOARD_DIR="$(abspath $(BOARD_DIR))" ifdef USER_C_MODULES CMAKE_ARGS += -DUSER_C_MODULES=${USER_C_MODULES} From 9322e276fe555fb858bcb0d6056d7fbc30439647 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 5 Aug 2024 13:47:25 -0600 Subject: [PATCH 66/87] rp2/machine_bitstream: Set SysTick reset value. Signed-off-by: Damien George --- ports/rp2/machine_bitstream.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/rp2/machine_bitstream.c b/ports/rp2/machine_bitstream.c index b65ec02147b40..8538bcbaade7c 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -48,6 +48,10 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } } mp_hal_pin_output(pin); + + // Set systick reset value + systick_hw->rvr = 0x00FFFFFF; + // Enable the systick counter, source CPU clock. systick_hw->csr = 5; From 00f3223bca5c4ebb4e93f7cdafb85d51b140a336 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Tue, 6 Aug 2024 16:13:57 -0600 Subject: [PATCH 67/87] rp2/machine_uart: Allow new TX/RX pins on RP2350. Signed-off-by: Damien George --- ports/rp2/machine_uart.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c index 689906003e7dc..57981363c97b2 100644 --- a/ports/rp2/machine_uart.c +++ b/ports/rp2/machine_uart.c @@ -69,8 +69,13 @@ #define MAX_BUFFER_SIZE (32766) #define IS_VALID_PERIPH(uart, pin) (((((pin) + 4) & 8) >> 3) == (uart)) +#if PICO_RP2350 +#define IS_VALID_TX(uart, pin) (((pin) & 1) == 0 && IS_VALID_PERIPH(uart, pin)) +#define IS_VALID_RX(uart, pin) (((pin) & 1) == 1 && IS_VALID_PERIPH(uart, pin)) +#else #define IS_VALID_TX(uart, pin) (((pin) & 3) == 0 && IS_VALID_PERIPH(uart, pin)) #define IS_VALID_RX(uart, pin) (((pin) & 3) == 1 && IS_VALID_PERIPH(uart, pin)) +#endif #define IS_VALID_CTS(uart, pin) (((pin) & 3) == 2 && IS_VALID_PERIPH(uart, pin)) #define IS_VALID_RTS(uart, pin) (((pin) & 3) == 3 && IS_VALID_PERIPH(uart, pin)) @@ -398,8 +403,8 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, __dsb(); // make sure UARTLCR_H register is written to uart_set_fifo_enabled(self->uart, true); __dsb(); // make sure UARTLCR_H register is written to - gpio_set_function(self->tx, GPIO_FUNC_UART); - gpio_set_function(self->rx, GPIO_FUNC_UART); + gpio_set_function(self->tx, UART_FUNCSEL_NUM(self->uart, self->tx)); + gpio_set_function(self->rx, UART_FUNCSEL_NUM(self->uart, self->rx)); if (self->invert & UART_INVERT_RX) { gpio_set_inover(self->rx, GPIO_OVERRIDE_INVERT); } From 239321389402c2673abf977e7e92a1907cc2862d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 22:26:14 +1000 Subject: [PATCH 68/87] rp2/mphalport: Implement mp_hal_ticks_cpu for RISCV using mcycle. Signed-off-by: Damien George --- ports/rp2/mphalport.c | 11 +++++++++++ ports/rp2/mphalport.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 3fcc011b62676..3f50151620a02 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -125,6 +125,17 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { return did_write ? ret : 0; } +#if PICO_RISCV +__attribute__((naked)) mp_uint_t mp_hal_ticks_cpu(void) { + __asm volatile ( + "li a0, 4\n" // mask value to uninhibit mcycle counter + "csrw mcountinhibit, a0\n" // uninhibit mcycle counter + "csrr a0, mcycle\n" // get mcycle counter + "ret\n" + ); +} +#endif + void mp_hal_delay_us(mp_uint_t us) { // Avoid calling sleep_us() and invoking the alarm pool by splitting long // sleeps into an optional longer sleep and a shorter busy-wait diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index ac1c99264b295..25a472dfbdda4 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -92,11 +92,15 @@ static inline mp_uint_t mp_hal_ticks_ms(void) { return to_ms_since_boot(get_absolute_time()); } +#if PICO_ARM static inline mp_uint_t mp_hal_ticks_cpu(void) { // ticks_cpu() is defined as using the highest-resolution timing source // in the system. This is usually a CPU clock, but doesn't have to be. return time_us_32(); } +#elif PICO_RISCV +mp_uint_t mp_hal_ticks_cpu(void); +#endif static inline mp_uint_t mp_hal_get_cpu_freq(void) { return clock_get_hz(clk_sys); From 6d027dedcc99a3e91dd628decb6374dcd4551a47 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Aug 2024 22:26:37 +1000 Subject: [PATCH 69/87] rp2/machine_bitstream: Implement bitstream for RISC-V using mcycle. Signed-off-by: Damien George --- ports/rp2/machine_bitstream.c | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_bitstream.c b/ports/rp2/machine_bitstream.c index 8538bcbaade7c..ec21beed35c56 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -34,6 +34,25 @@ #define MP_HAL_BITSTREAM_NS_OVERHEAD (9) +#if PICO_RISCV + +__attribute__((naked)) void mcycle_init(void) { + __asm volatile ( + "li a0, 4\n" + "csrw mcountinhibit, a0\n" + "ret\n" + ); +} + +__attribute__((naked)) uint32_t mcycle_get(void) { + __asm volatile ( + "csrr a0, mcycle\n" + "ret\n" + ); +} + +#endif + void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { uint32_t fcpu_mhz = mp_hal_get_cpu_freq() / 1000000; // Convert ns to clock ticks [high_time_0, period_0, high_time_1, period_1]. @@ -49,14 +68,16 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } mp_hal_pin_output(pin); + uint32_t irq_state = mp_hal_quiet_timing_enter(); + + #if PICO_ARM + // Set systick reset value systick_hw->rvr = 0x00FFFFFF; // Enable the systick counter, source CPU clock. systick_hw->csr = 5; - uint32_t irq_state = mp_hal_quiet_timing_enter(); - for (size_t i = 0; i < len; ++i) { uint8_t b = buf[i]; for (size_t j = 0; j < 8; ++j) { @@ -72,6 +93,27 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint } } + #elif PICO_RISCV + + mcycle_init(); + + for (size_t i = 0; i < len; ++i) { + uint8_t b = buf[i]; + for (size_t j = 0; j < 8; ++j) { + uint32_t *t = &timing_ns[b >> 6 & 2]; + uint32_t start_ticks = mcycle_get(); + mp_hal_pin_high(pin); + while ((mcycle_get() - start_ticks) < t[0]) { + } + b <<= 1; + mp_hal_pin_low(pin); + while ((mcycle_get() - start_ticks) < t[1]) { + } + } + } + + #endif + mp_hal_quiet_timing_exit(irq_state); } From e008dbb06debc48f501f7248ac01762668bacc23 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 Aug 2024 17:03:22 +1000 Subject: [PATCH 70/87] tests/ports/rp2: Update DMA test for higher freq CPU. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index 2459213f4c014..f9bd1c5fbf9ae 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -62,7 +62,8 @@ def run_and_time_dma(dma): dma.count = len(dest) // 4 dma.ctrl = dma.pack_ctrl() dt = run_and_time_dma(dma) -print(70 <= dt <= 110) +expected_dt = 90 * 120000000 // machine.freq() +print(abs(dt - expected_dt) <= 20) print(dest[:8], dest[-8:]) dma.close() From 22b2f7388b864c34bf285b4432fcd6dfdae7e9b2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 9 Aug 2024 11:25:01 +1000 Subject: [PATCH 71/87] tests/ports/rp2: Add simple rp2-specific UART test. To test construction of UART instances. Signed-off-by: Damien George --- tests/ports/rp2/rp2_uart.py | 10 ++++++++++ tests/ports/rp2/rp2_uart.py.exp | 1 + 2 files changed, 11 insertions(+) create mode 100644 tests/ports/rp2/rp2_uart.py create mode 100644 tests/ports/rp2/rp2_uart.py.exp diff --git a/tests/ports/rp2/rp2_uart.py b/tests/ports/rp2/rp2_uart.py new file mode 100644 index 0000000000000..da88599344795 --- /dev/null +++ b/tests/ports/rp2/rp2_uart.py @@ -0,0 +1,10 @@ +# Test construction of machine.UART objects. + +import sys +from machine import UART + +print(UART(0, tx=0, rx=1)) + +if "RP2350" in sys.implementation._machine: + # Test that UART can be constructed using other tx/rx pins. + UART(0, tx=2, rx=3) diff --git a/tests/ports/rp2/rp2_uart.py.exp b/tests/ports/rp2/rp2_uart.py.exp new file mode 100644 index 0000000000000..4594de5dc78f2 --- /dev/null +++ b/tests/ports/rp2/rp2_uart.py.exp @@ -0,0 +1 @@ +UART(0, baudrate=115200, bits=8, parity=None, stop=1, tx=0, rx=1, txbuf=256, rxbuf=256, timeout=0, timeout_char=1, invert=None) From df545f4da5e7f900e90c0ea56d2eaf07389b7031 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 9 Aug 2024 12:32:48 +1000 Subject: [PATCH 72/87] tools/ci.sh: Add RPI_PICO2 to CI. Signed-off-by: Damien George --- tools/ci.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index 175d7f57622fb..9774895faf28b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -314,6 +314,8 @@ function ci_rp2_build { make ${MAKEOPTS} -C ports/rp2 make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W submodules make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W USER_C_MODULES=../../examples/usercmodule/micropython.cmake + make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 submodules + make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO submodules make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO From 89b17d19308b030088c3428bf68c121260bb6f5d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 13 Aug 2024 10:34:23 +1000 Subject: [PATCH 73/87] rp2: Workaround pico_aon_timer timezone binary size increase. Provide stub implementations of localtime_r() and mktime() to avoid code size increase. Reported upstream at https://github.com/raspberrypi/pico-sdk/issues/1810 This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/datetime_patch.c | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 ports/rp2/datetime_patch.c diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 36b9b284cf7f1..6a304da97a74a 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -147,6 +147,7 @@ set(MICROPY_SOURCE_DRIVERS set(MICROPY_SOURCE_PORT clocks_extra.c + datetime_patch.c fatfs_port.c help.c machine_bitstream.c diff --git a/ports/rp2/datetime_patch.c b/ports/rp2/datetime_patch.c new file mode 100644 index 0000000000000..810af4cf1650d --- /dev/null +++ b/ports/rp2/datetime_patch.c @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Angus Gratton + * + * 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 +#include "py/mpconfig.h" +#include "shared/timeutils/timeutils.h" + +// This is a workaround for the issue that pico-sdk datetime.c will otherwise +// pull in a lot of libc code for time zone support. +// +// Upstream issue is https://github.com/raspberrypi/pico-sdk/issues/1810 + +struct tm *localtime_r(const time_t *__restrict time, struct tm *__restrict local_time) { + return gmtime_r(time, local_time); +} + +time_t mktime(struct tm *__restrict tm) { + return timeutils_mktime(tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); +} From a81963ca911f582fd26443fcd829d9846d3a304b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 6 Aug 2024 19:49:40 +0100 Subject: [PATCH 74/87] ports/rp2: PSRAM support. Add PSRAM support with auto detection. Performs a best-effort attempt to detect attached PSRAM, configure it and *add* it to the MicroPython heap. If PSRAM is not present, should fall back to use internal RAM. Introduce two new port/board defines: * MICROPY_HW_ENABLE_PSRAM to enable PSRAM. * MICROPY_HW_PSRAM_CS_PIN to define the chip-select pin. Changes: ports/rp2/rp2_psram.c/h: Add new PSRAM module. ports/rp2/main.c: Add optional PSRAM support. ports/rp2/CMakeLists.txt: Include rp2_psram.c. ports/rp2/rp2_flash.c: Add buffered write to avoid reads from PSRAM. ports/rp2/mpconfigport.h: Enable MICROPY_GC_SPLIT_HEAP for boards that set MICROPY_HW_ENABLE_PSRAM. Co-authored-by: Kirk Benell Co-authored-by: Mike Bell Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/main.c | 8 ++ ports/rp2/mpconfigport.h | 3 + ports/rp2/rp2_flash.c | 48 +++++++++-- ports/rp2/rp2_psram.c | 180 +++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_psram.h | 11 +++ 6 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 ports/rp2/rp2_psram.c create mode 100644 ports/rp2/rp2_psram.h diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 6a304da97a74a..f08e036c682c7 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -165,6 +165,7 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c + rp2_psram.c rp2_dma.c uart.c usbd.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 0aef74be246f2..34ae05a954317 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -26,6 +26,7 @@ #include +#include "rp2_psram.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -117,7 +118,14 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); + gc_init(&__GcHeapStart, &__GcHeapEnd); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); + if (psram_size) { + gc_add((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + } + #endif #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 3f3e71cceb1c2..0ad959b38c9f5 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -73,6 +73,9 @@ // Memory allocation policies #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#ifdef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_SPLIT_HEAP (1) +#endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index c1acb54e75748..722bf5c0b76c0 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -26,6 +26,7 @@ #include +#include "rp2_psram.h" #include "py/mphal.h" #include "py/runtime.h" #include "extmod/vfs.h" @@ -76,10 +77,21 @@ static uint32_t begin_critical_flash_section(void) { if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_start_blocking(); } - return save_and_disable_interrupts(); + uint32_t state = save_and_disable_interrupts(); + + // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM + uint8_t *maintenance_ptr = (uint8_t *)XIP_MAINTENANCE_BASE; + for (int i = 1; i < 16 * 1024; i += 8) { + maintenance_ptr[i] = 0; + } + + return state; } static void end_critical_flash_section(uint32_t state) { + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); @@ -145,11 +157,16 @@ static mp_obj_t rp2_flash_readblocks(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_flash_readblocks_obj, 3, 4, rp2_flash_readblocks); +static inline size_t min_size(size_t a, size_t b) { + return a < b ? a : b; +} + static mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { rp2_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); uint32_t offset = mp_obj_get_int(args[1]) * BLOCK_SIZE_BYTES; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + if (n_args == 3) { mp_uint_t atomic_state = begin_critical_flash_section(); flash_range_erase(self->flash_base + offset, bufinfo.len); @@ -159,10 +176,31 @@ static mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { } else { offset += mp_obj_get_int(args[3]); } - mp_uint_t atomic_state = begin_critical_flash_section(); - flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); - end_critical_flash_section(atomic_state); - mp_event_handle_nowait(); + + if ((uintptr_t)bufinfo.buf >= SRAM_BASE) { + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); + end_critical_flash_section(atomic_state); + mp_event_handle_nowait(); + } else { + size_t bytes_left = bufinfo.len; + size_t bytes_offset = 0; + static uint8_t copy_buffer[BLOCK_SIZE_BYTES] = {0}; + + while (bytes_left) { + memcpy(copy_buffer, bufinfo.buf + bytes_offset, min_size(bytes_left, BLOCK_SIZE_BYTES)); + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset + bytes_offset, copy_buffer, min_size(bytes_left, BLOCK_SIZE_BYTES)); + end_critical_flash_section(atomic_state); + bytes_offset += BLOCK_SIZE_BYTES; + if (bytes_left <= BLOCK_SIZE_BYTES) { + break; + } + bytes_left -= BLOCK_SIZE_BYTES; + mp_event_handle_nowait(); + } + } + // TODO check return value return mp_const_none; } diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c new file mode 100644 index 0000000000000..07fd28c49ca8e --- /dev/null +++ b/ports/rp2/rp2_psram.c @@ -0,0 +1,180 @@ +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip_ctrl.h" +#include "hardware/sync.h" +#include "rp2_psram.h" + + +void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { + ; + } + + // For > 133 MHz + qmi_hw->m[0].timing = 0x40000202; + + // For <= 133 MHz + // qmi_hw->m[0].timing = 0x40000101; + + // Force a read through XIP to ensure the timing is applied + volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; + (void)*ptr; +} + +size_t __no_inline_not_in_flash_func(psram_detect)() { + int psram_size = 0; + + uint32_t intr_stash = save_and_disable_interrupts(); + + // Try and read the PSRAM ID via direct_csr. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // Exit out of QMI in case we've inited already + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + + // Transmit as quad. + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + (void)qmi_hw->direct_rx; + + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + + // Read the id + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + uint8_t kgd = 0; + uint8_t eid = 0; + + for (size_t i = 0; i < 7; i++) + { + if (i == 0) { + qmi_hw->direct_tx = 0x9f; + } else { + qmi_hw->direct_tx = 0xff; + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + if (i == 5) { + kgd = qmi_hw->direct_rx; + } else if (i == 6) { + eid = qmi_hw->direct_rx; + } else { + (void)qmi_hw->direct_rx; + } + } + + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + if (kgd == 0x5D) { + psram_size = 1024 * 1024; // 1 MiB + uint8_t size_id = eid >> 5; + if (eid == 0x26 || size_id == 2) { + psram_size *= 8; // 8 MiB + } else if (size_id == 0) { + psram_size *= 2; // 2 MiB + } else if (size_id == 1) { + psram_size *= 4; // 4 MiB + } + } + + restore_interrupts(intr_stash); + return psram_size; +} + +size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { + gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + return 0; + } + + psram_set_qmi_timing(); + + // Enable direct mode, PSRAM CS, clkdiv of 10. + qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ + QMI_DIRECT_CSR_EN_BITS | \ + QMI_DIRECT_CSR_AUTO_CS1N_BITS; + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Enable QPI mode on the PSRAM + const uint CMD_QPI_EN = 0x35; + qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; + + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + #if 0 + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 240MHz + // - Min deselect assumes a sys clock speed <= 305MHz + // - Clkdiv of 2 is OK up to 266MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 30 << QMI_M1_TIMING_MAX_SELECT_LSB | + 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 3 << QMI_M1_TIMING_RXDELAY_LSB | + 2 << QMI_M1_TIMING_CLKDIV_LSB; + #else + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 138MHz + // - Clkdiv of 1 is OK up to 133MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 2 << QMI_M1_TIMING_RXDELAY_LSB | + 1 << QMI_M1_TIMING_CLKDIV_LSB; + #endif + + // Set PSRAM commands and formats + qmi_hw->m[1].rfmt = + QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ + QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ + QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ + 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; + + qmi_hw->m[1].rcmd = 0xEB; + + qmi_hw->m[1].wfmt = + QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ + QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ + QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; + + qmi_hw->m[1].wcmd = 0x38; + + // Disable direct mode + qmi_hw->direct_csr = 0; + + // Enable writes to PSRAM + hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); + + // TODO: Detect PSRAM ID and size + return psram_size; +} diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h new file mode 100644 index 0000000000000..cd791602cdd68 --- /dev/null +++ b/ports/rp2/rp2_psram.h @@ -0,0 +1,11 @@ +#include "pico/stdlib.h" + +#ifndef MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H +#define MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H + +#define PSRAM_LOCATION _u(0x11000000) + +extern void psram_set_qmi_timing(); +extern size_t psram_init(uint cs_pin); + +#endif From 360e6edd65a387021a6b45f777367aea654048c4 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 10:16:55 +0100 Subject: [PATCH 75/87] ports/rp2: PSRAM: Fix RP2040/Pico build. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 7 ++++++- ports/rp2/rp2_flash.c | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index f08e036c682c7..5e69da4b8cb08 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -165,7 +165,6 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c - rp2_psram.c rp2_dma.c uart.c usbd.c @@ -174,6 +173,12 @@ set(MICROPY_SOURCE_PORT ${CMAKE_BINARY_DIR}/pins_${MICROPY_BOARD}.c ) +if(PICO_RP2350) + list(APPEND MICROPY_SOURCE_PORT + rp2_psram.c + ) +endif() + set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_PY} ${MICROPY_DIR}/shared/readline/readline.c diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 722bf5c0b76c0..4386986011db2 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -79,11 +79,13 @@ static uint32_t begin_critical_flash_section(void) { } uint32_t state = save_and_disable_interrupts(); + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM uint8_t *maintenance_ptr = (uint8_t *)XIP_MAINTENANCE_BASE; for (int i = 1; i < 16 * 1024; i += 8) { maintenance_ptr[i] = 0; } + #endif return state; } From 3cad8bbdf7edcb17151cdbdfe908ca8097bf98a6 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:10 +0100 Subject: [PATCH 76/87] ports/rp2: Re-init PSRAM on CPU freq change. Signed-off-by: Phil Howard --- ports/rp2/modmachine.c | 4 +++ ports/rp2/rp2_psram.c | 60 ++++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 2faf0bc6f8713..1fb6bc6df9d8e 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -31,6 +31,7 @@ #include "mp_usbd.h" #include "modmachine.h" #include "uart.h" +#include "rp2_psram.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -115,6 +116,9 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { setup_default_uart(); mp_uart_init(); #endif + #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif } static void mp_machine_idle(void) { diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 07fd28c49ca8e..90c370c86a55e 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -1,6 +1,7 @@ #include "hardware/structs/ioqspi.h" #include "hardware/structs/qmi.h" #include "hardware/structs/xip_ctrl.h" +#include "hardware/clocks.h" #include "hardware/sync.h" #include "rp2_psram.h" @@ -11,11 +12,13 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { ; } - // For > 133 MHz - qmi_hw->m[0].timing = 0x40000202; - - // For <= 133 MHz - // qmi_hw->m[0].timing = 0x40000101; + if (clock_get_hz(clk_sys) > 133000000) { + // For > 133 MHz + qmi_hw->m[0].timing = 0x40000202; + } else { + // For <= 133 MHz + qmi_hw->m[0].timing = 0x40000101; + } // Force a read through XIP to ensure the timing is applied volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; @@ -123,29 +126,29 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { ; } - #if 0 - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 240MHz - // - Min deselect assumes a sys clock speed <= 305MHz - // - Clkdiv of 2 is OK up to 266MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 30 << QMI_M1_TIMING_MAX_SELECT_LSB | - 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 3 << QMI_M1_TIMING_RXDELAY_LSB | - 2 << QMI_M1_TIMING_CLKDIV_LSB; - #else - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 138MHz - // - Clkdiv of 1 is OK up to 133MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 2 << QMI_M1_TIMING_RXDELAY_LSB | - 1 << QMI_M1_TIMING_CLKDIV_LSB; - #endif + if (clock_get_hz(clk_sys) >= 120000000) { + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 305MHz + // - Clkdiv of 2 is OK up to 266MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 3 << QMI_M1_TIMING_RXDELAY_LSB | + 2 << QMI_M1_TIMING_CLKDIV_LSB; + } else { + // Set PSRAM timing for APS6404: + // - Max select assumes a sys clock speed >= 120MHz + // - Min deselect assumes a sys clock speed <= 138MHz + // - Clkdiv of 1 is OK up to 133MHz. + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + 15 << QMI_M1_TIMING_MAX_SELECT_LSB | + 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | + 2 << QMI_M1_TIMING_RXDELAY_LSB | + 1 << QMI_M1_TIMING_CLKDIV_LSB; + } // Set PSRAM commands and formats qmi_hw->m[1].rfmt = @@ -175,6 +178,5 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { // Enable writes to PSRAM hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); - // TODO: Detect PSRAM ID and size return psram_size; } From 562b929cadc58ed30684c068ece0afe383e432dd Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:33 +0100 Subject: [PATCH 77/87] ports/rp2: Make split-heap optional. My tests found issues when PSRAM is combined with the existing RAM in a split-heap configuration. Since this option is not enabled by default on RP2 I have changed it to be optional. PSRAM will be used exclusively if MICROPY_GC_SPLIT_HEAP == 0, it will be added to RAM if MICROPY_GC_SPLIT_HEAP == 1, and the system will fall back to RAM only if it's not detected. Signed-off-by: Phil Howard --- ports/rp2/main.c | 10 +++++++++- ports/rp2/mpconfigport.h | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 34ae05a954317..6b14c9c809155 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -119,12 +119,20 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); - gc_init(&__GcHeapStart, &__GcHeapEnd); #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); if (psram_size) { + #if MICROPY_GC_SPLIT_HEAP + gc_init(&__GcHeapStart, &__GcHeapEnd); gc_add((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + #else + gc_init((void *)PSRAM_LOCATION, (void *)(PSRAM_LOCATION + psram_size)); + #endif + } else { + gc_init(&__GcHeapStart, &__GcHeapEnd); } + #else + gc_init(&__GcHeapStart, &__GcHeapEnd); #endif #if MICROPY_PY_LWIP diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 0ad959b38c9f5..da3f7592ba8df 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -73,8 +73,8 @@ // Memory allocation policies #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t -#ifdef MICROPY_HW_ENABLE_PSRAM -#define MICROPY_GC_SPLIT_HEAP (1) +#ifndef MICROPY_GC_SPLIT_HEAP +#define MICROPY_GC_SPLIT_HEAP (0) // whether PSRAM is added to or replaces the heap #endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) From aee2331067ad79aba8b250e7fc67c821efde6e13 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 17:34:04 +0100 Subject: [PATCH 78/87] ports/rp2: Compute QMI timing based on system clock. Signed-off-by: Mike Bell --- ports/rp2/rp2_psram.c | 65 ++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 90c370c86a55e..e3da848d09265 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -12,13 +12,15 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { ; } - if (clock_get_hz(clk_sys) > 133000000) { - // For > 133 MHz - qmi_hw->m[0].timing = 0x40000202; - } else { - // For <= 133 MHz - qmi_hw->m[0].timing = 0x40000101; - } + // Use the minimum divisor assuming a 133MHz flash. + // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the + // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. + const int max_flash_freq = 133000000; + const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq; + const int rxdelay = divisor; + qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; // Force a read through XIP to ensure the timing is applied volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; @@ -126,30 +128,35 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { ; } - if (clock_get_hz(clk_sys) >= 120000000) { - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 305MHz - // - Clkdiv of 2 is OK up to 266MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 5 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 3 << QMI_M1_TIMING_RXDELAY_LSB | - 2 << QMI_M1_TIMING_CLKDIV_LSB; - } else { - // Set PSRAM timing for APS6404: - // - Max select assumes a sys clock speed >= 120MHz - // - Min deselect assumes a sys clock speed <= 138MHz - // - Clkdiv of 1 is OK up to 133MHz. - qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | - QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | - 15 << QMI_M1_TIMING_MAX_SELECT_LSB | - 2 << QMI_M1_TIMING_MIN_DESELECT_LSB | - 2 << QMI_M1_TIMING_RXDELAY_LSB | - 1 << QMI_M1_TIMING_CLKDIV_LSB; + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const int max_psram_freq = 133000000; + const int clock_hz = clock_get_hz(clk_sys); + int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if (divisor == 1 && clock_hz > 100000000) { + divisor = 2; + } + int rxdelay = divisor; + if (clock_hz / divisor > 100000000) { + rxdelay += 1; } + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + const int clock_period_fs = 1000000000000000ll / clock_hz; + const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 + const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; + + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + max_select << QMI_M1_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + // Set PSRAM commands and formats qmi_hw->m[1].rfmt = QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ From 03c5d9f85987344c437162778152fc7d1514c8f6 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 17:45:15 +0100 Subject: [PATCH 79/87] ports/rp2: Fix garbage collection with large heap. Signed-off-by: Mike Bell --- ports/rp2/mpconfigport.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index da3f7592ba8df..23be3240cc13a 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -71,8 +71,16 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) #endif +#ifndef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_HW_ENABLE_PSRAM (0) +#endif + // Memory allocation policies +#if MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#else #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#endif #ifndef MICROPY_GC_SPLIT_HEAP #define MICROPY_GC_SPLIT_HEAP (0) // whether PSRAM is added to or replaces the heap #endif From 50dee90c1d9bc3b0b441d1cc5078794b8a654e10 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 20:52:34 +0100 Subject: [PATCH 80/87] ports/rp2: Remove flash timing control from PSRAM. Signed-off-by: Mike Bell --- ports/rp2/rp2_psram.c | 23 ----------------------- ports/rp2/rp2_psram.h | 1 - 2 files changed, 24 deletions(-) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index e3da848d09265..30f87adaf4122 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -6,27 +6,6 @@ #include "rp2_psram.h" -void __no_inline_not_in_flash_func(psram_set_qmi_timing)() { - // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) - while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { - ; - } - - // Use the minimum divisor assuming a 133MHz flash. - // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the - // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. - const int max_flash_freq = 133000000; - const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq; - const int rxdelay = divisor; - qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | - rxdelay << QMI_M1_TIMING_RXDELAY_LSB | - divisor << QMI_M1_TIMING_CLKDIV_LSB; - - // Force a read through XIP to ensure the timing is applied - volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; - (void)*ptr; -} - size_t __no_inline_not_in_flash_func(psram_detect)() { int psram_size = 0; @@ -110,8 +89,6 @@ size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { return 0; } - psram_set_qmi_timing(); - // Enable direct mode, PSRAM CS, clkdiv of 10. qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ QMI_DIRECT_CSR_EN_BITS | \ diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h index cd791602cdd68..718e7a1ea9e76 100644 --- a/ports/rp2/rp2_psram.h +++ b/ports/rp2/rp2_psram.h @@ -5,7 +5,6 @@ #define PSRAM_LOCATION _u(0x11000000) -extern void psram_set_qmi_timing(); extern size_t psram_init(uint cs_pin); #endif From 8070c714673076097ce67693fe0336479be713c3 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 21:39:05 +0100 Subject: [PATCH 81/87] ports/rp2: Set flash divisor appropriately. Signed-off-by: Mike Bell --- ports/rp2/main.c | 4 +++ ports/rp2/modmachine.c | 12 ++++++++ ports/rp2/rp2_flash.c | 69 ++++++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_flash.h | 7 +++++ 4 files changed, 92 insertions(+) create mode 100644 ports/rp2/rp2_flash.h diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 6b14c9c809155..bb0283d9bb542 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -27,6 +27,7 @@ #include #include "rp2_psram.h" +#include "rp2_flash.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -91,6 +92,9 @@ int main(int argc, char **argv) { // Set the MCU frequency and as a side effect the peripheral clock to 48 MHz. set_sys_clock_khz(125000, false); + // Set the flash divisor to an appropriate value + rp2_flash_set_timing(); + #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) setup_default_uart(); diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 1fb6bc6df9d8e..7e9881b6c5206 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -32,6 +32,7 @@ #include "modmachine.h" #include "uart.h" #include "rp2_psram.h" +#include "rp2_flash.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -95,6 +96,11 @@ static mp_obj_t mp_machine_get_freq(void) { static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_int_t freq = mp_obj_get_int(args[0]); + + // If necessary, increase the flash divider before increasing the clock speed + const int old_freq = clock_get_hz(clk_sys); + rp2_flash_set_timing_for_freq(MAX(freq, old_freq)); + if (!set_sys_clock_khz(freq / 1000, false)) { mp_raise_ValueError(MP_ERROR_TEXT("cannot change frequency")); } @@ -112,6 +118,12 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } } } + + // If clock speed was reduced, maybe we can reduce the flash divider + if (freq < old_freq) { + rp2_flash_set_timing_for_freq(freq); + } + #if MICROPY_HW_ENABLE_UART_REPL setup_default_uart(); mp_uart_init(); diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 4386986011db2..45f2ec70e1017 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -33,6 +33,12 @@ #include "modrp2.h" #include "hardware/flash.h" #include "pico/binary_info.h" +#ifdef PICO_RP2350 +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#else +#include "hardware/structs/ssi.h" +#endif #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) @@ -71,6 +77,48 @@ bi_decl(bi_block_device( BINARY_INFO_BLOCK_DEV_FLAG_WRITE | BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); +// Function to set the flash divisor to the correct divisor, assumes interrupts disabled +// and core1 locked out if relevant. +static void __no_inline_not_in_flash_func(rp2_flash_set_timing_internal)(int clock_hz) { + + // Use the minimum divisor assuming a 133MHz flash. + const int max_flash_freq = 133000000; + int divisor = (clock_hz + max_flash_freq - 1) / max_flash_freq; + + #if PICO_RP2350 + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { + ; + } + + // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the + // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. + const int rxdelay = divisor; + qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + + // Force a read through XIP to ensure the timing is applied + volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; + (void)*ptr; + #else + // RP2040 SSI hardware only supports even divisors + if (divisor & 1) { + divisor += 1; + } + + // Wait for SSI not busy + while (ssi_hw->sr & SSI_SR_BUSY_BITS) { + ; + } + + // Disable, set the new divisor, and re-enable + hw_clear_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + ssi_hw->baudr = divisor; + hw_set_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + #endif +} + // Flash erase and write must run with interrupts disabled and the other core suspended, // because the XIP bit gets disabled. static uint32_t begin_critical_flash_section(void) { @@ -94,6 +142,7 @@ static void end_critical_flash_section(uint32_t state) { #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); @@ -250,3 +299,23 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, rp2_flash_make_new, locals_dict, &rp2_flash_locals_dict ); + +// Modify the flash timing. Ensure flash access is suspended while +// the timings are altered. +void rp2_flash_set_timing_for_freq(int clock_hz) { + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_start_blocking(); + } + uint32_t state = save_and_disable_interrupts(); + + rp2_flash_set_timing_internal(clock_hz); + + restore_interrupts(state); + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_end_blocking(); + } +} + +void rp2_flash_set_timing() { + rp2_flash_set_timing_for_freq(clock_get_hz(clk_sys)); +} diff --git a/ports/rp2/rp2_flash.h b/ports/rp2/rp2_flash.h new file mode 100644 index 0000000000000..d5cf3ba2acac0 --- /dev/null +++ b/ports/rp2/rp2_flash.h @@ -0,0 +1,7 @@ +#ifndef MICROPY_INCLUDED_RP2_MACHINE_FLASH_H +#define MICROPY_INCLUDED_RP2_MACHINE_FLASH_H + +extern void rp2_flash_set_timing_for_freq(int clock_hz); +extern void rp2_flash_set_timing(); + +#endif From 291ca793ca700f2bf3e6b73ad53f9b99db7bdc2f Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Thu, 15 Aug 2024 14:01:57 +0100 Subject: [PATCH 82/87] ports/rp2: Reset flash timing before PSRAM timing. Signed-off-by: Mike Bell --- ports/rp2/rp2_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 45f2ec70e1017..eae219f49ea9e 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -139,10 +139,10 @@ static uint32_t begin_critical_flash_section(void) { } static void end_critical_flash_section(uint32_t state) { + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); #if defined(MICROPY_HW_PSRAM_CS_PIN) && MICROPY_HW_ENABLE_PSRAM psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif - rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); restore_interrupts(state); if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { multicore_lockout_end_blocking(); From 51cd340020d58258ed6cdc8c995f20e6b940cb1e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 15 Aug 2024 16:18:09 +0100 Subject: [PATCH 83/87] ports/rp2: Add RP2350 slices to machine_pwm. Signed-off-by: Phil Howard --- ports/rp2/machine_pwm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_pwm.c b/ports/rp2/machine_pwm.c index 4c0bc2c103243..fd20be5ec2f6f 100644 --- a/ports/rp2/machine_pwm.c +++ b/ports/rp2/machine_pwm.c @@ -67,10 +67,20 @@ static machine_pwm_obj_t machine_pwm_obj[] = { {{&machine_pwm_type}, 6, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, {{&machine_pwm_type}, 7, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, {{&machine_pwm_type}, 7, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + #if NUM_PWM_SLICES == 12 + {{&machine_pwm_type}, 8, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 8, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 9, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 9, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 10, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 10, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 11, PWM_CHAN_A, 0, DUTY_NOT_SET, 0 }, + {{&machine_pwm_type}, 11, PWM_CHAN_B, 0, DUTY_NOT_SET, 0 }, + #endif }; static bool defer_start; -static bool slice_freq_set[8]; +static bool slice_freq_set[NUM_PWM_SLICES]; static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq); static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16); @@ -155,7 +165,7 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Stop all active slices. void machine_pwm_deinit_all(void) { - for (int i = 0; i < 8; i++) { + for (int i = 0; i < NUM_PWM_SLICES; i++) { slice_freq_set[i] = false; pwm_set_enabled(machine_pwm_obj[i].slice, false); } From 4010a9fa8b5f60304db662b9d04cc2cfaf894ab1 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 30 Aug 2024 08:36:58 +0100 Subject: [PATCH 84/87] ports/rp2: Fix rp2_pio for 48-pin RP2350B. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 6 ++++++ ports/rp2/rp2_pio.c | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 5e69da4b8cb08..9dc5dbc948ea7 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -538,6 +538,12 @@ set_source_files_properties( COMPILE_OPTIONS "-O2" ) +set_source_files_properties( + rp2_pio.c + PROPERTIES + COMPILE_OPTIONS "-Wno-error=discarded-qualifiers" +) + set_source_files_properties( ${PICO_SDK_PATH}/src/rp2_common/pico_double/double_math.c ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_math.c diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 8e91c1d8fb328..0d885471538dc 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -32,11 +32,15 @@ #include "py/mphal.h" #include "shared/runtime/mpirq.h" #include "modrp2.h" +#include "machine_pin.h" +#include "genhdr/pins.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/pio.h" +extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; + typedef struct _rp2_pio_obj_t { mp_obj_base_t base; PIO pio; From ed4c0d284314cfcb78747e6f637ae5d201a8b1d1 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Fri, 30 Aug 2024 10:58:15 -0600 Subject: [PATCH 85/87] comment tweak --- ports/rp2/rp2_psram.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c index 4696a233ab6f9..a3b7ad2b009bb 100644 --- a/ports/rp2/rp2_psram.c +++ b/ports/rp2/rp2_psram.c @@ -41,7 +41,7 @@ // The origin of this logic is from the Circuit Python code that was downloaded from: // https://github.com/raspberrypi/pico-sdk-rp2350/issues/12#issuecomment-2055274428 // -// The returnedID for the above IC +// The returned ID value for the above IC const uint8_t PSRAM_ID = 0x5D; // From the above datasheet, PSRAM SPI command codes @@ -57,7 +57,7 @@ const uint8_t PSRAM_CMD_NOOP = 0xFF; // For PSRAM timing calculations - to use int math, we work in femto seconds (fs) (1e-15), -// NOTE: This idea is from micro python work on psram.. +// NOTE: This idea is from micro python work on psram support. #define SEC_TO_FS 1000000000000000ll From c399dd2e398e30eb37da08622585bfecd8875911 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Fri, 30 Aug 2024 12:49:55 -0600 Subject: [PATCH 86/87] updates/results from merge with the latest on the pimoroni-feature/psram branch --- .github/workflows/ports_qemu-arm.yml | 2 +- .github/workflows/ports_zephyr.yml | 5 + docs/library/machine.UART.rst | 105 +- docs/library/network.PPP.rst | 98 + docs/library/network.rst | 1 + docs/library/rp2.PIO.rst | 11 + examples/network/http_client.py | 39 +- examples/network/https_client.py | 46 +- examples/network/https_client_nonblocking.py | 4 +- extmod/extmod.cmake | 30 + extmod/extmod.mk | 30 + extmod/modnetwork.c | 4 + extmod/modnetwork.h | 4 + extmod/modtls_mbedtls.c | 49 +- extmod/network_ppp_lwip.c | 326 +++ extmod/network_wiznet5k.c | 15 +- lib/lwip | 2 +- ports/cc3200/mods/pybuart.c | 1 + ports/esp32/esp32_common.cmake | 1 + ports/esp32/machine_timer.c | 49 +- ports/esp32/machine_timer.h | 66 + ports/esp32/machine_uart.c | 191 +- ports/esp32/mpconfigport.h | 1 + ports/mimxrt/Makefile | 2 +- ports/mimxrt/hal/fsl_lpuart.c | 2013 +++++++++++++++++ ports/mimxrt/machine_uart.c | 83 +- ports/mimxrt/mpconfigport.h | 1 + ports/nrf/Makefile | 1 + ports/nrf/modules/machine/uart.c | 113 +- ports/nrf/mpconfigport.h | 4 + ports/qemu-arm/Makefile | 46 +- ports/qemu-arm/Makefile.test | 33 - ports/qemu-arm/README.md | 59 +- ports/qemu-arm/main.c | 53 +- ports/qemu-arm/modmachine.c | 16 + ports/qemu-arm/mpconfigport.h | 13 +- ports/qemu-arm/mphalport.c | 66 + ports/qemu-arm/mphalport.h | 5 +- ports/qemu-arm/startup.c | 29 +- ports/qemu-arm/tests_profile.txt | 16 - ports/qemu-arm/uart.c | 57 +- ports/qemu-arm/uart.h | 4 + ports/qemu-riscv/Makefile | 2 +- .../test_main.c => qemu-riscv/main.c} | 38 +- ports/renesas-ra/machine_uart.c | 28 + ports/renesas-ra/uart.c | 45 +- ports/renesas-ra/uart.h | 24 +- ports/rp2/CMakeLists.txt | 7 + .../datetime_patch.c} | 23 +- ports/rp2/lwip_inc/lwipopts.h | 11 +- ports/rp2/machine_adc.c | 7 +- ports/rp2/machine_uart.c | 136 +- ports/rp2/main.c | 1 + ports/rp2/modmachine.h | 1 + ports/rp2/mpconfigport.h | 2 + ports/rp2/rp2_pio.c | 69 +- ports/samd/machine_uart.c | 198 +- ports/samd/mcu/samd21/mpconfigmcu.h | 9 + ports/samd/mcu/samd51/mpconfigmcu.h | 1 + ports/stm32/lwip_inc/arch/cc.h | 4 +- ports/stm32/lwip_inc/lwipopts.h | 11 +- ports/stm32/machine_uart.c | 8 + ports/stm32/mpconfigport.h | 1 + ports/stm32/uart.c | 34 +- ports/stm32/uart.h | 2 +- ports/unix/coveragecpp.cpp | 17 +- ports/zephyr/main.c | 14 - ports/zephyr/make-bin-testsuite | 20 - ports/zephyr/prj.conf | 2 +- ports/zephyr/prj_minimal.conf | 2 +- py/mkrules.mk | 4 +- shared/runtime/semihosting_arm.c | 33 +- shared/runtime/semihosting_arm.h | 4 + shared/tinyusb/mp_usbd.h | 12 +- shared/tinyusb/mp_usbd_runtime.c | 8 +- shared/tinyusb/tusb_config.h | 4 + tests/extmod/machine_uart_irq_txidle.py | 76 + tests/extmod/machine_uart_irq_txidle.py.exp | 12 + .../extmod_hardware/machine_uart_irq_break.py | 62 + .../machine_uart_irq_break.py.exp | 15 + tests/extmod_hardware/machine_uart_irq_rx.py | 78 + .../machine_uart_irq_rx.py.exp | 12 + .../machine_uart_irq_rxidle.py | 70 + .../machine_uart_irq_rxidle.py.exp | 12 + tests/ports/rp2/rp2_uart.py | 2 +- tests/run-tests.py | 14 +- tools/ci.sh | 18 +- tools/pyboard.py | 4 +- 88 files changed, 4420 insertions(+), 431 deletions(-) create mode 100644 docs/library/network.PPP.rst create mode 100644 extmod/network_ppp_lwip.c create mode 100644 ports/esp32/machine_timer.h create mode 100644 ports/mimxrt/hal/fsl_lpuart.c delete mode 100644 ports/qemu-arm/Makefile.test create mode 100644 ports/qemu-arm/mphalport.c delete mode 100644 ports/qemu-arm/tests_profile.txt rename ports/{qemu-arm/test_main.c => qemu-riscv/main.c} (65%) rename ports/{zephyr/mpconfigport_bin_testsuite.h => rp2/datetime_patch.c} (66%) delete mode 100755 ports/zephyr/make-bin-testsuite create mode 100644 tests/extmod/machine_uart_irq_txidle.py create mode 100644 tests/extmod/machine_uart_irq_txidle.py.exp create mode 100644 tests/extmod_hardware/machine_uart_irq_break.py create mode 100644 tests/extmod_hardware/machine_uart_irq_break.py.exp create mode 100644 tests/extmod_hardware/machine_uart_irq_rx.py create mode 100644 tests/extmod_hardware/machine_uart_irq_rx.py.exp create mode 100644 tests/extmod_hardware/machine_uart_irq_rxidle.py create mode 100644 tests/extmod_hardware/machine_uart_irq_rxidle.py.exp diff --git a/.github/workflows/ports_qemu-arm.yml b/.github/workflows/ports_qemu-arm.yml index db3cd7871d591..99750b7535657 100644 --- a/.github/workflows/ports_qemu-arm.yml +++ b/.github/workflows/ports_qemu-arm.yml @@ -29,4 +29,4 @@ jobs: run: source tools/ci.sh && ci_qemu_arm_build - name: Print failures if: failure() - run: grep --before-context=100 --text "FAIL" ports/qemu-arm/build/console.out + run: tests/run-tests.py --print-failures diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index f6f328c9277e4..d075582f30919 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -27,3 +27,8 @@ jobs: run: source tools/ci.sh && ci_zephyr_install - name: Build run: source tools/ci.sh && ci_zephyr_build + - name: Run main test suite + run: source tools/ci.sh && ci_zephyr_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures diff --git a/docs/library/machine.UART.rst b/docs/library/machine.UART.rst index 072bdb7188a3d..0f3e77ec471a9 100644 --- a/docs/library/machine.UART.rst +++ b/docs/library/machine.UART.rst @@ -152,31 +152,6 @@ Methods Send a break condition on the bus. This drives the bus low for a duration longer than required for a normal transmission of a character. -.. method:: UART.irq(trigger, priority=1, handler=None, wake=machine.IDLE) - - Create a callback to be triggered when data is received on the UART. - - - *trigger* can only be ``UART.RX_ANY`` - - *priority* level of the interrupt. Can take values in the range 1-7. - Higher values represent higher priorities. - - *handler* an optional function to be called when new characters arrive. - - *wake* can only be ``machine.IDLE``. - - .. note:: - - The handler will be called whenever any of the following two conditions are met: - - - 8 new characters have been received. - - At least 1 new character is waiting in the Rx buffer and the Rx line has been - silent for the duration of 1 complete frame. - - This means that when the handler function is called there will be between 1 to 8 - characters waiting. - - Returns an irq object. - - Availability: WiPy. - .. method:: UART.flush() Waits until all data has been sent. In case of a timeout, an exception is raised. The timeout @@ -203,11 +178,85 @@ Methods Availability: rp2, esp32, esp8266, mimxrt, cc3200, stm32, nrf ports, renesas-ra +.. method:: UART.irq(handler=None, trigger=0, hard=False) + + Configure an interrupt handler to be called when a UART event occurs. + + The arguments are: + + - *handler* is an optional function to be called when the interrupt event + triggers. The handler must take exactly one argument which is the + ``UART`` instance. + + - *trigger* configures the event(s) which can generate an interrupt. + Possible values are a mask of one or more of the following: + + - ``UART.IRQ_RXIDLE`` interrupt after receiving at least one character + and then the RX line goes idle. + - ``UART.IRQ_RX`` interrupt after each received character. + - ``UART.IRQ_TXIDLE`` interrupt after or while the last character(s) of + a message are or have been sent. + - ``UART.IRQ_BREAK`` interrupt when a break state is detected at RX + + - *hard* if true a hardware interrupt is used. This reduces the delay + between the pin change and the handler being called. Hard interrupt + handlers may not allocate memory; see :ref:`isr_rules`. + + Returns an irq object. + + Due to limitations of the hardware not all trigger events are available on all ports. + + .. table:: Availability of triggers + :align: center + + ============== ========== ====== ========== ========= + Port / Trigger IRQ_RXIDLE IRQ_RX IRQ_TXIDLE IRQ_BREAK + ============== ========== ====== ========== ========= + CC3200 yes + ESP32 yes yes yes + MIMXRT yes yes + NRF yes yes + RENESAS-RA yes yes + RP2 yes yes yes + SAMD yes yes yes + STM32 yes yes + ============== ========== ====== ========== ========= + + + .. note:: + - The ESP32 port does not support the option hard=True. + + - The rp2 port's UART.IRQ_TXIDLE is only triggered when the message + is longer than 5 characters and the trigger happens when still 5 characters + are to be sent. + + - The rp2 port's UART.IRQ_BREAK needs receiving valid characters for triggering + again. + + - The SAMD port's UART.IRQ_TXIDLE is triggered while the last character is sent. + + - On STM32F4xx MCU's, using the trigger UART.IRQ_RXIDLE the handler will be called once + after the first character and then after the end of the message, when the line is + idle. + + + Availability: cc3200, esp32, mimxrt, nrf, renesas-ra, rp2, samd, stm32. + Constants --------- -.. data:: UART.RX_ANY +.. data:: UART.RTS + UART.CTS + + Flow control options. + + Availability: esp32, mimxrt, renesas-ra, rp2, stm32. + +.. data:: UART.IRQ_RXIDLE + UART.IRQ_RX + UART.IRQ_TXIDLE + UART.IRQ_BREAK - IRQ trigger sources + IRQ trigger sources. - Availability: WiPy. + Availability: renesas-ra, stm32, esp32, rp2040, mimxrt, samd, cc3200. diff --git a/docs/library/network.PPP.rst b/docs/library/network.PPP.rst new file mode 100644 index 0000000000000..85f580ce540ed --- /dev/null +++ b/docs/library/network.PPP.rst @@ -0,0 +1,98 @@ +.. currentmodule:: network +.. _network.PPP: + +class PPP -- create network connections over serial PPP +======================================================= + +This class allows you to create a network connection over a serial port using +the PPP protocol. It is only available on selected ports and boards. + +Example usage:: + + import network + + ppp = network.PPP(uart) + ppp.connect() + + while not ppp.isconnected(): + pass + + print(ppp.ipconfig("addr4")) + + # use the socket module as usual, etc + + ppp.disconnect() + +Constructors +------------ + +.. class:: PPP(stream) + + Create a PPP driver object. + + Arguments are: + + - *stream* is any object that supports the stream protocol, but is most commonly a + :class:`machine.UART` instance. This stream object must have an ``irq()`` method + and an ``IRQ_RXIDLE`` constant, for use by `PPP.connect`. + +Methods +------- + +.. method:: PPP.connect(security=SEC_NONE, user=None, key=None) + + Initiate a PPP connection with the given parameters: + + - *security* is the type of security, either ``PPP.SEC_NONE``, ``PPP.SEC_PAP``, + or ``PPP.SEC_CHAP``. + - *user* is an optional user name to use with the security mode. + - *key* is an optional password to use with the security mode. + + When this method is called the underlying stream has its interrupt configured to call + `PPP.poll` via ``stream.irq(ppp.poll, stream.IRQ_RXIDLE)``. This makes sure the + stream is polled, and data passed up the PPP stack, wheverver data becomes available + on the stream. + + The connection proceeds asynchronously, in the background. + +.. method:: PPP.disconnect() + + Terminate the connection. This must be called to cleanly close the PPP connection. + +.. method:: PPP.isconnected() + + Returns ``True`` if the PPP link is connected and up. + Returns ``False`` otherwise. + +.. method:: PPP.status() + + Returns the PPP status. + +.. method:: PPP.config(config_parameters) + + Sets or gets parameters of the PPP interface. There are currently no parameter that + can be set or retrieved. + +.. method:: PPP.ipconfig('param') + PPP.ipconfig(param=value, ...) + + See `AbstractNIC.ipconfig`. + +.. method:: PPP.ifconfig([(ip, subnet, gateway, dns)]) + + See `AbstractNIC.ifconfig`. + +.. method:: PPP.poll() + + Poll the underlying stream for data, and pass it up the PPP stack. + This is called automatically if the stream is a UART with a RXIDLE interrupt, + so it's not usually necessary to call it manually. + +Constants +--------- + +.. data:: PPP.SEC_NONE + PPP.SEC_PAP + PPP.SEC_CHAP + + The type of connection security. diff --git a/docs/library/network.rst b/docs/library/network.rst index 6a436fa85e052..d05d17132dc67 100644 --- a/docs/library/network.rst +++ b/docs/library/network.rst @@ -192,6 +192,7 @@ provide a way to control networking interfaces of various kinds. network.WLANWiPy.rst network.WIZNET5K.rst network.LAN.rst + network.PPP.rst Network functions ================= diff --git a/docs/library/rp2.PIO.rst b/docs/library/rp2.PIO.rst index e0675af1e9a2c..f922456c8c483 100644 --- a/docs/library/rp2.PIO.rst +++ b/docs/library/rp2.PIO.rst @@ -27,6 +27,17 @@ Constructors Methods ------- +.. method:: PIO.gpio_base([base]) + + Query and optionally set the current GPIO base for this PIO instance. + + If an argument is given then it must be a pin (or integer corresponding to a pin + number), restricted to either GPIO0 or GPIO16. The GPIO base will then be set to + that pin. Setting the GPIO base must be done before any programs are added or state + machines created. + + Returns the current GPIO base pin. + .. method:: PIO.add_program(program) Add the *program* to the instruction memory of this PIO instance. diff --git a/examples/network/http_client.py b/examples/network/http_client.py index 661c286b70bc4..b16f87a98187a 100644 --- a/examples/network/http_client.py +++ b/examples/network/http_client.py @@ -1,27 +1,52 @@ +# Very simple HTTP client example: +# - Connects to a server. +# - Sends a HTTP request. +# - Reads the result. +# +# This example works in both MicroPython and CPython. +# +# To implement an HTTP client using less code, use mip to install the requests package: +# https://github.com/micropython/micropython-lib/tree/master/python-ecosys/requests + import socket -def main(use_stream=False): - s = socket.socket() +# `addr_family` selects IPv4 vs IPv6: 0 means either, or use +# socket.AF_INET or socket.AF_INET6 to select a particular one. +def main(url, addr_family=0, use_stream=False): + # Split the given URL into components. + proto, _, host, path = url.split(b"/", 3) + assert proto == b"http:" - ai = socket.getaddrinfo("google.com", 80) + # Lookup the server address, for the given family and socket type. + ai = socket.getaddrinfo(host, 80, addr_family, socket.SOCK_STREAM) print("Address infos:", ai) - addr = ai[0][-1] + # Select the first address. + ai = ai[0] + + # Create a socket with the server's family, type and proto. + s = socket.socket(ai[0], ai[1], ai[2]) + + # Connect to the server. + addr = ai[-1] print("Connect address:", addr) s.connect(addr) + # Send request and read response. + request = b"GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host) if use_stream: # MicroPython socket objects support stream (aka file) interface # directly, but the line below is needed for CPython. s = s.makefile("rwb", 0) - s.write(b"GET / HTTP/1.0\r\n\r\n") + s.write(request) print(s.read()) else: - s.send(b"GET / HTTP/1.0\r\n\r\n") + s.send(request) print(s.recv(4096)) + # Close the socket. s.close() -main() +main(b"http://www.google.com/") diff --git a/examples/network/https_client.py b/examples/network/https_client.py index 323971c0ee24d..aaad9b657417b 100644 --- a/examples/network/https_client.py +++ b/examples/network/https_client.py @@ -1,32 +1,62 @@ +# Very simple HTTPS client example: +# - Connects to a server. +# - Upgrades the connection to a TLS connection. +# - Sends a HTTP request. +# - Reads the result. +# +# This example works in both MicroPython and CPython. +# +# To implement an HTTPS client using less code, use mip to install the requests package: +# https://github.com/micropython/micropython-lib/tree/master/python-ecosys/requests + import socket import ssl -def main(use_stream=True): - s = socket.socket() +# `addr_family` selects IPv4 vs IPv6: 0 means either, or use +# socket.AF_INET or socket.AF_INET6 to select a particular one. +def main(url, addr_family=0, use_stream=True): + # Split the given URL into components. + proto, _, host, path = url.split(b"/", 3) + assert proto == b"https:" - ai = socket.getaddrinfo("google.com", 443) + # Lookup the server address, for the given family and socket type. + ai = socket.getaddrinfo(host, 443, addr_family, socket.SOCK_STREAM) print("Address infos:", ai) - addr = ai[0][-1] + # Select the first address. + ai = ai[0] + + # Create a socket with the server's family, type and proto. + s = socket.socket(ai[0], ai[1], ai[2]) + + # Connect to the server. + addr = ai[-1] print("Connect address:", addr) s.connect(addr) - s = ssl.wrap_socket(s) + # Upgrade the socket to a TLS connection. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + s = ctx.wrap_socket(s) print(s) + # Send request and read response. + request = b"GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host) if use_stream: # Both CPython and MicroPython SSLSocket objects support read() and # write() methods. - s.write(b"GET / HTTP/1.0\r\n\r\n") + s.write(request) print(s.read(4096)) else: # MicroPython SSLSocket objects implement only stream interface, not # socket interface - s.send(b"GET / HTTP/1.0\r\n\r\n") + s.send(request) print(s.recv(4096)) + # Close the socket. s.close() -main() +main(b"https://www.google.com/") diff --git a/examples/network/https_client_nonblocking.py b/examples/network/https_client_nonblocking.py index 6e6b7f37bc7b1..41447e81e701b 100644 --- a/examples/network/https_client_nonblocking.py +++ b/examples/network/https_client_nonblocking.py @@ -37,13 +37,13 @@ def read_nonblocking(poller, sock, n): return data -def main(url): +def main(url, addr_family=0): # Split the given URL into components. proto, _, host, path = url.split(b"/", 3) assert proto == b"https:" # Note: this getaddrinfo() call is blocking! - ai = socket.getaddrinfo(host, 443)[0] + ai = socket.getaddrinfo(host, 443, addr_family, socket.SOCK_STREAM)[0] addr = ai[-1] print("Connect address:", addr) diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 60f1a23ad6b22..98e8a84608a56 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -49,6 +49,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/network_cyw43.c ${MICROPY_EXTMOD_DIR}/network_lwip.c ${MICROPY_EXTMOD_DIR}/network_ninaw10.c + ${MICROPY_EXTMOD_DIR}/network_ppp_lwip.c ${MICROPY_EXTMOD_DIR}/network_wiznet5k.c ${MICROPY_EXTMOD_DIR}/os_dupterm.c ${MICROPY_EXTMOD_DIR}/vfs.c @@ -275,11 +276,14 @@ if(MICROPY_PY_LWIP) target_sources(micropy_lib_lwip INTERFACE ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_LIB_LWIP_DIR}/apps/mdns/mdns.c + ${MICROPY_LIB_LWIP_DIR}/apps/mdns/mdns_domain.c + ${MICROPY_LIB_LWIP_DIR}/apps/mdns/mdns_out.c ${MICROPY_LIB_LWIP_DIR}/core/def.c ${MICROPY_LIB_LWIP_DIR}/core/dns.c ${MICROPY_LIB_LWIP_DIR}/core/inet_chksum.c ${MICROPY_LIB_LWIP_DIR}/core/init.c ${MICROPY_LIB_LWIP_DIR}/core/ip.c + ${MICROPY_LIB_LWIP_DIR}/core/ipv4/acd.c ${MICROPY_LIB_LWIP_DIR}/core/ipv4/autoip.c ${MICROPY_LIB_LWIP_DIR}/core/ipv4/dhcp.c ${MICROPY_LIB_LWIP_DIR}/core/ipv4/etharp.c @@ -310,6 +314,32 @@ if(MICROPY_PY_LWIP) ${MICROPY_LIB_LWIP_DIR}/core/timeouts.c ${MICROPY_LIB_LWIP_DIR}/core/udp.c ${MICROPY_LIB_LWIP_DIR}/netif/ethernet.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/auth.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/ccp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/chap-md5.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/chap_ms.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/chap-new.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/demand.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/eap.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/ecp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/eui64.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/fsm.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/ipcp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/ipv6cp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/lcp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/magic.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/mppe.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/multilink.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/polarssl/md5.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/pppapi.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/ppp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/pppcrypt.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/pppoe.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/pppol2tp.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/pppos.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/upap.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/utils.c + ${MICROPY_LIB_LWIP_DIR}/netif/ppp/vj.c ) list(APPEND MICROPY_INC_CORE diff --git a/extmod/extmod.mk b/extmod/extmod.mk index c2298bc52130f..c132fd89ce887 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -51,6 +51,7 @@ SRC_EXTMOD_C += \ extmod/network_esp_hosted.c \ extmod/network_lwip.c \ extmod/network_ninaw10.c \ + extmod/network_ppp_lwip.c \ extmod/network_wiznet5k.c \ extmod/os_dupterm.c \ extmod/vfs.c \ @@ -332,6 +333,8 @@ $(BUILD)/$(LWIP_DIR)/core/ipv4/dhcp.o: CFLAGS += -Wno-address SRC_THIRDPARTY_C += shared/netutils/netutils.c SRC_THIRDPARTY_C += $(addprefix $(LWIP_DIR)/,\ apps/mdns/mdns.c \ + apps/mdns/mdns_domain.c \ + apps/mdns/mdns_out.c \ core/def.c \ core/dns.c \ core/inet_chksum.c \ @@ -349,6 +352,7 @@ SRC_THIRDPARTY_C += $(addprefix $(LWIP_DIR)/,\ core/tcp_out.c \ core/timeouts.c \ core/udp.c \ + core/ipv4/acd.c \ core/ipv4/autoip.c \ core/ipv4/dhcp.c \ core/ipv4/etharp.c \ @@ -367,6 +371,32 @@ SRC_THIRDPARTY_C += $(addprefix $(LWIP_DIR)/,\ core/ipv6/mld6.c \ core/ipv6/nd6.c \ netif/ethernet.c \ + netif/ppp/auth.c \ + netif/ppp/ccp.c \ + netif/ppp/chap-md5.c \ + netif/ppp/chap_ms.c \ + netif/ppp/chap-new.c \ + netif/ppp/demand.c \ + netif/ppp/eap.c \ + netif/ppp/ecp.c \ + netif/ppp/eui64.c \ + netif/ppp/fsm.c \ + netif/ppp/ipcp.c \ + netif/ppp/ipv6cp.c \ + netif/ppp/lcp.c \ + netif/ppp/magic.c \ + netif/ppp/mppe.c \ + netif/ppp/multilink.c \ + netif/ppp/polarssl/md5.c \ + netif/ppp/pppapi.c \ + netif/ppp/ppp.c \ + netif/ppp/pppcrypt.c \ + netif/ppp/pppoe.c \ + netif/ppp/pppol2tp.c \ + netif/ppp/pppos.c \ + netif/ppp/upap.c \ + netif/ppp/utils.c \ + netif/ppp/vj.c \ ) ifeq ($(MICROPY_PY_LWIP_LOOPBACK),1) CFLAGS_EXTMOD += -DLWIP_NETIF_LOOPBACK=1 diff --git a/extmod/modnetwork.c b/extmod/modnetwork.c index f3d7d0faa8e1d..336836b6b8694 100644 --- a/extmod/modnetwork.c +++ b/extmod/modnetwork.c @@ -156,6 +156,10 @@ static const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&mod_network_ipconfig_obj) }, #endif + #if MICROPY_PY_NETWORK_PPP_LWIP + { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&mp_network_ppp_lwip_type) }, + #endif + // Defined per port in mpconfigport.h #ifdef MICROPY_PORT_NETWORK_INTERFACES { MP_ROM_QSTR(MP_QSTR_route), MP_ROM_PTR(&network_route_obj) }, diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index 1a4aa7797e455..7e5a283353724 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -72,6 +72,10 @@ mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args); #include "lwip/init.h" +#if MICROPY_PY_NETWORK_PPP_LWIP +extern const struct _mp_obj_type_t mp_network_ppp_lwip_type; +#endif + struct netif; void mod_network_lwip_init(void); void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index d3fc26fad2a54..30118e200dee0 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -100,11 +100,22 @@ static void mbedtls_debug(void *ctx, int level, const char *file, int line, cons } #endif -#if defined(MBEDTLS_PEM_PARSE_C) -static int mbedtls_is_pem(const byte *data, size_t len) { - return (len >= 10) && (strstr((const char *)data, "-----BEGIN") != NULL); +// Given a string-like object holding PEM or DER formatted ASN.1 data, return a +// pointer to its buffer and the correct length for mbedTLS APIs. +// +// (mbedTLS >= 3.5 rejects DER formatted data with trailing bytes within keylen, +// but PEM must include a terminating NUL byte in the keylen...) +static const unsigned char *asn1_get_data(mp_obj_t obj, size_t *out_len) { + size_t len; + const char *str = mp_obj_str_get_data(obj, &len); + #if defined(MBEDTLS_PEM_PARSE_C) + if (strstr(str, "-----BEGIN ") != NULL) { + ++len; + } + #endif + *out_len = len; + return (const unsigned char *)str; } -#endif static NORETURN void mbedtls_raise_error(int err) { // Handle special cases. @@ -352,15 +363,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_set_ciphers_obj, ssl_context_set_ci static void ssl_context_load_key(mp_obj_ssl_context_t *self, mp_obj_t key_obj, mp_obj_t cert_obj) { size_t key_len; - const byte *key = (const byte *)mp_obj_str_get_data(key_obj, &key_len); - - #if defined(MBEDTLS_PEM_PARSE_C) - // len should include terminating null if the data is PEM encoded - if (mbedtls_is_pem(key, key_len)) { - key_len += 1; - } - #endif - + const unsigned char *key = asn1_get_data(key_obj, &key_len); int ret; #if MBEDTLS_VERSION_NUMBER >= 0x03000000 ret = mbedtls_pk_parse_key(&self->pkey, key, key_len, NULL, 0, mbedtls_ctr_drbg_random, &self->ctr_drbg); @@ -372,15 +375,7 @@ static void ssl_context_load_key(mp_obj_ssl_context_t *self, mp_obj_t key_obj, m } size_t cert_len; - const byte *cert = (const byte *)mp_obj_str_get_data(cert_obj, &cert_len); - - #if defined(MBEDTLS_PEM_PARSE_C) - // len should include terminating null if the data is PEM encoded - if (mbedtls_is_pem(cert, cert_len)) { - cert_len += 1; - } - #endif - + const unsigned char *cert = asn1_get_data(cert_obj, &cert_len); ret = mbedtls_x509_crt_parse(&self->cert, cert, cert_len); if (ret != 0) { mbedtls_raise_error(MBEDTLS_ERR_X509_BAD_INPUT_DATA); // use general error for all cert errors @@ -402,15 +397,7 @@ static MP_DEFINE_CONST_FUN_OBJ_3(ssl_context_load_cert_chain_obj, ssl_context_lo static void ssl_context_load_cadata(mp_obj_ssl_context_t *self, mp_obj_t cadata_obj) { size_t cacert_len; - const byte *cacert = (const byte *)mp_obj_str_get_data(cadata_obj, &cacert_len); - - #if defined(MBEDTLS_PEM_PARSE_C) - // len should include terminating null if the data is PEM encoded - if (mbedtls_is_pem(cacert, cacert_len)) { - cacert_len += 1; - } - #endif - + const unsigned char *cacert = asn1_get_data(cadata_obj, &cacert_len); int ret = mbedtls_x509_crt_parse(&self->cacert, cacert, cacert_len); if (ret != 0) { mbedtls_raise_error(MBEDTLS_ERR_X509_BAD_INPUT_DATA); // use general error for all cert errors diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c new file mode 100644 index 0000000000000..2b77662a24f26 --- /dev/null +++ b/extmod/network_ppp_lwip.c @@ -0,0 +1,326 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Damien P. George + * + * 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 "py/runtime.h" +#include "py/mphal.h" +#include "py/stream.h" +#include "extmod/modnetwork.h" + +#if MICROPY_PY_NETWORK_PPP_LWIP + +#include "lwip/dns.h" +#include "netif/ppp/ppp.h" +#include "netif/ppp/pppapi.h" +#include "netif/ppp/pppos.h" + +// Enable this to see the serial data going between the PPP layer. +#define PPP_TRACE_IN_OUT (0) + +typedef enum { + STATE_INACTIVE, + STATE_ACTIVE, + STATE_ERROR, + STATE_CONNECTING, + STATE_CONNECTED, +} network_ppp_state_t; + +typedef struct _network_ppp_obj_t { + mp_obj_base_t base; + network_ppp_state_t state; + int error_code; + mp_obj_t stream; + ppp_pcb *pcb; + struct netif netif; +} network_ppp_obj_t; + +const mp_obj_type_t mp_network_ppp_lwip_type; + +static mp_obj_t network_ppp___del__(mp_obj_t self_in); + +static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { + network_ppp_obj_t *self = ctx; + switch (err_code) { + case PPPERR_NONE: + self->state = STATE_CONNECTED; + break; + case PPPERR_USER: + if (self->state >= STATE_ERROR) { + // Disable UART IRQ. + mp_obj_t dest[3]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_const_none; + mp_call_method_n_kw(1, 0, dest); + // Indicate that the IRQ is disabled. + self->state = STATE_ACTIVE; + } + // Clean up the PPP PCB. + network_ppp___del__(MP_OBJ_FROM_PTR(self)); + break; + default: + self->state = STATE_ERROR; + self->error_code = err_code; + break; + } +} + +static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_t stream = all_args[0]; + + mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + + network_ppp_obj_t *self = mp_obj_malloc_with_finaliser(network_ppp_obj_t, type); + self->state = STATE_INACTIVE; + self->stream = stream; + self->pcb = NULL; + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t network_ppp___del__(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state >= STATE_ACTIVE) { + if (self->state >= STATE_ERROR) { + // Still connected over the UART stream. + // Force the connection to close, with nocarrier=1. + self->state = STATE_INACTIVE; + ppp_close(self->pcb, 1); + } + // Free PPP PCB and reset state. + self->state = STATE_INACTIVE; + ppp_free(self->pcb); + self->pcb = NULL; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp___del___obj, network_ppp___del__); + +static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->state <= STATE_ERROR) { + return MP_OBJ_NEW_SMALL_INT(-MP_EPERM); + } + + mp_int_t total_len = 0; + for (;;) { + uint8_t buf[256]; + int err; + mp_uint_t len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); + if (len == 0) { + break; + } + #if PPP_TRACE_IN_OUT + mp_printf(&mp_plat_print, "ppp_in(n=%u,data=", len); + for (size_t i = 0; i < len; ++i) { + mp_printf(&mp_plat_print, "%02x:", buf[i]); + } + mp_printf(&mp_plat_print, ")\n"); + #endif + pppos_input(self->pcb, (u8_t *)buf, len); + total_len += len; + } + + return MP_OBJ_NEW_SMALL_INT(total_len); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_poll_obj, 1, 2, network_ppp_poll); + +static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + // network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used != 0) { + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + default: + break; + } + } + } + return mp_const_none; + } + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } + + return val; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_config_obj, 1, network_ppp_config); + +static mp_obj_t network_ppp_status(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state == STATE_ERROR) { + return MP_OBJ_NEW_SMALL_INT(-self->error_code); + } else { + return MP_OBJ_NEW_SMALL_INT(self->state); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_status_obj, network_ppp_status); + +static u32_t network_ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) { + network_ppp_obj_t *self = ctx; + #if PPP_TRACE_IN_OUT + mp_printf(&mp_plat_print, "ppp_out(n=%u,data=", len); + for (size_t i = 0; i < len; ++i) { + mp_printf(&mp_plat_print, "%02x:", ((const uint8_t *)data)[i]); + } + mp_printf(&mp_plat_print, ")\n"); + #endif + int err; + // The return value from this output callback is the number of bytes written out. + // If it's less than the requested number of bytes then lwIP will propagate out an error. + return mp_stream_rw(self->stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); +} + +static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + enum { ARG_security, ARG_user, ARG_key }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PPPAUTHTYPE_NONE} }, + { MP_QSTR_user, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t parsed_args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, parsed_args); + + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->state == STATE_INACTIVE) { + self->pcb = pppos_create(&self->netif, network_ppp_output_callback, network_ppp_status_cb, self); + if (self->pcb == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("pppos_create failed")); + } + self->state = STATE_ACTIVE; + + // Enable UART IRQ to call PPP.poll() when incoming data is ready. + mp_obj_t dest[4]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&network_ppp_poll_obj), MP_OBJ_FROM_PTR(self)); + dest[3] = mp_load_attr(self->stream, MP_QSTR_IRQ_RXIDLE); + mp_call_method_n_kw(2, 0, dest); + } + + if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { + mp_raise_OSError(MP_EALREADY); + } + + switch (parsed_args[ARG_security].u_int) { + case PPPAUTHTYPE_NONE: + case PPPAUTHTYPE_PAP: + case PPPAUTHTYPE_CHAP: + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid auth")); + } + + if (parsed_args[ARG_security].u_int != PPPAUTHTYPE_NONE) { + const char *user_str = mp_obj_str_get_str(parsed_args[ARG_user].u_obj); + const char *key_str = mp_obj_str_get_str(parsed_args[ARG_key].u_obj); + ppp_set_auth(self->pcb, parsed_args[ARG_security].u_int, user_str, key_str); + } + + netif_set_default(self->pcb->netif); + ppp_set_usepeerdns(self->pcb, true); + + if (ppp_connect(self->pcb, 0) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_connect failed")); + } + + self->state = STATE_CONNECTING; + + // Do a poll in case there is data waiting on the input stream. + network_ppp_poll(1, args); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_connect_obj, 1, network_ppp_connect); + +static mp_obj_t network_ppp_disconnect(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { + // Initiate close and wait for PPPERR_USER callback. + ppp_close(self->pcb, 0); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_disconnect_obj, network_ppp_disconnect); + +static mp_obj_t network_ppp_isconnected(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->state == STATE_CONNECTED); +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_isconnected_obj, network_ppp_isconnected); + +static mp_obj_t network_ppp_ifconfig(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ifconfig(&self->netif, n_args - 1, args + 1); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_ifconfig_obj, 1, 2, network_ppp_ifconfig); + +static mp_obj_t network_ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_ipconfig_obj, 1, network_ppp_ipconfig); + +static const mp_rom_map_elem_t network_ppp_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&network_ppp___del___obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_ppp_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_ppp_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_ppp_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_ppp_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_ppp_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_ppp_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_ppp_ipconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&network_ppp_poll_obj) }, + + { MP_ROM_QSTR(MP_QSTR_SEC_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, +}; +static MP_DEFINE_CONST_DICT(network_ppp_locals_dict, network_ppp_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_network_ppp_lwip_type, + MP_QSTR_PPP, + MP_TYPE_FLAG_NONE, + make_new, network_ppp_make_new, + locals_dict, &network_ppp_locals_dict + ); + +#endif diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index 1eaabe9ebeac3..96f9b6131c4b7 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -61,6 +61,7 @@ #include "lwip/err.h" #include "lwip/dns.h" #include "lwip/dhcp.h" +#include "lwip/ethip6.h" #include "netif/etharp.h" #define TRACE_ETH_TX (0x0002) @@ -297,13 +298,21 @@ static err_t wiznet5k_netif_init(struct netif *netif) { netif->hwaddr_len = sizeof(netif->hwaddr); int ret = WIZCHIP_EXPORT(socket)(0, Sn_MR_MACRAW, 0, 0); if (ret != 0) { - printf("WIZNET fatal error in netifinit: %d\n", ret); + printf("WIZNET fatal error in netif_init: %d\n", ret); return ERR_IF; } // Enable MAC filtering so we only get frames destined for us, to reduce load on lwIP setSn_MR(0, getSn_MR(0) | Sn_MR_MFEN); + #if LWIP_IPV6 + netif->output_ip6 = ethip6_output; + netif->flags |= NETIF_FLAG_MLD6; + #else + // Drop IPv6 packets if firmware does not support it + setSn_MR(0, getSn_MR(0) | Sn_MR_MIP6B); + #endif + return ERR_OK; } @@ -847,6 +856,10 @@ static mp_obj_t wiznet5k_active(size_t n_args, const mp_obj_t *args) { setSHAR(mac); } + #if WIZNET5K_WITH_LWIP_STACK && LWIP_IPV6 + netif_create_ip6_linklocal_address(&self->netif, 1); + #endif + // seems we need a small delay after init mp_hal_delay_ms(250); diff --git a/lib/lwip b/lib/lwip index 6ca936f6b588c..0a0452b2c39bd 160000 --- a/lib/lwip +++ b/lib/lwip @@ -1 +1 @@ -Subproject commit 6ca936f6b588cee702c638eee75c2436e6cf75de +Subproject commit 0a0452b2c39bdd91e252aef045c115f88f6ca773 diff --git a/ports/cc3200/mods/pybuart.c b/ports/cc3200/mods/pybuart.c index 6ab2371ba708f..eb2b3754f7473 100644 --- a/ports/cc3200/mods/pybuart.c +++ b/ports/cc3200/mods/pybuart.c @@ -590,6 +590,7 @@ static const mp_rom_map_elem_t pyb_uart_locals_dict_table[] = { // class constants { MP_ROM_QSTR(MP_QSTR_RX_ANY), MP_ROM_INT(UART_TRIGGER_RX_ANY) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_TRIGGER_RX_ANY) }, }; static MP_DEFINE_CONST_DICT(pyb_uart_locals_dict, pyb_uart_locals_dict_table); diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index e928fb439da5e..7c5089639a5f2 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -32,6 +32,7 @@ list(APPEND MICROPY_SOURCE_SHARED ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/timeutils/timeutils.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/stdout_helpers.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c ${MICROPY_DIR}/shared/runtime/pyexec.c diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 011f87ba9eb30..278deb10649d4 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -38,6 +38,7 @@ #include "hal/timer_hal.h" #include "hal/timer_ll.h" #include "soc/timer_periph.h" +#include "machine_timer.h" #define TIMER_DIVIDER 8 @@ -46,27 +47,8 @@ #define TIMER_FLAGS 0 -typedef struct _machine_timer_obj_t { - mp_obj_base_t base; - - timer_hal_context_t hal_context; - mp_uint_t group; - mp_uint_t index; - - mp_uint_t repeat; - // ESP32 timers are 64 or 54-bit - uint64_t period; - - mp_obj_t callback; - - intr_handle_t handle; - - struct _machine_timer_obj_t *next; -} machine_timer_obj_t; - const mp_obj_type_t machine_timer_type; -static void machine_timer_disable(machine_timer_obj_t *self); static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); void machine_timer_deinit_all(void) { @@ -91,18 +73,17 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr #endif } -static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); +machine_timer_obj_t *machine_timer_create(mp_uint_t timer) { + + machine_timer_obj_t *self = NULL; #if CONFIG_IDF_TARGET_ESP32C3 - mp_uint_t group = mp_obj_get_int(args[0]) & 1; + mp_uint_t group = timer & 1; mp_uint_t index = 0; #else - mp_uint_t group = (mp_obj_get_int(args[0]) >> 1) & 1; - mp_uint_t index = mp_obj_get_int(args[0]) & 1; + mp_uint_t group = (timer >> 1) & 1; + mp_uint_t index = timer & 1; #endif - machine_timer_obj_t *self = NULL; - // Check whether the timer is already initialized, if so use it for (machine_timer_obj_t *t = MP_STATE_PORT(machine_timer_obj_head); t; t = t->next) { if (t->group == group && t->index == index) { @@ -120,6 +101,14 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, self->next = MP_STATE_PORT(machine_timer_obj_head); MP_STATE_PORT(machine_timer_obj_head) = self; } + return self; +} + +static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Create the new timer. + machine_timer_obj_t *self = machine_timer_create(mp_obj_get_int(args[0])); if (n_args > 1 || n_kw > 0) { mp_map_t kw_args; @@ -130,7 +119,7 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, return self; } -static void machine_timer_disable(machine_timer_obj_t *self) { +void machine_timer_disable(machine_timer_obj_t *self) { if (self->hal_context.dev != NULL) { // Disable the counter and alarm. timer_ll_enable_counter(self->hal_context.dev, self->index, false); @@ -162,7 +151,7 @@ static void machine_timer_isr(void *self_in) { } } -static void machine_timer_enable(machine_timer_obj_t *self) { +void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { // Initialise the timer. timer_hal_init(&self->hal_context, self->group, self->index); timer_ll_enable_counter(self->hal_context.dev, self->index, false); @@ -176,7 +165,7 @@ static void machine_timer_enable(machine_timer_obj_t *self) { timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); ESP_ERROR_CHECK( esp_intr_alloc(timer_group_periph_signals.groups[self->group].timer_irq_id[self->index], - TIMER_FLAGS, machine_timer_isr, self, &self->handle) + TIMER_FLAGS, timer_isr, self, &self->handle) ); timer_ll_enable_intr(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index), true); @@ -234,7 +223,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n self->callback = args[ARG_callback].u_obj; self->handle = NULL; - machine_timer_enable(self); + machine_timer_enable(self, machine_timer_isr); return mp_const_none; } diff --git a/ports/esp32/machine_timer.h b/ports/esp32/machine_timer.h new file mode 100644 index 0000000000000..914bedd86baa9 --- /dev/null +++ b/ports/esp32/machine_timer.h @@ -0,0 +1,66 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H +#define MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H + +#include "hal/timer_hal.h" +#include "hal/timer_ll.h" +#include "soc/timer_periph.h" + +#define TIMER_DIVIDER 8 + +// TIMER_BASE_CLK is normally 80MHz. TIMER_DIVIDER ought to divide this exactly +#define TIMER_SCALE (APB_CLK_FREQ / TIMER_DIVIDER) + +#define TIMER_FLAGS 0 + +typedef struct _machine_timer_obj_t { + mp_obj_base_t base; + + timer_hal_context_t hal_context; + mp_uint_t group; + mp_uint_t index; + + mp_uint_t repeat; + // ESP32 timers are 64-bit + uint64_t period; + + mp_obj_t callback; + + intr_handle_t handle; + + struct _machine_timer_obj_t *next; +} machine_timer_obj_t; + +machine_timer_obj_t *machine_timer_create(mp_uint_t timer); +void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)); +void machine_timer_disable(machine_timer_obj_t *self); + +#endif // MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 50c9a19bef1bb..d260c45c48f80 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -29,12 +29,17 @@ #include "driver/uart.h" #include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_task.h" +#include "shared/runtime/mpirq.h" #include "py/runtime.h" #include "py/stream.h" #include "py/mperrno.h" #include "py/mphal.h" #include "uart.h" +#include "machine_timer.h" #if SOC_UART_SUPPORT_XTAL_CLK // Works independently of APB frequency, on ESP32C3, ESP32S3. @@ -49,6 +54,18 @@ #define UART_INV_CTS UART_SIGNAL_CTS_INV #define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) +#define UART_IRQ_RX (1 << UART_DATA) +#define UART_IRQ_RXIDLE (0x1000) +#define UART_IRQ_BREAK (1 << UART_BREAK) +#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_RXIDLE | UART_IRQ_BREAK) +#define RXIDLE_TIMER_MIN (5000) // 500 us + +enum { + RXIDLE_INACTIVE, + RXIDLE_STANDBY, + RXIDLE_ARMED, + RXIDLE_ALERT, +}; typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -66,6 +83,14 @@ typedef struct _machine_uart_obj_t { uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms) uint32_t invert; // lines to invert + TaskHandle_t uart_event_task; + QueueHandle_t uart_queue; + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object + machine_timer_obj_t *rxidle_timer; + uint8_t rxidle_state; + uint16_t rxidle_period; } machine_uart_obj_t; static const char *_parity_name[] = {"None", "1", "0"}; @@ -80,14 +105,82 @@ static const char *_parity_name[] = {"None", "1", "0"}; { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INV_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HW_FLOWCTRL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HW_FLOWCTRL_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_IRQ_BREAK) }, \ + +static void uart_timer_callback(void *self_in) { + machine_timer_obj_t *self = self_in; + + uint32_t intr_status = timer_ll_get_intr_status(self->hal_context.dev); + + if (intr_status & TIMER_LL_EVENT_ALARM(self->index)) { + timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); + if (self->repeat) { + timer_ll_enable_alarm(self->hal_context.dev, self->index, true); + } + } + + // The UART object is referred here by the callback field. + machine_uart_obj_t *uart = (machine_uart_obj_t *)self->callback; + if (uart->rxidle_state == RXIDLE_ALERT) { + // At the first call, just switch the state + uart->rxidle_state = RXIDLE_ARMED; + } else if (uart->rxidle_state == RXIDLE_ARMED) { + // At the second call, run the irq callback and stop the timer + uart->rxidle_state = RXIDLE_STANDBY; + uart->mp_irq_flags = UART_IRQ_RXIDLE; + mp_irq_handler(uart->mp_irq_obj); + mp_hal_wake_main_task_from_isr(); + machine_timer_disable(uart->rxidle_timer); + } +} + +static void uart_event_task(void *self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_event_t event; + for (;;) { + // Waiting for an UART event. + if (xQueueReceive(self->uart_queue, (void *)&event, (TickType_t)portMAX_DELAY)) { + uint16_t mp_irq_flags = 0; + switch (event.type) { + // Event of UART receiving data + case UART_DATA: + if (self->mp_irq_trigger & UART_IRQ_RXIDLE) { + if (self->rxidle_state != RXIDLE_INACTIVE) { + if (self->rxidle_state == RXIDLE_STANDBY) { + self->rxidle_timer->repeat = true; + self->rxidle_timer->handle = NULL; + machine_timer_enable(self->rxidle_timer, uart_timer_callback); + } + } + self->rxidle_state = RXIDLE_ALERT; + } + mp_irq_flags |= UART_IRQ_RX; + break; + case UART_BREAK: + mp_irq_flags |= UART_IRQ_BREAK; + break; + default: + break; + } + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); + mp_hal_wake_main_task_from_isr(); + } + } + } +} static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t baudrate; check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); - mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u", + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u, irq=%d", self->uart_num, baudrate, self->bits, _parity_name[self->parity], - self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char); + self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char, self->mp_irq_trigger); if (self->invert) { mp_printf(print, ", invert="); uint32_t invert_mask = self->invert; @@ -348,6 +441,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout_char = 0; self->invert = 0; self->flowcontrol = 0; + self->uart_event_task = 0; + self->rxidle_state = RXIDLE_INACTIVE; switch (uart_num) { case UART_NUM_0: @@ -378,7 +473,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // Setup check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 0, NULL, 0)); + check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 3, &self->uart_queue, 0)); } mp_map_t kw_args; @@ -422,6 +517,96 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { check_esp_err(uart_set_baudrate(self->uart_num, baudrate)); } +// Configure the timer used for IRQ_RXIDLE +static void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger) { + + self->rxidle_state = RXIDLE_INACTIVE; + + if (trigger & UART_IRQ_RXIDLE) { + // The RXIDLE event is always a soft IRQ. + self->mp_irq_obj->ishard = false; + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + mp_int_t period = TIMER_SCALE * 20 / baudrate + 1; + if (period < RXIDLE_TIMER_MIN) { + period = RXIDLE_TIMER_MIN; + } + self->rxidle_period = period; + self->rxidle_timer->period = period; + // The Python callback is not used. So use this + // data field to hold a reference to the UART object. + self->rxidle_timer->callback = self; + self->rxidle_timer->repeat = true; + self->rxidle_timer->handle = NULL; + self->rxidle_state = RXIDLE_STANDBY; + } +} + +static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + uart_irq_configure_timer(self, new_trigger); + self->mp_irq_trigger = new_trigger; + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + if (args[MP_IRQ_ARG_INIT_hard].u_bool) { + mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ is not supported")); + } + self->mp_irq_obj->ishard = false; + self->mp_irq_trigger = trigger; + self->rxidle_timer = machine_timer_create(0); + uart_irq_configure_timer(self, trigger); + + // Start a task for handling events + if (handler != mp_const_none && self->uart_event_task == NULL) { + xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, + ESP_TASKD_EVENT_PRIO, (TaskHandle_t *)&self->uart_event_task, MP_TASK_COREID); + } else if (handler == mp_const_none && self->uart_event_task != NULL) { + vTaskDelete(self->uart_event_task); + self->uart_event_task = NULL; + } + } + + return self->mp_irq_obj; +} + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 5051afb799ead..a5c6b9c014492 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -155,6 +155,7 @@ #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/esp32/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/esp32/machine_wdt.c" #define MICROPY_PY_NETWORK (1) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 75cf9caec962c..e98073d33626d 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -132,7 +132,6 @@ SRC_HAL_IMX_C += \ $(MCU_DIR)/drivers/fsl_lpi2c.c \ $(MCU_DIR)/drivers/fsl_lpspi.c \ $(MCU_DIR)/drivers/fsl_lpspi_edma.c \ - $(MCU_DIR)/drivers/fsl_lpuart.c \ $(MCU_DIR)/drivers/fsl_pit.c \ $(MCU_DIR)/drivers/fsl_pwm.c \ $(MCU_DIR)/drivers/fsl_sai.c \ @@ -191,6 +190,7 @@ SRC_C += \ eth.c \ fatfs_port.c \ flash.c \ + hal/fsl_lpuart.c \ hal/pwm_backport.c \ help.c \ led.c \ diff --git a/ports/mimxrt/hal/fsl_lpuart.c b/ports/mimxrt/hal/fsl_lpuart.c new file mode 100644 index 0000000000000..54651c3efbcd4 --- /dev/null +++ b/ports/mimxrt/hal/fsl_lpuart.c @@ -0,0 +1,2013 @@ +/* + * Copyright (c) 2015-2016, Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "fsl_lpuart.h" + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.lpuart" +#endif + +/* LPUART transfer state. */ +enum +{ + kLPUART_TxIdle, /*!< TX idle. */ + kLPUART_TxBusy, /*!< TX busy. */ + kLPUART_RxIdle, /*!< RX idle. */ + kLPUART_RxBusy /*!< RX busy. */ +}; + +/******************************************************************************* + * Prototypes + ******************************************************************************/ +/*! + * @brief Check whether the RX ring buffer is full. + * + * @userData handle LPUART handle pointer. + * @retval true RX ring buffer is full. + * @retval false RX ring buffer is not full. + */ +static bool LPUART_TransferIsRxRingBufferFull(LPUART_Type *base, lpuart_handle_t *handle); + +/*! + * @brief Write to TX register using non-blocking method. + * + * This function writes data to the TX register directly, upper layer must make + * sure the TX register is empty or TX FIFO has empty room before calling this function. + * + * @note This function does not check whether all the data has been sent out to bus, + * so before disable TX, check kLPUART_TransmissionCompleteFlag to ensure the TX is + * finished. + * + * @param base LPUART peripheral base address. + * @param data Start address of the data to write. + * @param length Size of the buffer to be sent. + */ +static void LPUART_WriteNonBlocking(LPUART_Type *base, const uint8_t *data, size_t length); + +/*! + * @brief Read RX register using non-blocking method. + * + * This function reads data from the TX register directly, upper layer must make + * sure the RX register is full or TX FIFO has data before calling this function. + * + * @param base LPUART peripheral base address. + * @param data Start address of the buffer to store the received data. + * @param length Size of the buffer. + */ +static void LPUART_ReadNonBlocking(LPUART_Type *base, uint8_t *data, size_t length); + +/******************************************************************************* + * Variables + ******************************************************************************/ +/* Array of LPUART peripheral base address. */ +static LPUART_Type *const s_lpuartBases[] = LPUART_BASE_PTRS; +/* Array of LPUART handle. */ +void *s_lpuartHandle[ARRAY_SIZE(s_lpuartBases)]; +/* Array of LPUART IRQ number. */ +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +static const IRQn_Type s_lpuartRxIRQ[] = LPUART_RX_IRQS; +const IRQn_Type s_lpuartTxIRQ[] = LPUART_TX_IRQS; +#else +const IRQn_Type s_lpuartIRQ[] = LPUART_RX_TX_IRQS; +#endif +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) +/* Array of LPUART clock name. */ +static const clock_ip_name_t s_lpuartClock[] = LPUART_CLOCKS; + +#if defined(LPUART_PERIPH_CLOCKS) +/* Array of LPUART functional clock name. */ +static const clock_ip_name_t s_lpuartPeriphClocks[] = LPUART_PERIPH_CLOCKS; +#endif + +#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ + +/* LPUART ISR for transactional APIs. */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) +lpuart_isr_t s_lpuartIsr = (lpuart_isr_t)DefaultISR; +#else +lpuart_isr_t s_lpuartIsr; +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +/*! + * brief Get the LPUART instance from peripheral base address. + * + * param base LPUART peripheral base address. + * return LPUART instance. + */ +uint32_t LPUART_GetInstance(LPUART_Type *base) { + uint32_t instance; + + /* Find the instance index from base address mappings. */ + for (instance = 0U; instance < ARRAY_SIZE(s_lpuartBases); instance++) { + if (s_lpuartBases[instance] == base) { + break; + } + } + + assert(instance < ARRAY_SIZE(s_lpuartBases)); + + return instance; +} + +/*! + * brief Get the length of received data in RX ring buffer. + * + * userData handle LPUART handle pointer. + * return Length of received data in RX ring buffer. + */ +size_t LPUART_TransferGetRxRingBufferLength(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + size_t size; + size_t tmpRxRingBufferSize = handle->rxRingBufferSize; + uint16_t tmpRxRingBufferTail = handle->rxRingBufferTail; + uint16_t tmpRxRingBufferHead = handle->rxRingBufferHead; + + if (tmpRxRingBufferTail > tmpRxRingBufferHead) { + size = ((size_t)tmpRxRingBufferHead + tmpRxRingBufferSize - (size_t)tmpRxRingBufferTail); + } else { + size = ((size_t)tmpRxRingBufferHead - (size_t)tmpRxRingBufferTail); + } + + return size; +} + +static bool LPUART_TransferIsRxRingBufferFull(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + bool full; + + if (LPUART_TransferGetRxRingBufferLength(base, handle) == (handle->rxRingBufferSize - 1U)) { + full = true; + } else { + full = false; + } + return full; +} + +static void LPUART_WriteNonBlocking(LPUART_Type *base, const uint8_t *data, size_t length) { + assert(NULL != data); + + size_t i; + + /* The Non Blocking write data API assume user have ensured there is enough space in + peripheral to write. */ + for (i = 0; i < length; i++) { + base->DATA = data[i]; + } +} + +static void LPUART_ReadNonBlocking(LPUART_Type *base, uint8_t *data, size_t length) { + assert(NULL != data); + + size_t i; + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + /* The Non Blocking read data API assume user have ensured there is enough space in + peripheral to write. */ + for (i = 0; i < length; i++) { + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (isSevenDataBits) { + data[i] = (uint8_t)(base->DATA & 0x7FU); + } else { + data[i] = (uint8_t)base->DATA; + } + #else + data[i] = (uint8_t)(base->DATA); + #endif + } +} + +/*! + * brief Initializes an LPUART instance with the user configuration structure and the peripheral clock. + * + * This function configures the LPUART module with user-defined settings. Call the LPUART_GetDefaultConfig() function + * to configure the configuration structure and get the default configuration. + * The example below shows how to use this API to configure the LPUART. + * code + * lpuart_config_t lpuartConfig; + * lpuartConfig.baudRate_Bps = 115200U; + * lpuartConfig.parityMode = kLPUART_ParityDisabled; + * lpuartConfig.dataBitsCount = kLPUART_EightDataBits; + * lpuartConfig.isMsb = false; + * lpuartConfig.stopBitCount = kLPUART_OneStopBit; + * lpuartConfig.txFifoWatermark = 0; + * lpuartConfig.rxFifoWatermark = 1; + * LPUART_Init(LPUART1, &lpuartConfig, 20000000U); + * endcode + * + * param base LPUART peripheral base address. + * param config Pointer to a user-defined configuration structure. + * param srcClock_Hz LPUART clock source frequency in HZ. + * retval kStatus_LPUART_BaudrateNotSupport Baudrate is not support in current clock source. + * retval kStatus_Success LPUART initialize succeed + */ +status_t LPUART_Init(LPUART_Type *base, const lpuart_config_t *config, uint32_t srcClock_Hz) { + assert(NULL != config); + assert(0U < config->baudRate_Bps); + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + assert((uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) >= config->txFifoWatermark); + assert((uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) >= config->rxFifoWatermark); + #endif + + status_t status = kStatus_Success; + uint32_t temp; + uint16_t sbr, sbrTemp; + uint8_t osr, osrTemp; + uint32_t tempDiff, calculatedBaud, baudDiff; + + /* This LPUART instantiation uses a slightly different baud rate calculation + * The idea is to use the best OSR (over-sampling rate) possible + * Note, OSR is typically hard-set to 16 in other LPUART instantiations + * loop to find the best OSR value possible, one that generates minimum baudDiff + * iterate through the rest of the supported values of OSR */ + + baudDiff = config->baudRate_Bps; + osr = 0U; + sbr = 0U; + for (osrTemp = 4U; osrTemp <= 32U; osrTemp++) { + /* calculate the temporary sbr value */ + sbrTemp = (uint16_t)((srcClock_Hz * 10U / (config->baudRate_Bps * (uint32_t)osrTemp) + 5U) / 10U); + /*set sbrTemp to 1 if the sourceClockInHz can not satisfy the desired baud rate*/ + if (sbrTemp == 0U) { + sbrTemp = 1U; + } + /* Calculate the baud rate based on the temporary OSR and SBR values */ + calculatedBaud = (srcClock_Hz / ((uint32_t)osrTemp * (uint32_t)sbrTemp)); + tempDiff = calculatedBaud > config->baudRate_Bps ? (calculatedBaud - config->baudRate_Bps) : + (config->baudRate_Bps - calculatedBaud); + + if (tempDiff <= baudDiff) { + baudDiff = tempDiff; + osr = osrTemp; /* update and store the best OSR value calculated */ + sbr = sbrTemp; /* update store the best SBR value calculated */ + } + } + + /* Check to see if actual baud rate is within 3% of desired baud rate + * based on the best calculate OSR value */ + if (baudDiff > ((config->baudRate_Bps / 100U) * 3U)) { + /* Unacceptable baud rate difference of more than 3%*/ + status = kStatus_LPUART_BaudrateNotSupport; + } else { + #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + + uint32_t instance = LPUART_GetInstance(base); + + /* Enable lpuart clock */ + (void)CLOCK_EnableClock(s_lpuartClock[instance]); + #if defined(LPUART_PERIPH_CLOCKS) + (void)CLOCK_EnableClock(s_lpuartPeriphClocks[instance]); + #endif + + #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ + + #if defined(FSL_FEATURE_LPUART_HAS_GLOBAL) && FSL_FEATURE_LPUART_HAS_GLOBAL + /*Reset all internal logic and registers, except the Global Register */ + LPUART_SoftwareReset(base); + #else + /* Disable LPUART TX RX before setting. */ + base->CTRL &= ~(LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK); + #endif + + temp = base->BAUD; + + /* Acceptable baud rate, check if OSR is between 4x and 7x oversampling. + * If so, then "BOTHEDGE" sampling must be turned on */ + if ((osr > 3U) && (osr < 8U)) { + temp |= LPUART_BAUD_BOTHEDGE_MASK; + } + + /* program the osr value (bit value is one less than actual value) */ + temp &= ~LPUART_BAUD_OSR_MASK; + temp |= LPUART_BAUD_OSR((uint32_t)osr - 1UL); + + /* write the sbr value to the BAUD registers */ + temp &= ~LPUART_BAUD_SBR_MASK; + base->BAUD = temp | LPUART_BAUD_SBR(sbr); + + /* Set bit count and parity mode. */ + base->BAUD &= ~LPUART_BAUD_M10_MASK; + + temp = base->CTRL & ~(LPUART_CTRL_PE_MASK | LPUART_CTRL_PT_MASK | LPUART_CTRL_M_MASK | LPUART_CTRL_ILT_MASK | + LPUART_CTRL_IDLECFG_MASK); + + temp |= (uint8_t)config->parityMode | LPUART_CTRL_IDLECFG(config->rxIdleConfig) | + LPUART_CTRL_ILT(config->rxIdleType); + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (kLPUART_SevenDataBits == config->dataBitsCount) { + if (kLPUART_ParityDisabled != config->parityMode) { + temp &= ~LPUART_CTRL_M7_MASK; /* Seven data bits and one parity bit */ + } else { + temp |= LPUART_CTRL_M7_MASK; + } + } else + #endif + { + if (kLPUART_ParityDisabled != config->parityMode) { + temp |= LPUART_CTRL_M_MASK; /* Eight data bits and one parity bit */ + } + } + + base->CTRL = temp; + + #if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT + /* set stop bit per char */ + temp = base->BAUD & ~LPUART_BAUD_SBNS_MASK; + base->BAUD = temp | LPUART_BAUD_SBNS((uint8_t)config->stopBitCount); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Set tx/rx WATER watermark + Note: + Take care of the RX FIFO, RX interrupt request only assert when received bytes + equal or more than RX water mark, there is potential issue if RX water + mark larger than 1. + For example, if RX FIFO water mark is 2, upper layer needs 5 bytes and + 5 bytes are received. the last byte will be saved in FIFO but not trigger + RX interrupt because the water mark is 2. + */ + base->WATER = (((uint32_t)(config->rxFifoWatermark) << 16U) | config->txFifoWatermark); + + /* Enable tx/rx FIFO */ + base->FIFO |= (LPUART_FIFO_TXFE_MASK | LPUART_FIFO_RXFE_MASK); + + /* Flush FIFO */ + base->FIFO |= (LPUART_FIFO_TXFLUSH_MASK | LPUART_FIFO_RXFLUSH_MASK); + #endif + + /* Clear all status flags */ + temp = (LPUART_STAT_RXEDGIF_MASK | LPUART_STAT_IDLE_MASK | LPUART_STAT_OR_MASK | LPUART_STAT_NF_MASK | + LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK); + + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp |= LPUART_STAT_LBKDIF_MASK; + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING) && FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING + temp |= (LPUART_STAT_MA1F_MASK | LPUART_STAT_MA2F_MASK); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT + /* Set the CTS configuration/TX CTS source. */ + base->MODIR |= LPUART_MODIR_TXCTSC(config->txCtsConfig) | LPUART_MODIR_TXCTSSRC(config->txCtsSource); + if (true == config->enableRxRTS) { + /* Enable the receiver RTS(request-to-send) function. */ + base->MODIR |= LPUART_MODIR_RXRTSE_MASK; + } + if (true == config->enableTxCTS) { + /* Enable the CTS(clear-to-send) function. */ + base->MODIR |= LPUART_MODIR_TXCTSE_MASK; + } + #endif + + /* Set data bits order. */ + if (true == config->isMsb) { + temp |= LPUART_STAT_MSBF_MASK; + } else { + temp &= ~LPUART_STAT_MSBF_MASK; + } + + base->STAT |= temp; + + /* Enable TX/RX base on configure structure. */ + temp = base->CTRL; + if (true == config->enableTx) { + temp |= LPUART_CTRL_TE_MASK; + } + + if (true == config->enableRx) { + temp |= LPUART_CTRL_RE_MASK; + } + + base->CTRL = temp; + } + + return status; +} +/*! + * brief Deinitializes a LPUART instance. + * + * This function waits for transmit to complete, disables TX and RX, and disables the LPUART clock. + * + * param base LPUART peripheral base address. + */ +void LPUART_Deinit(LPUART_Type *base) { + uint32_t temp; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Wait tx FIFO send out*/ + while (0U != ((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXWATER_SHIFT)) { + } + #endif + /* Wait last char shift out */ + while (0U == (base->STAT & LPUART_STAT_TC_MASK)) { + } + + /* Clear all status flags */ + temp = (LPUART_STAT_RXEDGIF_MASK | LPUART_STAT_IDLE_MASK | LPUART_STAT_OR_MASK | LPUART_STAT_NF_MASK | + LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK); + + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp |= LPUART_STAT_LBKDIF_MASK; + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING) && FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING + temp |= (LPUART_STAT_MA1F_MASK | LPUART_STAT_MA2F_MASK); + #endif + + base->STAT |= temp; + + /* Disable the module. */ + base->CTRL = 0U; + + #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + uint32_t instance = LPUART_GetInstance(base); + + /* Disable lpuart clock */ + (void)CLOCK_DisableClock(s_lpuartClock[instance]); + + #if defined(LPUART_PERIPH_CLOCKS) + (void)CLOCK_DisableClock(s_lpuartPeriphClocks[instance]); + #endif + + #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ +} + +/*! + * brief Gets the default configuration structure. + * + * This function initializes the LPUART configuration structure to a default value. The default + * values are: + * lpuartConfig->baudRate_Bps = 115200U; + * lpuartConfig->parityMode = kLPUART_ParityDisabled; + * lpuartConfig->dataBitsCount = kLPUART_EightDataBits; + * lpuartConfig->isMsb = false; + * lpuartConfig->stopBitCount = kLPUART_OneStopBit; + * lpuartConfig->txFifoWatermark = 0; + * lpuartConfig->rxFifoWatermark = 1; + * lpuartConfig->rxIdleType = kLPUART_IdleTypeStartBit; + * lpuartConfig->rxIdleConfig = kLPUART_IdleCharacter1; + * lpuartConfig->enableTx = false; + * lpuartConfig->enableRx = false; + * + * param config Pointer to a configuration structure. + */ +void LPUART_GetDefaultConfig(lpuart_config_t *config) { + assert(NULL != config); + + /* Initializes the configure structure to zero. */ + (void)memset(config, 0, sizeof(*config)); + + config->baudRate_Bps = 115200U; + config->parityMode = kLPUART_ParityDisabled; + config->dataBitsCount = kLPUART_EightDataBits; + config->isMsb = false; + #if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT + config->stopBitCount = kLPUART_OneStopBit; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + config->txFifoWatermark = 0U; + config->rxFifoWatermark = 0U; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT + config->enableRxRTS = false; + config->enableTxCTS = false; + config->txCtsConfig = kLPUART_CtsSampleAtStart; + config->txCtsSource = kLPUART_CtsSourcePin; + #endif + config->rxIdleType = kLPUART_IdleTypeStartBit; + config->rxIdleConfig = kLPUART_IdleCharacter1; + config->enableTx = false; + config->enableRx = false; +} + +/*! + * brief Sets the LPUART instance baudrate. + * + * This function configures the LPUART module baudrate. This function is used to update + * the LPUART module baudrate after the LPUART module is initialized by the LPUART_Init. + * code + * LPUART_SetBaudRate(LPUART1, 115200U, 20000000U); + * endcode + * + * param base LPUART peripheral base address. + * param baudRate_Bps LPUART baudrate to be set. + * param srcClock_Hz LPUART clock source frequency in HZ. + * retval kStatus_LPUART_BaudrateNotSupport Baudrate is not supported in the current clock source. + * retval kStatus_Success Set baudrate succeeded. + */ +status_t LPUART_SetBaudRate(LPUART_Type *base, uint32_t baudRate_Bps, uint32_t srcClock_Hz) { + assert(0U < baudRate_Bps); + + status_t status = kStatus_Success; + uint32_t temp, oldCtrl; + uint16_t sbr, sbrTemp; + uint8_t osr, osrTemp; + uint32_t tempDiff, calculatedBaud, baudDiff; + + /* This LPUART instantiation uses a slightly different baud rate calculation + * The idea is to use the best OSR (over-sampling rate) possible + * Note, OSR is typically hard-set to 16 in other LPUART instantiations + * loop to find the best OSR value possible, one that generates minimum baudDiff + * iterate through the rest of the supported values of OSR */ + + baudDiff = baudRate_Bps; + osr = 0U; + sbr = 0U; + for (osrTemp = 4U; osrTemp <= 32U; osrTemp++) { + /* calculate the temporary sbr value */ + sbrTemp = (uint16_t)((srcClock_Hz * 10U / (baudRate_Bps * (uint32_t)osrTemp) + 5U) / 10U); + /*set sbrTemp to 1 if the sourceClockInHz can not satisfy the desired baud rate*/ + if (sbrTemp == 0U) { + sbrTemp = 1U; + } + /* Calculate the baud rate based on the temporary OSR and SBR values */ + calculatedBaud = srcClock_Hz / ((uint32_t)osrTemp * (uint32_t)sbrTemp); + + tempDiff = calculatedBaud > baudRate_Bps ? (calculatedBaud - baudRate_Bps) : (baudRate_Bps - calculatedBaud); + + if (tempDiff <= baudDiff) { + baudDiff = tempDiff; + osr = osrTemp; /* update and store the best OSR value calculated */ + sbr = sbrTemp; /* update store the best SBR value calculated */ + } + } + + /* Check to see if actual baud rate is within 3% of desired baud rate + * based on the best calculate OSR value */ + if (baudDiff < (uint32_t)((baudRate_Bps / 100U) * 3U)) { + /* Store CTRL before disable Tx and Rx */ + oldCtrl = base->CTRL; + + /* Disable LPUART TX RX before setting. */ + base->CTRL &= ~(LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK); + + temp = base->BAUD; + + /* Acceptable baud rate, check if OSR is between 4x and 7x oversampling. + * If so, then "BOTHEDGE" sampling must be turned on */ + if ((osr > 3U) && (osr < 8U)) { + temp |= LPUART_BAUD_BOTHEDGE_MASK; + } + + /* program the osr value (bit value is one less than actual value) */ + temp &= ~LPUART_BAUD_OSR_MASK; + temp |= LPUART_BAUD_OSR((uint32_t)osr - 1UL); + + /* write the sbr value to the BAUD registers */ + temp &= ~LPUART_BAUD_SBR_MASK; + base->BAUD = temp | LPUART_BAUD_SBR(sbr); + + /* Restore CTRL. */ + base->CTRL = oldCtrl; + } else { + /* Unacceptable baud rate difference of more than 3%*/ + status = kStatus_LPUART_BaudrateNotSupport; + } + + return status; +} + +/*! + * brief Enable 9-bit data mode for LPUART. + * + * This function set the 9-bit mode for LPUART module. The 9th bit is not used for parity thus can be modified by user. + * + * param base LPUART peripheral base address. + * param enable true to enable, false to disable. + */ +void LPUART_Enable9bitMode(LPUART_Type *base, bool enable) { + assert(base != NULL); + + uint32_t temp = 0U; + + if (enable) { + /* Set LPUART_CTRL_M for 9-bit mode, clear LPUART_CTRL_PE to disable parity. */ + temp = base->CTRL & ~((uint32_t)LPUART_CTRL_PE_MASK | (uint32_t)LPUART_CTRL_M_MASK); + temp |= (uint32_t)LPUART_CTRL_M_MASK; + base->CTRL = temp; + } else { + /* Clear LPUART_CTRL_M. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_M_MASK; + } + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + /* Clear LPUART_CTRL_M7 to disable 7-bit mode. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_M7_MASK; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_10BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_10BIT_DATA_SUPPORT + /* Clear LPUART_BAUD_M10 to disable 10-bit mode. */ + base->BAUD &= ~(uint32_t)LPUART_BAUD_M10_MASK; + #endif +} + +/*! + * brief Transmit an address frame in 9-bit data mode. + * + * param base LPUART peripheral base address. + * param address LPUART slave address. + */ +void LPUART_SendAddress(LPUART_Type *base, uint8_t address) { + assert(base != NULL); + + uint32_t temp = base->DATA & 0xFFFFFC00UL; + temp |= ((uint32_t)address | (1UL << LPUART_DATA_R8T8_SHIFT)); + base->DATA = temp; +} + +/*! + * brief Enables LPUART interrupts according to a provided mask. + * + * This function enables the LPUART interrupts according to a provided mask. The mask + * is a logical OR of enumeration members. See the ref _lpuart_interrupt_enable. + * This examples shows how to enable TX empty interrupt and RX full interrupt: + * code + * LPUART_EnableInterrupts(LPUART1,kLPUART_TxDataRegEmptyInterruptEnable | kLPUART_RxDataRegFullInterruptEnable); + * endcode + * + * param base LPUART peripheral base address. + * param mask The interrupts to enable. Logical OR of ref _uart_interrupt_enable. + */ +void LPUART_EnableInterrupts(LPUART_Type *base, uint32_t mask) { + /* Only consider the real interrupt enable bits. */ + mask &= (uint32_t)kLPUART_AllInterruptEnable; + + /* Check int enable bits in base->BAUD */ + uint32_t tempReg = base->BAUD; + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + tempReg |= ((mask << 8U) & LPUART_BAUD_LBKDIE_MASK); + /* Clear bit 7 from mask */ + mask &= ~(uint32_t)kLPUART_LinBreakInterruptEnable; + #endif + tempReg |= ((mask << 8U) & LPUART_BAUD_RXEDGIE_MASK); + /* Clear bit 6 from mask */ + mask &= ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable; + base->BAUD = tempReg; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + base->FIFO = (base->FIFO & ~(LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) | + (mask & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + /* Clear bit 9 and bit 8 from mask */ + mask &= ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable); + #endif + + /* Check int enable bits in base->CTRL */ + base->CTRL |= mask; +} + +/*! + * brief Disables LPUART interrupts according to a provided mask. + * + * This function disables the LPUART interrupts according to a provided mask. The mask + * is a logical OR of enumeration members. See ref _lpuart_interrupt_enable. + * This example shows how to disable the TX empty interrupt and RX full interrupt: + * code + * LPUART_DisableInterrupts(LPUART1,kLPUART_TxDataRegEmptyInterruptEnable | kLPUART_RxDataRegFullInterruptEnable); + * endcode + * + * param base LPUART peripheral base address. + * param mask The interrupts to disable. Logical OR of ref _lpuart_interrupt_enable. + */ +void LPUART_DisableInterrupts(LPUART_Type *base, uint32_t mask) { + /* Only consider the real interrupt enable bits. */ + mask &= (uint32_t)kLPUART_AllInterruptEnable; + /* Check int enable bits in base->BAUD */ + uint32_t tempReg = base->BAUD; + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + tempReg &= ~((mask << 8U) & LPUART_BAUD_LBKDIE_MASK); + /* Clear bit 7 from mask */ + mask &= ~(uint32_t)kLPUART_LinBreakInterruptEnable; + #endif + tempReg &= ~((mask << 8U) & LPUART_BAUD_RXEDGIE_MASK); + /* Clear bit 6 from mask */ + mask &= ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable; + base->BAUD = tempReg; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + base->FIFO = (base->FIFO & ~(LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) & + ~(mask & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + /* Clear bit 9 and bit 8 from mask */ + mask &= ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable); + #endif + + /* Check int enable bits in base->CTRL */ + base->CTRL &= ~mask; +} + +/*! + * brief Gets enabled LPUART interrupts. + * + * This function gets the enabled LPUART interrupts. The enabled interrupts are returned + * as the logical OR value of the enumerators ref _lpuart_interrupt_enable. To check + * a specific interrupt enable status, compare the return value with enumerators + * in ref _lpuart_interrupt_enable. + * For example, to check whether the TX empty interrupt is enabled: + * code + * uint32_t enabledInterrupts = LPUART_GetEnabledInterrupts(LPUART1); + * + * if (kLPUART_TxDataRegEmptyInterruptEnable & enabledInterrupts) + * { + * ... + * } + * endcode + * + * param base LPUART peripheral base address. + * return LPUART interrupt flags which are logical OR of the enumerators in ref _lpuart_interrupt_enable. + */ +uint32_t LPUART_GetEnabledInterrupts(LPUART_Type *base) { + /* Check int enable bits in base->CTRL */ + uint32_t temp = (uint32_t)(base->CTRL & (uint32_t)kLPUART_AllInterruptEnable); + + /* Check int enable bits in base->BAUD */ + temp = (temp & ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable) | ((base->BAUD & LPUART_BAUD_RXEDGIE_MASK) >> 8U); + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp = (temp & ~(uint32_t)kLPUART_LinBreakInterruptEnable) | ((base->BAUD & LPUART_BAUD_LBKDIE_MASK) >> 8U); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + temp = + (temp & ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable)) | + (base->FIFO & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + #endif + + return temp; +} + +/*! + * brief Gets LPUART status flags. + * + * This function gets all LPUART status flags. The flags are returned as the logical + * OR value of the enumerators ref _lpuart_flags. To check for a specific status, + * compare the return value with enumerators in the ref _lpuart_flags. + * For example, to check whether the TX is empty: + * code + * if (kLPUART_TxDataRegEmptyFlag & LPUART_GetStatusFlags(LPUART1)) + * { + * ... + * } + * endcode + * + * param base LPUART peripheral base address. + * return LPUART status flags which are ORed by the enumerators in the _lpuart_flags. + */ +uint32_t LPUART_GetStatusFlags(LPUART_Type *base) { + uint32_t temp; + temp = base->STAT; + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + temp |= (base->FIFO & + (LPUART_FIFO_TXEMPT_MASK | LPUART_FIFO_RXEMPT_MASK | LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) >> + 16U; + #endif + /* Only keeps the status bits */ + temp &= (uint32_t)kLPUART_AllFlags; + return temp; +} + +/*! + * brief Clears status flags with a provided mask. + * + * This function clears LPUART status flags with a provided mask. Automatically cleared flags + * can't be cleared by this function. + * Flags that can only cleared or set by hardware are: + * kLPUART_TxDataRegEmptyFlag, kLPUART_TransmissionCompleteFlag, kLPUART_RxDataRegFullFlag, + * kLPUART_RxActiveFlag, kLPUART_NoiseErrorInRxDataRegFlag, kLPUART_ParityErrorInRxDataRegFlag, + * kLPUART_TxFifoEmptyFlag,kLPUART_RxFifoEmptyFlag + * Note: This API should be called when the Tx/Rx is idle, otherwise it takes no effects. + * + * param base LPUART peripheral base address. + * param mask the status flags to be cleared. The user can use the enumerators in the + * _lpuart_status_flag_t to do the OR operation and get the mask. + * return 0 succeed, others failed. + * retval kStatus_LPUART_FlagCannotClearManually The flag can't be cleared by this function but + * it is cleared automatically by hardware. + * retval kStatus_Success Status in the mask are cleared. + */ +status_t LPUART_ClearStatusFlags(LPUART_Type *base, uint32_t mask) { + uint32_t temp; + status_t status; + + /* Only deal with the clearable flags */ + mask &= (uint32_t)kLPUART_AllClearFlags; + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Status bits in FIFO register */ + if ((mask & ((uint32_t)kLPUART_TxFifoOverflowFlag | (uint32_t)kLPUART_RxFifoUnderflowFlag)) != 0U) { + /* Get the FIFO register value and mask the rx/tx FIFO flush bits and the status bits that can be W1C in case + they are written 1 accidentally. */ + temp = (uint32_t)base->FIFO; + temp &= (uint32_t)( + ~(LPUART_FIFO_TXFLUSH_MASK | LPUART_FIFO_RXFLUSH_MASK | LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)); + temp |= (mask << 16U) & (LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK); + base->FIFO = temp; + } + #endif + /* Status bits in STAT register */ + /* First get the STAT register value and mask all the bits that not represent status, then OR with the status bit + * that is to be W1C */ + temp = (base->STAT & 0x3E000000UL) | mask; + base->STAT = temp; + /* If some flags still pending. */ + if (0U != (mask & LPUART_GetStatusFlags(base))) { + status = kStatus_LPUART_FlagCannotClearManually; + } else { + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Writes to the transmitter register using a blocking method. + * + * This function polls the transmitter register, first waits for the register to be empty or TX FIFO to have room, + * and writes data to the transmitter buffer, then waits for the data to be sent out to bus. + * + * param base LPUART peripheral base address. + * param data Start address of the data to write. + * param length Size of the data to write. + * retval kStatus_LPUART_Timeout Transmission timed out and was aborted. + * retval kStatus_Success Successfully wrote all data. + */ +status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length) { + assert(NULL != data); + + const uint8_t *dataAddress = data; + size_t transferSize = length; + + #if UART_RETRY_TIMES + uint32_t waitTimes; + #endif + + while (0U != transferSize) { + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + while ((0U == (base->STAT & LPUART_STAT_TDRE_MASK)) && (0U != --waitTimes)) + #else + while (0U == (base->STAT & LPUART_STAT_TDRE_MASK)) + #endif + { + } + #if UART_RETRY_TIMES + if (0U == waitTimes) { + return kStatus_LPUART_Timeout; + } + #endif + base->DATA = *(dataAddress); + dataAddress++; + transferSize--; + } + /* Ensure all the data in the transmit buffer are sent out to bus. */ + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + while ((0U == (base->STAT & LPUART_STAT_TC_MASK)) && (0U != --waitTimes)) + #else + while (0U == (base->STAT & LPUART_STAT_TC_MASK)) + #endif + { + } + #if UART_RETRY_TIMES + if (0U == waitTimes) { + return kStatus_LPUART_Timeout; + } + #endif + return kStatus_Success; +} + +/*! + * brief Reads the receiver data register using a blocking method. + * + * This function polls the receiver register, waits for the receiver register full or receiver FIFO + * has data, and reads data from the TX register. + * + * param base LPUART peripheral base address. + * param data Start address of the buffer to store the received data. + * param length Size of the buffer. + * retval kStatus_LPUART_RxHardwareOverrun Receiver overrun happened while receiving data. + * retval kStatus_LPUART_NoiseError Noise error happened while receiving data. + * retval kStatus_LPUART_FramingError Framing error happened while receiving data. + * retval kStatus_LPUART_ParityError Parity error happened while receiving data. + * retval kStatus_LPUART_Timeout Transmission timed out and was aborted. + * retval kStatus_Success Successfully received all data. + */ +status_t LPUART_ReadBlocking(LPUART_Type *base, uint8_t *data, size_t length) { + assert(NULL != data); + + status_t status = kStatus_Success; + uint32_t statusFlag; + uint8_t *dataAddress = data; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + #if UART_RETRY_TIMES + uint32_t waitTimes; + #endif + + while (0U != (length--)) { + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + while (0U == ((base->WATER & LPUART_WATER_RXCOUNT_MASK) >> LPUART_WATER_RXCOUNT_SHIFT)) + #else + while (0U == (base->STAT & LPUART_STAT_RDRF_MASK)) + #endif + { + #if UART_RETRY_TIMES + if (0U == --waitTimes) { + status = kStatus_LPUART_Timeout; + break; + } + #endif + statusFlag = LPUART_GetStatusFlags(base); + + if (0U != (statusFlag & (uint32_t)kLPUART_RxOverrunFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_RxOverrunFlag)) ? + (kStatus_LPUART_RxHardwareOverrun) : + (kStatus_LPUART_FlagCannotClearManually)); + /* Other error flags(FE, NF, and PF) are prevented from setting once OR is set, no need to check other + * error flags*/ + break; + } + + if (0U != (statusFlag & (uint32_t)kLPUART_ParityErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_ParityErrorFlag)) ? + (kStatus_LPUART_ParityError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + + if (0U != (statusFlag & (uint32_t)kLPUART_FramingErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_FramingErrorFlag)) ? + (kStatus_LPUART_FramingError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + + if (0U != (statusFlag & (uint32_t)kLPUART_NoiseErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_NoiseErrorFlag)) ? + (kStatus_LPUART_NoiseError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + if (kStatus_Success != status) { + break; + } + } + + if (kStatus_Success == status) { + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (isSevenDataBits) { + *(dataAddress) = (uint8_t)(base->DATA & 0x7FU); + dataAddress++; + } else { + *(dataAddress) = (uint8_t)base->DATA; + dataAddress++; + } + #else + *(dataAddress) = (uint8_t)base->DATA; + dataAddress++; + #endif + } else { + break; + } + } + + return status; +} + +/*! + * brief Initializes the LPUART handle. + * + * This function initializes the LPUART handle, which can be used for other LPUART + * transactional APIs. Usually, for a specified LPUART instance, + * call this API once to get the initialized handle. + * + * The LPUART driver supports the "background" receiving, which means that user can set up + * an RX ring buffer optionally. Data received is stored into the ring buffer even when the + * user doesn't call the LPUART_TransferReceiveNonBlocking() API. If there is already data received + * in the ring buffer, the user can get the received data from the ring buffer directly. + * The ring buffer is disabled if passing NULL as p ringBuffer. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param callback Callback function. + * param userData User data. + */ +void LPUART_TransferCreateHandle(LPUART_Type *base, + lpuart_handle_t *handle, + lpuart_transfer_callback_t callback, + void *userData) { + assert(NULL != handle); + + uint32_t instance; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + /* Zero the handle. */ + (void)memset(handle, 0, sizeof(lpuart_handle_t)); + + /* Set the TX/RX state. */ + handle->rxState = (uint8_t)kLPUART_RxIdle; + handle->txState = (uint8_t)kLPUART_TxIdle; + + /* Set the callback and user data. */ + handle->callback = callback; + handle->userData = userData; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + /* Initial seven data bits flag */ + handle->isSevenDataBits = isSevenDataBits; + #endif + + /* Get instance from peripheral base address. */ + instance = LPUART_GetInstance(base); + + /* Save the handle in global variables to support the double weak mechanism. */ + s_lpuartHandle[instance] = handle; + + s_lpuartIsr = LPUART_TransferHandleIRQ; + +/* Enable interrupt in NVIC. */ + #if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ + (void)EnableIRQ(s_lpuartRxIRQ[instance]); + (void)EnableIRQ(s_lpuartTxIRQ[instance]); + #else + (void)EnableIRQ(s_lpuartIRQ[instance]); + #endif +} + +/*! + * brief Sets up the RX ring buffer. + * + * This function sets up the RX ring buffer to a specific UART handle. + * + * When the RX ring buffer is used, data received is stored into the ring buffer even when + * the user doesn't call the UART_TransferReceiveNonBlocking() API. If there is already data received + * in the ring buffer, the user can get the received data from the ring buffer directly. + * + * note When using RX ring buffer, one byte is reserved for internal use. In other + * words, if p ringBufferSize is 32, then only 31 bytes are used for saving data. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param ringBuffer Start address of ring buffer for background receiving. Pass NULL to disable the ring buffer. + * param ringBufferSize size of the ring buffer. + */ +void LPUART_TransferStartRingBuffer(LPUART_Type *base, + lpuart_handle_t *handle, + uint8_t *ringBuffer, + size_t ringBufferSize) { + assert(NULL != handle); + assert(NULL != ringBuffer); + + /* Setup the ring buffer address */ + handle->rxRingBuffer = ringBuffer; + handle->rxRingBufferSize = ringBufferSize; + handle->rxRingBufferHead = 0U; + handle->rxRingBufferTail = 0U; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Enable the interrupt to accept the data when user need the ring buffer. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); +} + +/*! + * brief Aborts the background transfer and uninstalls the ring buffer. + * + * This function aborts the background transfer and uninstalls the ring buffer. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferStopRingBuffer(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + if (handle->rxState == (uint8_t)kLPUART_RxIdle) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + handle->rxRingBuffer = NULL; + handle->rxRingBufferSize = 0U; + handle->rxRingBufferHead = 0U; + handle->rxRingBufferTail = 0U; +} + +/*! + * brief Transmits a buffer of data using the interrupt method. + * + * This function send data using an interrupt method. This is a non-blocking function, which + * returns directly without waiting for all data written to the transmitter register. When + * all data is written to the TX register in the ISR, the LPUART driver calls the callback + * function and passes the ref kStatus_LPUART_TxIdle as status parameter. + * + * note The kStatus_LPUART_TxIdle is passed to the upper layer when all data are written + * to the TX register. However, there is no check to ensure that all the data sent out. Before disabling the TX, + * check the kLPUART_TransmissionCompleteFlag to ensure that the transmit is finished. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param xfer LPUART transfer structure, see #lpuart_transfer_t. + * retval kStatus_Success Successfully start the data transmission. + * retval kStatus_LPUART_TxBusy Previous transmission still not finished, data not all written to the TX register. + * retval kStatus_InvalidArgument Invalid argument. + */ +status_t LPUART_TransferSendNonBlocking(LPUART_Type *base, lpuart_handle_t *handle, lpuart_transfer_t *xfer) { + assert(NULL != handle); + assert(NULL != xfer); + assert(NULL != xfer->txData); + assert(0U != xfer->dataSize); + + status_t status; + + /* Return error if current TX busy. */ + if ((uint8_t)kLPUART_TxBusy == handle->txState) { + status = kStatus_LPUART_TxBusy; + } else { + handle->txData = xfer->txData; + handle->txDataSize = xfer->dataSize; + handle->txDataSizeAll = xfer->dataSize; + handle->txState = (uint8_t)kLPUART_TxBusy; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Enable transmitter interrupt. */ + base->CTRL |= (uint32_t)LPUART_CTRL_TIE_MASK; + EnableGlobalIRQ(irqMask); + + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Aborts the interrupt-driven data transmit. + * + * This function aborts the interrupt driven data sending. The user can get the remainBtyes to find out + * how many bytes are not sent out. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferAbortSend(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. */ + uint32_t irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_TIE_MASK | LPUART_CTRL_TCIE_MASK); + EnableGlobalIRQ(irqMask); + + handle->txDataSize = 0; + handle->txState = (uint8_t)kLPUART_TxIdle; +} + +/*! + * brief Gets the number of bytes that have been sent out to bus. + * + * This function gets the number of bytes that have been sent out to bus by an interrupt method. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param count Send bytes count. + * retval kStatus_NoTransferInProgress No send in progress. + * retval kStatus_InvalidArgument Parameter is invalid. + * retval kStatus_Success Get successfully through the parameter \p count; + */ +status_t LPUART_TransferGetSendCount(LPUART_Type *base, lpuart_handle_t *handle, uint32_t *count) { + assert(NULL != handle); + assert(NULL != count); + + status_t status = kStatus_Success; + size_t tmptxDataSize = handle->txDataSize; + + if ((uint8_t)kLPUART_TxIdle == handle->txState) { + status = kStatus_NoTransferInProgress; + } else { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + *count = handle->txDataSizeAll - tmptxDataSize - + ((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXCOUNT_SHIFT); + #else + if ((base->STAT & (uint32_t)kLPUART_TxDataRegEmptyFlag) != 0U) { + *count = handle->txDataSizeAll - tmptxDataSize; + } else { + *count = handle->txDataSizeAll - tmptxDataSize - 1U; + } + #endif + } + + return status; +} + +/*! + * brief Receives a buffer of data using the interrupt method. + * + * This function receives data using an interrupt method. This is a non-blocking function + * which returns without waiting to ensure that all data are received. + * If the RX ring buffer is used and not empty, the data in the ring buffer is copied and + * the parameter p receivedBytes shows how many bytes are copied from the ring buffer. + * After copying, if the data in the ring buffer is not enough for read, the receive + * request is saved by the LPUART driver. When the new data arrives, the receive request + * is serviced first. When all data is received, the LPUART driver notifies the upper layer + * through a callback function and passes a status parameter ref kStatus_UART_RxIdle. + * For example, the upper layer needs 10 bytes but there are only 5 bytes in ring buffer. + * The 5 bytes are copied to xfer->data, which returns with the + * parameter p receivedBytes set to 5. For the remaining 5 bytes, the newly arrived data is + * saved from xfer->data[5]. When 5 bytes are received, the LPUART driver notifies the upper layer. + * If the RX ring buffer is not enabled, this function enables the RX and RX interrupt + * to receive data to xfer->data. When all data is received, the upper layer is notified. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param xfer LPUART transfer structure, see #uart_transfer_t. + * param receivedBytes Bytes received from the ring buffer directly. + * retval kStatus_Success Successfully queue the transfer into the transmit queue. + * retval kStatus_LPUART_RxBusy Previous receive request is not finished. + * retval kStatus_InvalidArgument Invalid argument. + */ +status_t LPUART_TransferReceiveNonBlocking(LPUART_Type *base, + lpuart_handle_t *handle, + lpuart_transfer_t *xfer, + size_t *receivedBytes) { + assert(NULL != handle); + assert(NULL != xfer); + assert(NULL != xfer->rxData); + assert(0U != xfer->dataSize); + + uint32_t i; + status_t status; + uint32_t irqMask; + /* How many bytes to copy from ring buffer to user memory. */ + size_t bytesToCopy = 0U; + /* How many bytes to receive. */ + size_t bytesToReceive; + /* How many bytes currently have received. */ + size_t bytesCurrentReceived; + + /* How to get data: + 1. If RX ring buffer is not enabled, then save xfer->data and xfer->dataSize + to lpuart handle, enable interrupt to store received data to xfer->data. When + all data received, trigger callback. + 2. If RX ring buffer is enabled and not empty, get data from ring buffer first. + If there are enough data in ring buffer, copy them to xfer->data and return. + If there are not enough data in ring buffer, copy all of them to xfer->data, + save the xfer->data remained empty space to lpuart handle, receive data + to this empty space and trigger callback when finished. */ + + if ((uint8_t)kLPUART_RxBusy == handle->rxState) { + status = kStatus_LPUART_RxBusy; + } else { + bytesToReceive = xfer->dataSize; + bytesCurrentReceived = 0; + + /* If RX ring buffer is used. */ + if (NULL != handle->rxRingBuffer) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Disable LPUART RX IRQ, protect ring buffer. */ + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + + /* How many bytes in RX ring buffer currently. */ + bytesToCopy = LPUART_TransferGetRxRingBufferLength(base, handle); + + if (0U != bytesToCopy) { + bytesToCopy = MIN(bytesToReceive, bytesToCopy); + + bytesToReceive -= bytesToCopy; + + /* Copy data from ring buffer to user memory. */ + for (i = 0U; i < bytesToCopy; i++) { + xfer->rxData[bytesCurrentReceived] = handle->rxRingBuffer[handle->rxRingBufferTail]; + bytesCurrentReceived++; + + /* Wrap to 0. Not use modulo (%) because it might be large and slow. */ + if (((uint32_t)handle->rxRingBufferTail + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferTail = 0U; + } else { + handle->rxRingBufferTail++; + } + } + } + + /* If ring buffer does not have enough data, still need to read more data. */ + if (0U != bytesToReceive) { + /* No data in ring buffer, save the request to LPUART handle. */ + handle->rxData = &xfer->rxData[bytesCurrentReceived]; + handle->rxDataSize = bytesToReceive; + handle->rxDataSizeAll = xfer->dataSize; + handle->rxState = (uint8_t)kLPUART_RxBusy; + } + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Re-enable LPUART RX IRQ. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + + /* Call user callback since all data are received. */ + if (0U == bytesToReceive) { + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData); + } + } + } + /* Ring buffer not used. */ + else { + handle->rxData = &xfer->rxData[bytesCurrentReceived]; + handle->rxDataSize = bytesToReceive; + handle->rxDataSizeAll = bytesToReceive; + handle->rxState = (uint8_t)kLPUART_RxBusy; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Enable RX interrupt. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ILIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + /* Return the how many bytes have read. */ + if (NULL != receivedBytes) { + *receivedBytes = bytesCurrentReceived; + } + + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Aborts the interrupt-driven data receiving. + * + * This function aborts the interrupt-driven data receiving. The user can get the remainBytes to find out + * how many bytes not received yet. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferAbortReceive(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + /* Only abort the receive to handle->rxData, the RX ring buffer is still working. */ + if (NULL == handle->rxRingBuffer) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Disable RX interrupt. */ + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ILIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + handle->rxDataSize = 0U; + handle->rxState = (uint8_t)kLPUART_RxIdle; +} + +/*! + * brief Gets the number of bytes that have been received. + * + * This function gets the number of bytes that have been received. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param count Receive bytes count. + * retval kStatus_NoTransferInProgress No receive in progress. + * retval kStatus_InvalidArgument Parameter is invalid. + * retval kStatus_Success Get successfully through the parameter \p count; + */ +status_t LPUART_TransferGetReceiveCount(LPUART_Type *base, lpuart_handle_t *handle, uint32_t *count) { + assert(NULL != handle); + assert(NULL != count); + + status_t status = kStatus_Success; + size_t tmprxDataSize = handle->rxDataSize; + + if ((uint8_t)kLPUART_RxIdle == handle->rxState) { + status = kStatus_NoTransferInProgress; + } else { + *count = handle->rxDataSizeAll - tmprxDataSize; + } + + return status; +} + +/*! + * brief LPUART IRQ handle function. + * + * This function handles the LPUART transmit and receive IRQ request. + * + * param base LPUART peripheral base address. + * param irqHandle LPUART handle pointer. + */ +void LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle) { + assert(NULL != irqHandle); + + uint8_t count; + uint8_t tempCount; + uint32_t status = LPUART_GetStatusFlags(base); + uint32_t enabledInterrupts = LPUART_GetEnabledInterrupts(base); + uint16_t tpmRxRingBufferHead; + uint32_t tpmData; + uint32_t irqMask; + lpuart_handle_t *handle = (lpuart_handle_t *)irqHandle; + + /* If RX overrun. */ + if ((uint32_t)kLPUART_RxOverrunFlag == ((uint32_t)kLPUART_RxOverrunFlag & status)) { + /* Clear overrun flag, otherwise the RX does not work. */ + base->STAT = ((base->STAT & 0x3FE00000U) | LPUART_STAT_OR_MASK); + + /* Trigger callback. */ + if (NULL != (handle->callback)) { + handle->callback(base, handle, kStatus_LPUART_RxHardwareOverrun, handle->userData); + } + } + /* Receive data register full */ + if ((0U != ((uint32_t)kLPUART_RxDataRegFullFlag & status)) && + (0U != ((uint32_t)kLPUART_RxDataRegFullInterruptEnable & enabledInterrupts))) { + /* Get the size that can be stored into buffer for this interrupt. */ + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + count = ((uint8_t)((base->WATER & LPUART_WATER_RXCOUNT_MASK) >> LPUART_WATER_RXCOUNT_SHIFT)); + #else + count = 1; + #endif + + /* If handle->rxDataSize is not 0, first save data to handle->rxData. */ + while ((0U != handle->rxDataSize) && (0U != count)) { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + tempCount = (uint8_t)MIN(handle->rxDataSize, count); + #else + tempCount = 1; + #endif + + /* Using non block API to read the data from the registers. */ + LPUART_ReadNonBlocking(base, handle->rxData, tempCount); + handle->rxData = &handle->rxData[tempCount]; + handle->rxDataSize -= tempCount; + count -= tempCount; + + /* If all the data required for upper layer is ready, trigger callback. */ + if (0U == handle->rxDataSize) { + handle->rxState = (uint8_t)kLPUART_RxIdle; + + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData); + } + } + } + + /* If use RX ring buffer, receive data to ring buffer. */ + if (NULL != handle->rxRingBuffer) { + while (0U != count--) { + /* If RX ring buffer is full, trigger callback to notify over run. */ + if (LPUART_TransferIsRxRingBufferFull(base, handle)) { + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxRingBufferOverrun, handle->userData); + } + } + + /* If ring buffer is still full after callback function, the oldest data is overridden. */ + if (LPUART_TransferIsRxRingBufferFull(base, handle)) { + /* Increase handle->rxRingBufferTail to make room for new data. */ + if (((uint32_t)handle->rxRingBufferTail + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferTail = 0U; + } else { + handle->rxRingBufferTail++; + } + } + + /* Read data. */ + tpmRxRingBufferHead = handle->rxRingBufferHead; + tpmData = base->DATA; + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (handle->isSevenDataBits) { + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)(tpmData & 0x7FU); + } else { + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)tpmData; + } + #else + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)tpmData; + #endif + + /* Increase handle->rxRingBufferHead. */ + if (((uint32_t)handle->rxRingBufferHead + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferHead = 0U; + } else { + handle->rxRingBufferHead++; + } + } + } + /* If no receive request pending, stop RX interrupt. */ + else if (0U == handle->rxDataSize) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK | LPUART_CTRL_ILIE_MASK); + EnableGlobalIRQ(irqMask); + } else { + } + } + + /* Send data register empty and the interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_TxDataRegEmptyFlag & status)) && + (0U != ((uint32_t)kLPUART_TxDataRegEmptyInterruptEnable & enabledInterrupts))) { +/* Get the bytes that available at this moment. */ + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + count = (uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) - + (uint8_t)((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXCOUNT_SHIFT); + #else + count = 1; + #endif + + while ((0U != handle->txDataSize) && (0U != count)) { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + tempCount = (uint8_t)MIN(handle->txDataSize, count); + #else + tempCount = 1; + #endif + + /* Using non block API to write the data to the registers. */ + LPUART_WriteNonBlocking(base, handle->txData, tempCount); + handle->txData = &handle->txData[tempCount]; + handle->txDataSize -= tempCount; + count -= tempCount; + + /* If all the data are written to data register, notify user with the callback, then TX finished. */ + if (0U == handle->txDataSize) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Disable TX register empty interrupt and enable transmission completion interrupt. */ + base->CTRL = (base->CTRL & ~LPUART_CTRL_TIE_MASK) | LPUART_CTRL_TCIE_MASK; + EnableGlobalIRQ(irqMask); + } + } + } + + /* Transmission complete and the interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_TransmissionCompleteFlag & status)) && + (0U != ((uint32_t)kLPUART_TransmissionCompleteInterruptEnable & enabledInterrupts))) { + /* Set txState to idle only when all data has been sent out to bus. */ + handle->txState = (uint8_t)kLPUART_TxIdle; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + irqMask = DisableGlobalIRQ(); + /* Disable transmission complete interrupt. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_TCIE_MASK; + EnableGlobalIRQ(irqMask); + + /* Trigger callback. */ + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_TxIdle, handle->userData); + } + } + + /* If IDLE flag is set and the IDLE interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_IdleLineFlag & status)) && + (0U != ((uint32_t)kLPUART_IdleLineInterruptEnable & enabledInterrupts))) { + /* Clear IDLE flag.*/ + base->STAT |= LPUART_STAT_IDLE_MASK; + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_IdleLineDetected, handle->userData); + } else { + /* Avoid MISRA 15.7 */ + } + } + +} + +/*! + * brief LPUART Error IRQ handle function. + * + * This function handles the LPUART error IRQ request. + * + * param base LPUART peripheral base address. + * param irqHandle LPUART handle pointer. + */ +void LPUART_TransferHandleErrorIRQ(LPUART_Type *base, void *irqHandle) { + /* To be implemented by User. */ +} +#if defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1 +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART0_LPUART1_RX_DriverIRQHandler(void); +void LPUART0_LPUART1_RX_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +void LPUART0_LPUART1_TX_DriverIRQHandler(void); +void LPUART0_LPUART1_TX_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART0_LPUART1_DriverIRQHandler(void); +void LPUART0_LPUART1_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART0) +#if !(defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART0_TX_DriverIRQHandler(void); +void LPUART0_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART0_RX_DriverIRQHandler(void); +void LPUART0_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART0_DriverIRQHandler(void); +void LPUART0_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif +#endif + +#if defined(LPUART1) +#if !(defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART1_TX_DriverIRQHandler(void); +void LPUART1_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART1_RX_DriverIRQHandler(void); +void LPUART1_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART1_DriverIRQHandler(void); +void LPUART1_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif +#endif + +#if defined(LPUART2) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART2_TX_DriverIRQHandler(void); +void LPUART2_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART2_RX_DriverIRQHandler(void); +void LPUART2_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART2_DriverIRQHandler(void); +void LPUART2_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART3) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART3_TX_DriverIRQHandler(void); +void LPUART3_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART3_RX_DriverIRQHandler(void); +void LPUART3_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART3_DriverIRQHandler(void); +void LPUART3_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART4) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART4_TX_DriverIRQHandler(void); +void LPUART4_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART4_RX_DriverIRQHandler(void); +void LPUART4_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART4_DriverIRQHandler(void); +void LPUART4_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART5) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART5_TX_DriverIRQHandler(void); +void LPUART5_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART5_RX_DriverIRQHandler(void); +void LPUART5_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART5_DriverIRQHandler(void); +void LPUART5_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART6) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART6_TX_DriverIRQHandler(void); +void LPUART6_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART6_RX_DriverIRQHandler(void); +void LPUART6_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART6_DriverIRQHandler(void); +void LPUART6_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART7) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART7_TX_DriverIRQHandler(void); +void LPUART7_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART7_RX_DriverIRQHandler(void); +void LPUART7_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART7_DriverIRQHandler(void); +void LPUART7_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART8) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART8_TX_DriverIRQHandler(void); +void LPUART8_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART8_RX_DriverIRQHandler(void); +void LPUART8_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART8_DriverIRQHandler(void); +void LPUART8_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART9) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART9_TX_DriverIRQHandler(void); +void LPUART9_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART9_RX_DriverIRQHandler(void); +void LPUART9_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART9_DriverIRQHandler(void); +void LPUART9_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART10) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART10_TX_DriverIRQHandler(void); +void LPUART10_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART10_RX_DriverIRQHandler(void); +void LPUART10_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART10_DriverIRQHandler(void); +void LPUART10_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART11) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART11_TX_DriverIRQHandler(void); +void LPUART11_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART11_RX_DriverIRQHandler(void); +void LPUART11_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART11_DriverIRQHandler(void); +void LPUART11_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(CM4_0__LPUART) +void M4_0_LPUART_DriverIRQHandler(void); +void M4_0_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4_0__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4_0__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(CM4_1__LPUART) +void M4_1_LPUART_DriverIRQHandler(void); +void M4_1_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4_1__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4_1__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(CM4__LPUART) +void M4_LPUART_DriverIRQHandler(void); +void M4_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART0) +void DMA_UART0_INT_DriverIRQHandler(void); +void DMA_UART0_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART0, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART0)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART1) +void DMA_UART1_INT_DriverIRQHandler(void); +void DMA_UART1_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART1, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART1)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART2) +void DMA_UART2_INT_DriverIRQHandler(void); +void DMA_UART2_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART2, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART2)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART3) +void DMA_UART3_INT_DriverIRQHandler(void); +void DMA_UART3_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART3, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART3)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART4) +void DMA_UART4_INT_DriverIRQHandler(void); +void DMA_UART4_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART4, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART4)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART0) +void ADMA_UART0_INT_DriverIRQHandler(void); +void ADMA_UART0_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART0, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART0)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART1) +void ADMA_UART1_INT_DriverIRQHandler(void); +void ADMA_UART1_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART1, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART1)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART2) +void ADMA_UART2_INT_DriverIRQHandler(void); +void ADMA_UART2_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART2, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART2)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART3) +void ADMA_UART3_INT_DriverIRQHandler(void); +void ADMA_UART3_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART3, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART3)]); + SDK_ISR_EXIT_BARRIER; +} +#endif diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 7ae584fdd7454..9f9d6c8fd256f 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -50,6 +50,10 @@ #define UART_INVERT_RX (2) #define UART_INVERT_MASK (UART_INVERT_TX | UART_INVERT_RX) +#define UART_IRQ_RXIDLE (1) +#define UART_IRQ_TXIDLE (2) +#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RXIDLE | UART_IRQ_TXIDLE) + typedef struct _machine_uart_obj_t { mp_obj_base_t base; struct _lpuart_handle handle; @@ -63,6 +67,9 @@ typedef struct _machine_uart_obj_t { uint8_t *txbuf; uint16_t txbuf_len; bool new; + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object } machine_uart_obj_t; typedef struct _iomux_table_t { @@ -137,11 +144,21 @@ bool lpuart_set_iomux_cts(int8_t uart) { void LPUART_UserCallback(LPUART_Type *base, lpuart_handle_t *handle, status_t status, void *userData) { machine_uart_obj_t *self = userData; - if (kStatus_LPUART_TxIdle == status) { + + uint16_t mp_irq_flags = 0; + if (status == kStatus_LPUART_TxIdle) { self->tx_status = kStatus_LPUART_TxIdle; + mp_irq_flags = UART_IRQ_TXIDLE; + } else if (status == kStatus_LPUART_IdleLineDetected) { + mp_irq_flags = UART_IRQ_RXIDLE; + } + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); } - if (kStatus_LPUART_RxRingBufferOverrun == status) { + if (status == kStatus_LPUART_RxRingBufferOverrun) { ; // Ringbuffer full, deassert RTS if flow control is enabled } } @@ -170,16 +187,18 @@ void machine_uart_set_baudrate(mp_obj_t uart_in, uint32_t baudrate) { { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERT_RX) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(UART_IRQ_TXIDLE) }, \ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, flow=%s, " - "rxbuf=%d, txbuf=%d, timeout=%u, timeout_char=%u, invert=%s)", + "rxbuf=%d, txbuf=%d, timeout=%u, timeout_char=%u, invert=%s, irq=%d)", self->id, self->config.baudRate_Bps, 8 - self->config.dataBitsCount, _parity_name[self->config.parityMode], self->config.stopBitCount + 1, _flow_name[(self->config.enableTxCTS << 1) | self->config.enableRxRTS], self->handle.rxRingBufferSize, self->txbuf_len, self->timeout, self->timeout_char, - _invert_name[self->invert]); + _invert_name[self->invert], self->mp_irq_trigger); } static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -313,12 +332,17 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, #else LPUART_Init(self->lpuart, &self->config, CLOCK_GetClockRootFreq(kCLOCK_UartClkRoot)); #endif + self->config.rxIdleType = kLPUART_IdleTypeStartBit; + self->config.rxIdleConfig = kLPUART_IdleCharacter4; + LPUART_Init(self->lpuart, &self->config, BOARD_BOOTCLOCKRUN_UART_CLK_ROOT); LPUART_TransferCreateHandle(self->lpuart, &self->handle, LPUART_UserCallback, self); uint8_t *buffer = m_new(uint8_t, rxbuf_len + 1); LPUART_TransferStartRingBuffer(self->lpuart, &self->handle, buffer, rxbuf_len); self->txbuf = m_new(uint8_t, txbuf_len); // Allocate the TX buffer. self->txbuf_len = txbuf_len; + LPUART_EnableInterrupts(self->lpuart, kLPUART_IdleLineInterruptEnable); + // The Uart supports inverting, but not the fsl API, so it has to coded directly // And it has to be done after LPUART_Init. if (self->invert & UART_INVERT_RX) { @@ -356,6 +380,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout = 1; self->timeout_char = 1; self->new = true; + self->mp_irq_obj = NULL; + LPUART_GetDefaultConfig(&self->config); // Configure board-specific pin MUX based on the hardware device number. @@ -401,6 +427,55 @@ void machine_uart_deinit_all(void) { } } +static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->mp_irq_trigger = new_trigger; + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + } + + return self->mp_irq_obj; +} + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uint64_t t = ticks_us64() + (uint64_t)self->timeout * 1000; diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index ac918ba4da8d5..5ef6695c1425c 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -114,6 +114,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/mimxrt/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_ONEWIRE (1) // fatfs configuration used in ffconf.h diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index c4150c292cb7c..59e74dce4215a 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -191,6 +191,7 @@ endif SRC_SHARED_C += $(addprefix shared/,\ libc/string0.c \ readline/readline.c \ + runtime/mpirq.c \ runtime/pyexec.c \ runtime/stdout_helpers.c \ runtime/sys_stdio_mphal.c \ diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 90a67bcbc08ad..8d5a73e095f5e 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -62,6 +62,7 @@ typedef struct _machine_uart_buf_t { #define nrfx_uart_tx nrfx_uarte_tx #define nrfx_uart_tx_in_progress nrfx_uarte_tx_in_progress #define nrfx_uart_init nrfx_uarte_init +#define nrfx_uart_uninit nrfx_uarte_uninit #define nrfx_uart_event_t nrfx_uarte_event_t #define NRFX_UART_INSTANCE NRFX_UARTE_INSTANCE @@ -69,6 +70,7 @@ typedef struct _machine_uart_buf_t { #define NRF_UART_HWFC_DISABLED NRF_UARTE_HWFC_DISABLED #define NRF_UART_PARITY_EXCLUDED NRF_UARTE_PARITY_EXCLUDED #define NRFX_UART_EVT_RX_DONE NRFX_UARTE_EVT_RX_DONE +#define NRFX_UART_EVT_TX_DONE NRFX_UARTE_EVT_TX_DONE #define NRFX_UART_EVT_ERROR NRFX_UARTE_EVT_ERROR #define NRF_UART_BAUDRATE_1200 NRF_UARTE_BAUDRATE_1200 @@ -88,18 +90,31 @@ typedef struct _machine_uart_buf_t { #endif +#if MICROPY_PY_MACHINE_UART_IRQ +#define NRFX_UART_IRQ_RX (1 << NRFX_UART_EVT_RX_DONE) +#define NRFX_UART_IRQ_TXIDLE (1 << NRFX_UART_EVT_TX_DONE) +#define MP_UART_ALLOWED_FLAGS (NRFX_UART_IRQ_RX | NRFX_UART_IRQ_TXIDLE) +#endif + typedef struct _machine_uart_obj_t { mp_obj_base_t base; const nrfx_uart_t *p_uart; // Driver instance machine_uart_buf_t buf; uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms) + uint8_t uart_id; + bool initialized; // static flag. Initialized to False + #if MICROPY_PY_MACHINE_UART_IRQ + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object + #endif } machine_uart_obj_t; static const nrfx_uart_t instance0 = NRFX_UART_INSTANCE(0); static machine_uart_obj_t machine_uart_obj[] = { - {{&machine_uart_type}, .p_uart = &instance0} + {{&machine_uart_type}, .p_uart = &instance0, .uart_id = 0} }; void uart_init0(void) { @@ -116,6 +131,9 @@ static int uart_find(mp_obj_t id) { static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context) { machine_uart_obj_t *self = p_context; + #if MICROPY_PY_MACHINE_UART_IRQ + uint16_t mp_irq_flags = 0; + #endif if (p_event->type == NRFX_UART_EVT_RX_DONE) { nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); int chr = self->buf.rx_buf[0]; @@ -129,10 +147,24 @@ static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context { ringbuf_put((ringbuf_t *)&self->buf.rx_ringbuf, chr); } + #if MICROPY_PY_MACHINE_UART_IRQ + mp_irq_flags |= NRFX_UART_IRQ_RX; + #endif } else if (p_event->type == NRFX_UART_EVT_ERROR) { // Perform a read to unlock UART in case of an error nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); + } else if (p_event->type == NRFX_UART_EVT_TX_DONE) { + #if MICROPY_PY_MACHINE_UART_IRQ + mp_irq_flags |= NRFX_UART_IRQ_TXIDLE; + #endif + } + #if MICROPY_PY_MACHINE_UART_IRQ + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); } + #endif } bool uart_rx_any(machine_uart_obj_t *self) { @@ -170,8 +202,14 @@ void uart_tx_strn_cooked(machine_uart_obj_t *uart_obj, const char *str, uint len /******************************************************************************/ /* MicroPython bindings */ -// The UART class doesn't have any constants for this port. +#if MICROPY_PY_MACHINE_UART_IRQ +#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(NRFX_UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(NRFX_UART_IRQ_TXIDLE) }, \ + +#else #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS +#endif static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { mp_printf(print, "UART(0)"); @@ -254,8 +292,18 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->buf.rx_ringbuf.iget = 0; self->buf.rx_ringbuf.iput = 0; + #if MICROPY_PY_MACHINE_UART_IRQ + self->mp_irq_trigger = 0; + self->mp_irq_obj = NULL; + MP_STATE_PORT(nrf_uart_irq_obj)[self->uart_id] = NULL; + #endif + // Enable event callback and start asynchronous receive + if (self->initialized) { + nrfx_uart_uninit(self->p_uart); + } nrfx_uart_init(self->p_uart, &config, uart_event_handler); + self->initialized = true; nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); #if NRFX_UART_ENABLED @@ -266,7 +314,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg } static void mp_machine_uart_deinit(machine_uart_obj_t *self) { - (void)self; + if (self->initialized) { + nrfx_uart_uninit(self->p_uart); + } + self->initialized = false; } // Write a single character on the bus. `data` is an integer to write. @@ -293,6 +344,60 @@ static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { return !nrfx_uart_tx_in_progress(self->p_uart); } +#if MICROPY_PY_MACHINE_UART_IRQ + +static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->mp_irq_trigger = new_trigger; + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + MP_STATE_PORT(nrf_uart_irq_obj)[self->uart_id] = self->mp_irq_obj; + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + } + + return self->mp_irq_obj; +} + +#endif + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = self_in; byte *buf = buf_in; @@ -375,3 +480,5 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint } return MP_STREAM_ERROR; } + +MP_REGISTER_ROOT_POINTER(void *nrf_uart_irq_obj[1]); diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 1c789f779b8e7..7cc8a66d9840c 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -230,6 +230,10 @@ #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/nrf/modules/machine/uart.c" #define MICROPY_PY_MACHINE_UART_READCHAR_WRITECHAR (1) +#if defined(NRF52840) +#define MICROPY_PY_MACHINE_UART_IRQ (1) +#endif + #ifndef MICROPY_PY_MACHINE_TIMER_NRF #define MICROPY_PY_MACHINE_TIMER_NRF (1) #endif diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index f521a0c5ad82d..cdfc39580b9d8 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -1,3 +1,8 @@ +BOARD ?= mps2-an385 + +# Make the build directory reflect the board. +BUILD ?= build-$(BOARD) + include ../../py/mkenv.mk -include mpconfigport.mk @@ -6,16 +11,19 @@ QSTR_DEFS = qstrdefsport.h # MicroPython feature configurations MICROPY_ROM_TEXT_COMPRESSION ?= 1 +FROZEN_MANIFEST ?= "freeze('test-frzmpy')" # include py core make definitions include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk -BOARD ?= mps2-an385 +CFLAGS += -DMICROPY_HW_BOARD_NAME='"$(BOARD)"' +QEMU_ARGS += -machine $(BOARD) -nographic -monitor null -semihosting ifeq ($(BOARD),netduino2) CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft CFLAGS += -DQEMU_SOC_STM32 +CFLAGS += -DMICROPY_HW_MCU_NAME='"STM32"' LDSCRIPT = stm32.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m @@ -24,8 +32,9 @@ endif ifeq ($(BOARD),microbit) CFLAGS += -mthumb -mcpu=cortex-m0 -mfloat-abi=soft CFLAGS += -DQEMU_SOC_NRF51 +CFLAGS += -DMICROPY_HW_MCU_NAME='"nRF51"' LDSCRIPT = nrf51.ld -QEMU_EXTRA = -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144 +QEMU_ARGS += -global nrf51-soc.flash-size=1048576 -global nrf51-soc.sram-size=262144 SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb1.o MPY_CROSS_FLAGS += -march=armv7m endif @@ -33,6 +42,7 @@ endif ifeq ($(BOARD),mps2-an385) CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft CFLAGS += -DQEMU_SOC_MPS2 +CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M3"' LDSCRIPT = mps2.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m @@ -41,11 +51,16 @@ endif ifeq ($(BOARD),sabrelite) CFLAGS += -mcpu=cortex-a9 CFLAGS += -DQEMU_SOC_IMX6 +CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-A9"' LDSCRIPT = imx6.ld -QEMU_EXTRA = -m 128M +QEMU_ARGS += -m 128M SRC_BOARD_O = shared/runtime/gchelper_generic.o # It's really armv7a but closest supported value is armv6. MPY_CROSS_FLAGS += -march=armv6 +# Cortex-A9 should support unaligned-access, but qemu doesn't seem to. +CFLAGS += -mno-unaligned-access +# These don't work on Cortex-A9. +TESTS_EXCLUDE = --exclude '(asmdiv|asmspecialregs).py' endif CROSS_COMPILE ?= arm-none-eabi- @@ -78,16 +93,18 @@ LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) SRC_COMMON_C = \ startup.c \ uart.c \ + mphalport.c \ shared/libc/string0.c \ + shared/readline/readline.c \ + shared/runtime/interrupt_char.c \ + shared/runtime/pyexec.c \ + shared/runtime/semihosting_arm.c \ + shared/runtime/stdout_helpers.c \ shared/runtime/sys_stdio_mphal.c \ SRC_RUN_C = \ main.c \ -SRC_TEST_C = \ - test_main.c \ - lib/tinytest/tinytest.c \ - LIB_SRC_C += $(SRC_LIB_LIBM_C) LIB_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C) @@ -113,10 +130,21 @@ OBJ = $(OBJ_COMMON) $(OBJ_RUN) $(OBJ_TEST) # List of sources for qstr extraction SRC_QSTR += $(SRC_COMMON_C) $(SRC_RUN_C) $(LIB_SRC_C) -all: run +all: $(BUILD)/firmware.elf + +.PHONY: repl +repl: $(BUILD)/firmware.elf + $(ECHO) "Use machine.reset() to exit" + qemu-system-arm $(QEMU_ARGS) -serial mon:stdio -kernel $< +.PHONY: run run: $(BUILD)/firmware.elf - qemu-system-arm -machine $(BOARD) $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $< + qemu-system-arm $(QEMU_ARGS) -serial pty -kernel $< + +.PHONY: test +test: $(BUILD)/firmware.elf + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && ./run-tests.py --target qemu-arm --device execpty:"qemu-system-arm $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(TESTS_EXCLUDE) ## `$(LD)` doesn't seem to like `--specs` for some reason, but we can just use `$(CC)` here. $(BUILD)/firmware.elf: $(LDSCRIPT) $(ALL_OBJ_RUN) diff --git a/ports/qemu-arm/Makefile.test b/ports/qemu-arm/Makefile.test deleted file mode 100644 index cb5b0927c8c9a..0000000000000 --- a/ports/qemu-arm/Makefile.test +++ /dev/null @@ -1,33 +0,0 @@ -LIB_SRC_C = shared/upytesthelper/upytesthelper.c - -FROZEN_MANIFEST ?= "freeze('test-frzmpy')" - -include Makefile - -ifeq ($(BOARD),sabrelite) -# These don't work on Cortex-A9. -TESTS_EXCLUDE = inlineasm/asmdiv.py inlineasm/asmspecialregs.py -endif - -CFLAGS += -DTEST - -.PHONY: $(BUILD)/genhdr/tests.h - -TESTS_PROFILE = $(dir $(abspath $(firstword $(MAKEFILE_LIST))))/tests_profile.txt - -$(BUILD)/test_main.o: $(BUILD)/genhdr/tests.h -$(BUILD)/genhdr/tests.h: - (cd $(TOP)/tests; ./run-tests.py --target=qemu-arm --write-exp) - $(Q)echo "Generating $@";(cd $(TOP)/tests; ../tools/tinytest-codegen.py --profile $(TESTS_PROFILE) $(addprefix --exclude ,$(TESTS_EXCLUDE))) > $@ - -$(BUILD)/lib/tinytest/tinytest.o: CFLAGS += -DNO_FORKING - -$(BUILD)/firmware-test.elf: $(LDSCRIPT) $(ALL_OBJ_TEST) - $(Q)$(LD) $(LDFLAGS) -o $@ $(ALL_OBJ_TEST) $(LIBS) - $(Q)$(SIZE) $@ - -# Note: Using timeout(1) to handle cases where qemu hangs (e.g. this can happen with alignment errors). -test: $(BUILD)/firmware-test.elf - timeout --foreground -k 5s 30s qemu-system-arm -machine $(BOARD) $(QEMU_EXTRA) -nographic -monitor null -semihosting -kernel $< > $(BUILD)/console.out - $(Q)tail -n2 $(BUILD)/console.out - $(Q)tail -n1 $(BUILD)/console.out | grep -q "status: 0" diff --git a/ports/qemu-arm/README.md b/ports/qemu-arm/README.md index f821c4d1e28fb..34d73fd165756 100644 --- a/ports/qemu-arm/README.md +++ b/ports/qemu-arm/README.md @@ -1,3 +1,6 @@ +MicroPython port to qemu-arm +============================ + This is experimental, community-supported port for Cortex-M emulation as provided by QEMU (http://qemu.org). @@ -15,14 +18,52 @@ The purposes of this port are to enable: - no need to use OpenOCD or anything else that might slow down the process in terms of plugging things together, pressing buttons, etc. -This port will only work with the [GNU ARM Embedded Toolchain]( -https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads - and not with CodeSourcery toolchain. You will need to modify -`LDFLAGS` if you want to use CodeSourcery's version of `arm-none-eabi`. -The difference is that CodeSourcery needs `-T generic-m-hosted.ld` while -ARM's version requires `--specs=nano.specs --specs=rdimon.specs` to be -passed to the linker. +Build instructions +------------------ + +First make sure the MicroPython cross-compiler is built (run from this directory): + + $ make -C ../../mpy-cross + +Then build using: + + $ make + +The default qemu-supported board is `mps2-an385`, a Cortex-M3 board. To select a +different board pass the `BOARD` argument to `make`, for example: + + $ make BOARD=sabrelite + +Running +------- + +When the firmware is run it will provide a REPL on the emulated hardware UART. +To access the REPL directly use: + + $ make repl + +This will start `qemu-system-arm` with the UART redirected to stdio. It's also +possible to redirect the UART to a pty device using: + + $ make run + +This will start the emulation and the name of the pty device will be printed to +stdout. This serial device then be accessed via a serial terminal program, +for example `mpremote`: + + $ mpremote connect /dev/pts/1 + +You can disconnect and reconnect to the serial device multiple times. Once you +are finished, stop the `make run` command by pressing Ctrl-C where that command +was started (or execute `machine.reset()` at the REPL). + +The test suite can be run against the firmware by using the UART redirection. +You can either do this automatically using the single command: + + $ make test -To build and run image with builtin testsuite: +Or manually by first starting the emulation with `make run` and then running the +tests against the serial device, for example: - make -f Makefile.test test + $ cd ../../tests + $ ./run-tests.py --target qemu-arm --device /dev/pts/1 diff --git a/ports/qemu-arm/main.c b/ports/qemu-arm/main.c index 025c1f17da04a..042106580407d 100644 --- a/ports/qemu-arm/main.c +++ b/ports/qemu-arm/main.c @@ -25,41 +25,50 @@ */ #include -#include #include "py/compile.h" #include "py/runtime.h" #include "py/stackctrl.h" #include "py/gc.h" #include "py/mperrno.h" +#include "shared/runtime/gchelper.h" +#include "shared/runtime/pyexec.h" -void do_str(const char *src, mp_parse_input_kind_t input_kind) { - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); - qstr source_name = lex->source_name; - mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); - mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true); - mp_call_function_0(module_fun); - nlr_pop(); - } else { - // uncaught exception - mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); - } -} +#define HEAP_SIZE (100 * 1024) + +static uint32_t gc_heap[HEAP_SIZE / sizeof(uint32_t)]; int main(int argc, char **argv) { mp_stack_ctrl_init(); mp_stack_set_limit(10240); - uint32_t heap[16 * 1024 / 4]; - gc_init(heap, (char *)heap + 16 * 1024); - mp_init(); - do_str("print('hello world!')", MP_PARSE_SINGLE_INPUT); - mp_deinit(); - return 0; + gc_init(gc_heap, (char *)gc_heap + HEAP_SIZE); + + for (;;) { + mp_init(); + + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + + mp_printf(&mp_plat_print, "MPY: soft reboot\n"); + + gc_sweep_all(); + mp_deinit(); + } } void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + gc_collect_end(); } mp_lexer_t *mp_lexer_new_from_file(qstr filename) { @@ -67,6 +76,6 @@ mp_lexer_t *mp_lexer_new_from_file(qstr filename) { } void nlr_jump_fail(void *val) { - printf("uncaught NLR\n"); + mp_printf(&mp_plat_print, "uncaught NLR\n"); exit(1); } diff --git a/ports/qemu-arm/modmachine.c b/ports/qemu-arm/modmachine.c index a897c5670e49b..75872a22c1f40 100644 --- a/ports/qemu-arm/modmachine.c +++ b/ports/qemu-arm/modmachine.c @@ -27,6 +27,22 @@ // This file is never compiled standalone, it's included directly from // extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE. +#include + static void mp_machine_idle(void) { // Do nothing. } + +#if MICROPY_PY_MACHINE_RESET + +static void mp_machine_reset(void) { + // Exit qemu (via semihosting call). + exit(0); +} + +static mp_int_t mp_machine_reset_cause(void) { + // Not implemented. + return 0; +} + +#endif diff --git a/ports/qemu-arm/mpconfigport.h b/ports/qemu-arm/mpconfigport.h index fce379e47ecf4..4059a5926d3cd 100644 --- a/ports/qemu-arm/mpconfigport.h +++ b/ports/qemu-arm/mpconfigport.h @@ -42,22 +42,19 @@ #define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) #define MICROPY_MEM_STATS (1) #define MICROPY_ENABLE_GC (1) -#define MICROPY_KBD_EXCEPTION (0) -#define MICROPY_HELPER_REPL (0) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_WARNINGS (1) -#define MICROPY_PY_BUILTINS_INPUT (0) -#define MICROPY_PY_BUILTINS_HELP (0) #define MICROPY_PY_IO_IOBASE (0) #define MICROPY_PY_SYS_PLATFORM "qemu-arm" -#define MICROPY_PY_SYS_STDFILES (0) #define MICROPY_PY_SYS_STDIO_BUFFER (0) #define MICROPY_PY_SELECT (0) #define MICROPY_PY_TIME (0) #define MICROPY_PY_ASYNCIO (0) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/qemu-arm/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS (1) @@ -78,8 +75,4 @@ typedef long mp_off_t; // We need an implementation of the log2 function which is not a macro. #define MP_NEED_LOG2 (1) -#ifdef TEST -#include "shared/upytesthelper/upytesthelper.h" -#undef MP_PLAT_PRINT_STRN -#define MP_PLAT_PRINT_STRN(str, len) upytest_output(str, len) -#endif +#define MP_STATE_PORT MP_STATE_VM diff --git a/ports/qemu-arm/mphalport.c b/ports/qemu-arm/mphalport.c new file mode 100644 index 0000000000000..dbb87b48b801a --- /dev/null +++ b/ports/qemu-arm/mphalport.c @@ -0,0 +1,66 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Damien P. George + * + * 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 "py/mphal.h" +#include "shared/runtime/semihosting_arm.h" +#include "uart.h" + +// UART is better behaved with redirection under qemu-system-arm, so prefer that for stdio. +#define USE_UART (1) +#define USE_SEMIHOSTING (0) + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + // Not implemented. + return 0; +} + +int mp_hal_stdin_rx_chr(void) { + for (;;) { + #if USE_UART + int c = uart_rx_chr(); + if (c >= 0) { + return c; + } + #endif + #if USE_SEMIHOSTING + char str[1]; + int ret = mp_semihosting_rx_chars(str, 1); + if (ret == 0) { + return str[0]; + } + #endif + } +} + +mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { + #if USE_UART + uart_tx_strn(str, len); + #endif + #if USE_SEMIHOSTING + mp_semihosting_tx_strn(str, len); + #endif + return len; +} diff --git a/ports/qemu-arm/mphalport.h b/ports/qemu-arm/mphalport.h index 8a40505ba6b3a..348b45701b89e 100644 --- a/ports/qemu-arm/mphalport.h +++ b/ports/qemu-arm/mphalport.h @@ -24,7 +24,4 @@ * THE SOFTWARE. */ -#include "uart.h" - -#define mp_hal_stdin_rx_chr() (0) -#define mp_hal_stdout_tx_strn_cooked(s, l) uart_tx_strn((s), (l)) +#include "shared/runtime/interrupt_char.h" diff --git a/ports/qemu-arm/startup.c b/ports/qemu-arm/startup.c index a1e89d111c007..118a5b8006f15 100644 --- a/ports/qemu-arm/startup.c +++ b/ports/qemu-arm/startup.c @@ -28,6 +28,7 @@ #include #include +#include "shared/runtime/semihosting_arm.h" #include "uart.h" extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss; @@ -97,6 +98,8 @@ const uint32_t isr_vector[] __attribute__((section(".isr_vector"))) = { #endif void _start(void) { + mp_semihosting_init(); + // Enable the UART uart_init(); @@ -108,21 +111,9 @@ void _start(void) { exit(0); } -__attribute__((naked)) void exit(int status) { +void exit(int status) { // Force qemu to exit using ARM Semihosting - __asm volatile ( - "mov r1, r0\n" - "cmp r1, #0\n" - "bne .notclean\n" - "ldr r1, =0x20026\n" // ADP_Stopped_ApplicationExit, a clean exit - ".notclean:\n" - "movs r0, #0x18\n" // SYS_EXIT - #if defined(__ARM_ARCH_ISA_ARM) - "svc 0x00123456\n" - #elif defined(__ARM_ARCH_ISA_THUMB) - "bkpt 0xab\n" - #endif - ); + mp_semihosting_exit(status); for (;;) { } } @@ -134,13 +125,3 @@ void __assert_func(const char *file, int line, const char *func, const char *exp exit(1); } #endif - -// The following are needed for tinytest - -#include - -int setvbuf(FILE *stream, char *buf, int mode, size_t size) { - return 0; -} - -struct _reent *_impure_ptr; diff --git a/ports/qemu-arm/tests_profile.txt b/ports/qemu-arm/tests_profile.txt deleted file mode 100644 index 101943b7c6ab0..0000000000000 --- a/ports/qemu-arm/tests_profile.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Port-specific test directories. - -test_dirs.update(("inlineasm", "ports/qemu-arm")) - -# Port-specific tests exclusion list. - -exclude_tests.update( - ( - # inline asm FP tests (require Cortex-M4) - "inlineasm/asmfpaddsub.py", - "inlineasm/asmfpcmp.py", - "inlineasm/asmfpldrstr.py", - "inlineasm/asmfpmuldiv.py", - "inlineasm/asmfpsqrt.py", - ) -) diff --git a/ports/qemu-arm/uart.c b/ports/qemu-arm/uart.c index d7338f9a24230..5ace3d465f147 100644 --- a/ports/qemu-arm/uart.c +++ b/ports/qemu-arm/uart.c @@ -31,14 +31,29 @@ #if defined(QEMU_SOC_STM32) +#define UART_SR_RXNE (1 << 5) +#define UART_CR1_UE (1 << 13) +#define UART_CR1_TE (1 << 3) +#define UART_CR1_RE (1 << 2) + typedef struct _UART_t { volatile uint32_t SR; volatile uint32_t DR; + volatile uint32_t BRR; + volatile uint32_t CR1; } UART_t; #define UART0 ((UART_t *)(0x40011000)) void uart_init(void) { + UART0->CR1 = UART_CR1_UE | UART_CR1_TE | UART_CR1_RE; +} + +int uart_rx_chr(void) { + if (!(UART0->SR & UART_SR_RXNE)) { + return UART_RX_NO_CHAR; + } + return UART0->DR; } void uart_tx_strn(const char *buf, size_t len) { @@ -50,11 +65,15 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_NRF51) typedef struct _UART_t { - volatile uint32_t r0[2]; + volatile uint32_t STARTRX; // 0x000 + volatile uint32_t STOPRX; // 0x004 volatile uint32_t STARTTX; // 0x008 - volatile uint32_t r1[(0x500 - 0x008) / 4 - 1]; + volatile uint32_t r0[(0x108 - 0x008) / 4 - 1]; + volatile uint32_t RXDRDY; // 0x108 + volatile uint32_t r1[(0x500 - 0x108) / 4 - 1]; volatile uint32_t ENABLE; // 0x500 - volatile uint32_t r2[(0x51c - 0x500) / 4 - 1]; + volatile uint32_t r2[(0x518 - 0x500) / 4 - 1]; + volatile uint32_t RXD; // 0x518 volatile uint32_t TXD; // 0x51c } UART_t; @@ -62,9 +81,18 @@ typedef struct _UART_t { void uart_init(void) { UART0->ENABLE = 4; + UART0->STARTRX = 1; UART0->STARTTX = 1; } +int uart_rx_chr(void) { + if (!UART0->RXDRDY) { + return UART_RX_NO_CHAR; + } + UART0->RXDRDY = 0; + return UART0->RXD; +} + void uart_tx_strn(const char *buf, size_t len) { for (size_t i = 0; i < len; ++i) { UART0->TXD = buf[i]; @@ -74,6 +102,7 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_MPS2) #define UART_STATE_TXFULL (1 << 0) +#define UART_STATE_RXFULL (1 << 1) #define UART_CTRL_TX_EN (1 << 0) #define UART_CTRL_RX_EN (1 << 1) @@ -90,7 +119,14 @@ typedef struct _UART_t { void uart_init(void) { UART0->BAUDDIV = 16; - UART0->CTRL = UART_CTRL_TX_EN; + UART0->CTRL = UART_CTRL_TX_EN | UART_CTRL_RX_EN; +} + +int uart_rx_chr(void) { + if (!(UART0->STATE & UART_STATE_RXFULL)) { + return UART_RX_NO_CHAR; + } + return UART0->DATA; } void uart_tx_strn(const char *buf, size_t len) { @@ -104,7 +140,9 @@ void uart_tx_strn(const char *buf, size_t len) { #elif defined(QEMU_SOC_IMX6) #define UART_UCR1_UARTEN (1 << 0) +#define UART_UCR2_RXEN (1 << 1) #define UART_UCR2_TXEN (1 << 2) +#define UART_UTS1_RXEMPTY (1 << 5) typedef struct _UART_t { volatile uint32_t URXD; // 0x00 @@ -113,13 +151,22 @@ typedef struct _UART_t { volatile uint32_t r1[15]; volatile uint32_t UCR1; // 0x80 volatile uint32_t UCR2; // 0x84 + volatile uint32_t r2[11]; + volatile uint32_t UTS1; // 0xb4 } UART_t; #define UART1 ((UART_t *)(0x02020000)) void uart_init(void) { UART1->UCR1 = UART_UCR1_UARTEN; - UART1->UCR2 = UART_UCR2_TXEN; + UART1->UCR2 = UART_UCR2_TXEN | UART_UCR2_RXEN; +} + +int uart_rx_chr(void) { + if (UART1->UTS1 & UART_UTS1_RXEMPTY) { + return UART_RX_NO_CHAR; + } + return UART1->URXD & 0xff; } void uart_tx_strn(const char *buf, size_t len) { diff --git a/ports/qemu-arm/uart.h b/ports/qemu-arm/uart.h index 33eae05bd76dd..9c62a295d1d04 100644 --- a/ports/qemu-arm/uart.h +++ b/ports/qemu-arm/uart.h @@ -28,7 +28,11 @@ #include +// Returned from uart_rx_chr when there are no chars available. +#define UART_RX_NO_CHAR (-1) + void uart_init(void); +int uart_rx_chr(void); void uart_tx_strn(const char *buf, size_t len); #endif // MICROPY_INCLUDED_QEMU_ARM_UART_H diff --git a/ports/qemu-riscv/Makefile b/ports/qemu-riscv/Makefile index 6f15ce52e73c7..473aec882d640 100644 --- a/ports/qemu-riscv/Makefile +++ b/ports/qemu-riscv/Makefile @@ -81,7 +81,7 @@ SRC_COMMON_C = \ shared/runtime/sys_stdio_mphal.c \ SRC_RUN_C = \ - ports/qemu-arm/main.c \ + main.c \ SRC_TEST_C = \ test_main.c \ diff --git a/ports/qemu-arm/test_main.c b/ports/qemu-riscv/main.c similarity index 65% rename from ports/qemu-arm/test_main.c rename to ports/qemu-riscv/main.c index 96984f7cd16ed..025c1f17da04a 100644 --- a/ports/qemu-arm/test_main.c +++ b/ports/qemu-riscv/main.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2014 Ilya Dmitrichenko + * Copyright (c) 2014-2023 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,28 +32,34 @@ #include "py/stackctrl.h" #include "py/gc.h" #include "py/mperrno.h" -#include "shared/runtime/gchelper.h" -#include "lib/tinytest/tinytest.h" -#include "lib/tinytest/tinytest_macros.h" -#define HEAP_SIZE (100 * 1024) - -#include "genhdr/tests.h" +void do_str(const char *src, mp_parse_input_kind_t input_kind) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true); + mp_call_function_0(module_fun); + nlr_pop(); + } else { + // uncaught exception + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } +} -int main() { +int main(int argc, char **argv) { mp_stack_ctrl_init(); mp_stack_set_limit(10240); - static uint32_t heap[HEAP_SIZE / sizeof(uint32_t)]; - upytest_set_heap(heap, (char *)heap + HEAP_SIZE); - int r = tinytest_main(0, NULL, groups); - printf("status: %d\n", r); - return r; + uint32_t heap[16 * 1024 / 4]; + gc_init(heap, (char *)heap + 16 * 1024); + mp_init(); + do_str("print('hello world!')", MP_PARSE_SINGLE_INPUT); + mp_deinit(); + return 0; } void gc_collect(void) { - gc_collect_start(); - gc_helper_collect_regs_and_stack(); - gc_collect_end(); } mp_lexer_t *mp_lexer_new_from_file(qstr filename) { diff --git a/ports/renesas-ra/machine_uart.c b/ports/renesas-ra/machine_uart.c index 26ed625404374..b70978ad7a5fe 100644 --- a/ports/renesas-ra/machine_uart.c +++ b/ports/renesas-ra/machine_uart.c @@ -41,6 +41,8 @@ #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ static const char *_parity_name[] = {"None", "ODD", "EVEN"}; @@ -296,6 +298,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // reference existing UART object self = MP_STATE_PORT(machine_uart_obj_all)[uart_id]; } + self->mp_irq_obj = NULL; + self->rxidle_state = RXIDLE_INACTIVE; // start the peripheral mp_map_t kw_args; @@ -350,6 +354,29 @@ static mp_int_t mp_machine_uart_readchar(machine_uart_obj_t *self) { } } +// Configure the timer used for IRQ_RXIDLE +void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger) { + self->rxidle_state = RXIDLE_INACTIVE; + + if (trigger & UART_IRQ_RXIDLE) { + // The RXIDLE event is always a soft IRQ. + self->mp_irq_obj->ishard = false; + mp_int_t ms = 13000 / self->baudrate + 1; + if (ms < RXIDLE_TIMER_MIN) { + ms = RXIDLE_TIMER_MIN; + } + self->rxidle_ms = ms; + self->rxidle_timer.context = self; + soft_timer_static_init( + &self->rxidle_timer.base, + SOFT_TIMER_MODE_PERIODIC, + ms, + uart_soft_timer_callback + ); + self->rxidle_state = RXIDLE_STANDBY; + } +} + static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; @@ -375,6 +402,7 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args self->mp_irq_obj->handler = handler; self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; self->mp_irq_trigger = trigger; + uart_irq_configure_timer(self, trigger); uart_irq_config(self, true); } diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index 30707552b3efb..d17a1fc913fe0 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -71,22 +71,50 @@ static void uart_rx_cb(uint32_t ch, int d) { // even disable the IRQ. This should never happen. return; } - #if MICROPY_KBD_EXCEPTION - if (keyex_cb[ch]) { - (*keyex_cb[ch])(d); - } - #endif + #if defined(MICROPY_HW_UART_REPL) + if (ch == MICROPY_HW_UART_REPL) { + #if MICROPY_KBD_EXCEPTION + if (keyex_cb[ch]) { + (*keyex_cb[ch])(d); + } + #endif - #if MICROPY_HW_ENABLE_UART_REPL - ringbuf_put(&stdin_ringbuf, d); + #if MICROPY_HW_ENABLE_UART_REPL + ringbuf_put(&stdin_ringbuf, d); + #endif + } #endif + if ((self->mp_irq_trigger & UART_IRQ_RXIDLE) && (self->rxidle_state != RXIDLE_INACTIVE)) { + if (self->rxidle_state == RXIDLE_STANDBY) { + self->rxidle_timer.base.mode = SOFT_TIMER_MODE_PERIODIC; + soft_timer_insert(&self->rxidle_timer.base, self->rxidle_ms); + } + self->rxidle_state = RXIDLE_ALERT; + } // Check the flags to see if the user handler should be called - if (self->mp_irq_trigger) { + if (self->mp_irq_trigger & UART_IRQ_RX) { + self->mp_irq_flags = UART_IRQ_RX; mp_irq_handler(self->mp_irq_obj); } } +void uart_soft_timer_callback(soft_timer_entry_t *self) { + machine_uart_obj_t *uart = ((soft_timer_entry_extended_t *)self)->context; + if (uart->rxidle_state == RXIDLE_ALERT) { + // At the first call, just switch the state + uart->rxidle_state = RXIDLE_ARMED; + } else if (uart->rxidle_state == RXIDLE_ARMED) { + // At the second call, run the irq callback and stop the timer + // by setting the mode to SOFT_TIMER_MODE_ONE_SHOT. + // Calling soft_timer_remove() would fail here. + self->mode = SOFT_TIMER_MODE_ONE_SHOT; + uart->rxidle_state = RXIDLE_STANDBY; + uart->mp_irq_flags = UART_IRQ_RXIDLE; + mp_irq_handler(uart->mp_irq_obj); + } +} + void uart_init0(void) { } @@ -509,6 +537,7 @@ void uart_tx_strn(machine_uart_obj_t *uart_obj, const char *str, uint len) { static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uart_irq_config(self, false); + uart_irq_configure_timer(self, new_trigger); self->mp_irq_trigger = new_trigger; uart_irq_config(self, true); return 0; diff --git a/ports/renesas-ra/uart.h b/ports/renesas-ra/uart.h index ee8eb321d552c..1f74df412cb43 100644 --- a/ports/renesas-ra/uart.h +++ b/ports/renesas-ra/uart.h @@ -29,6 +29,7 @@ #define MICROPY_INCLUDED_RA_UART_H #include "shared/runtime/mpirq.h" +#include "shared/runtime/softtimer.h" #include "pin.h" typedef enum { @@ -57,12 +58,28 @@ typedef enum { #define UART_HWCONTROL_CTS (1) #define UART_HWCONTROL_RTS (2) +#define UART_IRQ_RX (0x10) +#define UART_IRQ_RXIDLE (0x1000) +#define RXIDLE_TIMER_MIN (1) + // OR-ed IRQ flags which are allowed to be used by the user -#define MP_UART_ALLOWED_FLAGS ((uint32_t)0x00000010) +#define MP_UART_ALLOWED_FLAGS ((uint32_t)(UART_IRQ_RX | UART_IRQ_RXIDLE)) // OR-ed IRQ flags which should not be touched by the user #define MP_UART_RESERVED_FLAGS ((uint16_t)0x0020) +enum { + RXIDLE_INACTIVE, + RXIDLE_STANDBY, + RXIDLE_ARMED, + RXIDLE_ALERT, +}; + +typedef struct _soft_timer_entry_extended_t { + soft_timer_entry_t base; + void *context; +} soft_timer_entry_extended_t; + typedef struct _machine_uart_obj_t { mp_obj_base_t base; machine_uart_t uart_id : 8; @@ -87,6 +104,9 @@ typedef struct _machine_uart_obj_t { uint16_t mp_irq_trigger; // user IRQ trigger mask uint16_t mp_irq_flags; // user IRQ active IRQ flags mp_irq_obj_t *mp_irq_obj; // user IRQ object + soft_timer_entry_extended_t rxidle_timer; + uint8_t rxidle_state; + uint16_t rxidle_ms; } machine_uart_obj_t; extern const mp_irq_methods_t uart_irq_methods; @@ -100,6 +120,8 @@ void uart_irq_config(machine_uart_obj_t *self, bool enable); void uart_set_rxbuf(machine_uart_obj_t *self, size_t len, void *buf); void uart_deinit(machine_uart_obj_t *uart_obj); // void uart_irq_handler(mp_uint_t uart_id); +void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger); +void uart_soft_timer_callback(soft_timer_entry_t *self); void uart_attach_to_repl(machine_uart_obj_t *self, bool attached); uint32_t uart_get_baudrate(machine_uart_obj_t *self); diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 83cfe40d957b9..9dc5dbc948ea7 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -147,6 +147,7 @@ set(MICROPY_SOURCE_DRIVERS set(MICROPY_SOURCE_PORT clocks_extra.c + datetime_patch.c fatfs_port.c help.c machine_bitstream.c @@ -537,6 +538,12 @@ set_source_files_properties( COMPILE_OPTIONS "-O2" ) +set_source_files_properties( + rp2_pio.c + PROPERTIES + COMPILE_OPTIONS "-Wno-error=discarded-qualifiers" +) + set_source_files_properties( ${PICO_SDK_PATH}/src/rp2_common/pico_double/double_math.c ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_math.c diff --git a/ports/zephyr/mpconfigport_bin_testsuite.h b/ports/rp2/datetime_patch.c similarity index 66% rename from ports/zephyr/mpconfigport_bin_testsuite.h rename to ports/rp2/datetime_patch.c index 4367067af3eb5..810af4cf1650d 100644 --- a/ports/zephyr/mpconfigport_bin_testsuite.h +++ b/ports/rp2/datetime_patch.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2017 Linaro Limited + * Copyright (c) 2024 Angus Gratton * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "mpconfigport.h" -#ifdef TEST -#include "shared/upytesthelper/upytesthelper.h" -#define MP_PLAT_PRINT_STRN(str, len) upytest_output(str, len) -#endif +#include +#include "py/mpconfig.h" +#include "shared/timeutils/timeutils.h" + +// This is a workaround for the issue that pico-sdk datetime.c will otherwise +// pull in a lot of libc code for time zone support. +// +// Upstream issue is https://github.com/raspberrypi/pico-sdk/issues/1810 + +struct tm *localtime_r(const time_t *__restrict time, struct tm *__restrict local_time) { + return gmtime_r(time, local_time); +} + +time_t mktime(struct tm *__restrict tm) { + return timeutils_mktime(tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); +} diff --git a/ports/rp2/lwip_inc/lwipopts.h b/ports/rp2/lwip_inc/lwipopts.h index 5f37ecdac2e9d..f42bea528e0a4 100644 --- a/ports/rp2/lwip_inc/lwipopts.h +++ b/ports/rp2/lwip_inc/lwipopts.h @@ -1,7 +1,7 @@ #ifndef MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H -#include +#include "py/mpconfig.h" // This protection is not needed, instead protect lwIP code with flags #define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) @@ -38,6 +38,12 @@ #define LWIP_MDNS_RESPONDER 1 #define LWIP_IGMP 1 +#if MICROPY_PY_LWIP_PPP +#define PPP_SUPPORT 1 +#define PAP_SUPPORT 1 +#define CHAP_SUPPORT 1 +#endif + #define LWIP_NUM_NETIF_CLIENT_DATA LWIP_MDNS_RESPONDER #define MEMP_NUM_UDP_PCB (4 + LWIP_MDNS_RESPONDER) #define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + LWIP_MDNS_RESPONDER) @@ -57,4 +63,7 @@ extern uint32_t rosc_random_u32(void); typedef uint32_t sys_prot_t; +// Needed for PPP. +#define sys_jiffies sys_now + #endif // MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H diff --git a/ports/rp2/machine_adc.c b/ports/rp2/machine_adc.c index abf20025c2fbc..40c216d59bc8d 100644 --- a/ports/rp2/machine_adc.c +++ b/ports/rp2/machine_adc.c @@ -31,12 +31,9 @@ #include "hardware/adc.h" #include "machine_pin.h" -#define ADC_CHANNEL_COUNT (NUM_ADC_CHANNELS - 1) - -#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= ADC_BASE_PIN && (gpio) < (ADC_BASE_PIN + ADC_CHANNEL_COUNT)) +#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= ADC_BASE_PIN && (gpio) < (ADC_BASE_PIN + NUM_ADC_CHANNELS)) #define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - ADC_BASE_PIN) - static uint16_t adc_config_and_read_u16(uint32_t channel) { adc_select_input(channel); uint32_t raw = adc_read(); @@ -77,7 +74,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args if (mp_obj_is_int(source)) { channel = mp_obj_get_int(source); - if (!(channel >= 0 && channel < ADC_CHANNEL_COUNT)) { + if (!(channel >= 0 && channel < NUM_ADC_CHANNELS)) { // Not a valid ADC channel, fallback to searching for a pin. channel = -1; } diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c index 412875cb1649b..57981363c97b2 100644 --- a/ports/rp2/machine_uart.c +++ b/ports/rp2/machine_uart.c @@ -85,6 +85,10 @@ #define UART_HWCONTROL_CTS (1) #define UART_HWCONTROL_RTS (2) +// OR-ed IRQ flags which are allowed to be used by the user +#define MP_UART_ALLOWED_FLAGS (UART_UARTMIS_RTMIS_BITS | UART_UARTMIS_TXMIS_BITS | UART_UARTMIS_BEMIS_BITS) +#define UART_FIFO_SIZE_RX (32) +#define UART_FIFO_TRIGGER_LEVEL_RX (24) static mutex_t write_mutex_0; static mutex_t write_mutex_1; @@ -116,12 +120,15 @@ typedef struct _machine_uart_obj_t { mutex_t *read_mutex; ringbuf_t write_buffer; mutex_t *write_mutex; + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object } machine_uart_obj_t; static machine_uart_obj_t machine_uart_obj[] = { {{&machine_uart_type}, uart0, 0, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, MICROPY_HW_UART0_TX, MICROPY_HW_UART0_RX, MICROPY_HW_UART0_CTS, MICROPY_HW_UART0_RTS, - 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_0, {NULL, 1, 0, 0}, &write_mutex_0}, + 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_0, {NULL, 1, 0, 0}, &write_mutex_0, 0, 0, NULL}, {{&machine_uart_type}, uart1, 1, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, MICROPY_HW_UART1_TX, MICROPY_HW_UART1_RX, MICROPY_HW_UART1_CTS, MICROPY_HW_UART1_RTS, 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_1, {NULL, 1, 0, 0}, &write_mutex_1}, @@ -149,14 +156,15 @@ static inline void read_mutex_unlock(machine_uart_obj_t *u) { mutex_exit(u->read_mutex); } -// take all bytes from the fifo and store them in the buffer -static void uart_drain_rx_fifo(machine_uart_obj_t *self) { +// take at most max_items bytes from the fifo and store them in the buffer +static void uart_drain_rx_fifo(machine_uart_obj_t *self, uint32_t max_items) { if (read_mutex_try_lock(self)) { - while (uart_is_readable(self->uart) && ringbuf_free(&self->read_buffer) > 0) { + while (uart_is_readable(self->uart) && ringbuf_free(&self->read_buffer) > 0 && max_items > 0) { // Get a byte from uart and put into the buffer. Every entry from // the FIFO is accompanied by 4 error bits, that may be used for // error handling. uint16_t c = uart_get_hw(self->uart)->dr; + max_items -= 1; if (c & UART_UARTDR_OE_BITS) { // Overrun Error: We missed at least one byte. Not much we can do here. } @@ -192,15 +200,30 @@ static void uart_fill_tx_fifo(machine_uart_obj_t *self) { } static inline void uart_service_interrupt(machine_uart_obj_t *self) { - if (uart_get_hw(self->uart)->mis & (UART_UARTMIS_RXMIS_BITS | UART_UARTMIS_RTMIS_BITS)) { // rx interrupt? - // clear all interrupt bits but tx - uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & (~UART_UARTICR_TXIC_BITS); - uart_drain_rx_fifo(self); + uint16_t mp_irq_flags = uart_get_hw(self->uart)->mis & (UART_UARTMIS_RXMIS_BITS | UART_UARTMIS_RTMIS_BITS); + if (mp_irq_flags) { // rx interrupt? + // clear all interrupt bits but tx and break + uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & ~(UART_UARTICR_TXIC_BITS | UART_UARTICR_BEIC_BITS); + uart_drain_rx_fifo(self, UART_FIFO_TRIGGER_LEVEL_RX - 1); } if (uart_get_hw(self->uart)->mis & UART_UARTMIS_TXMIS_BITS) { // tx interrupt? - // clear all interrupt bits but rx - uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & ~(UART_UARTICR_RXIC_BITS | UART_UARTICR_RTIC_BITS); - uart_fill_tx_fifo(self); + // clear all interrupt bits but rx and break + uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & ~(UART_UARTICR_RXIC_BITS | UART_UARTICR_RTIC_BITS | UART_UARTICR_BEIC_BITS); + if (ringbuf_avail(&self->write_buffer) == 0) { + mp_irq_flags |= UART_UARTMIS_TXMIS_BITS; + } else { + uart_fill_tx_fifo(self); + } + } + if (uart_get_hw(self->uart)->mis & UART_UARTMIS_BEMIS_BITS) { // break interrupt? + // CLear the event + hw_set_bits(&uart_get_hw(self->uart)->icr, UART_UARTICR_BEIC_BITS); + mp_irq_flags |= UART_UARTMIS_BEMIS_BITS; + } + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); } } @@ -220,14 +243,17 @@ static void uart1_irq_handler(void) { { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERT_RX) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_UARTMIS_RTMIS_BITS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(UART_UARTMIS_TXMIS_BITS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_UARTMIS_BEMIS_BITS) }, \ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, " - "txbuf=%d, rxbuf=%d, timeout=%u, timeout_char=%u, invert=%s)", + "txbuf=%d, rxbuf=%d, timeout=%u, timeout_char=%u, invert=%s, irq=%d)", self->uart_id, self->baudrate, self->bits, _parity_name[self->parity], self->stop, self->tx, self->rx, self->write_buffer.size - 1, self->read_buffer.size - 1, - self->timeout, self->timeout_char, _invert_name[self->invert]); + self->timeout, self->timeout_char, _invert_name[self->invert], self->mp_irq_trigger); } static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -446,11 +472,19 @@ static void mp_machine_uart_deinit(machine_uart_obj_t *self) { self->baudrate = 0; MP_STATE_PORT(rp2_uart_rx_buffer[self->uart_id]) = NULL; MP_STATE_PORT(rp2_uart_tx_buffer[self->uart_id]) = NULL; + MP_STATE_PORT(rp2_uart_irq_obj)[self->uart_id] = NULL; + self->mp_irq_obj = NULL; + self->mp_irq_trigger = 0; +} + +void machine_uart_deinit_all() { + mp_machine_uart_deinit((machine_uart_obj_t *)&machine_uart_obj[0]); + mp_machine_uart_deinit((machine_uart_obj_t *)&machine_uart_obj[1]); } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { // get all bytes from the fifo first - uart_drain_rx_fifo(self); + uart_drain_rx_fifo(self, UART_FIFO_SIZE_RX + 1); return ringbuf_avail(&self->read_buffer); } @@ -465,6 +499,77 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { uart_set_break(self->uart, false); } +static void uart_set_irq_level(machine_uart_obj_t *self, uint16_t trigger) { + if (trigger & UART_UARTMIS_BEMIS_BITS) { + // Enable the break Interrupt + hw_set_bits(&uart_get_hw(self->uart)->imsc, UART_UARTIMSC_BEIM_BITS); + } else { + // Disable the break Interrupt + hw_clear_bits(&uart_get_hw(self->uart)->imsc, UART_UARTIMSC_BEIM_BITS); + } + if (trigger & UART_UARTMIS_RTMIS_BITS) { + // Set the RX trigger level to 3/4 FIFO_size + hw_write_masked(&uart_get_hw(self->uart)->ifls, 0b011 << UART_UARTIFLS_RXIFLSEL_LSB, + UART_UARTIFLS_RXIFLSEL_BITS); + } else { + // Set the RX trigger level to 1/8 FIFO_size + hw_write_masked(&uart_get_hw(self->uart)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB, + UART_UARTIFLS_RXIFLSEL_BITS); + } +} + +static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->mp_irq_trigger = new_trigger; + uart_set_irq_level(self, new_trigger); + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + MP_STATE_PORT(rp2_uart_irq_obj)[self->uart_id] = self->mp_irq_obj; + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + uart_set_irq_level(self, trigger); + } + + return self->mp_irq_obj; +} + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_uint_t start = mp_hal_ticks_ms(); @@ -476,7 +581,7 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t while (ringbuf_avail(&self->read_buffer) == 0) { if (uart_is_readable(self->uart)) { // Force a few incoming bytes to the buffer - uart_drain_rx_fifo(self); + uart_drain_rx_fifo(self, UART_FIFO_SIZE_RX + 1); break; } mp_uint_t elapsed = mp_hal_ticks_ms() - start; @@ -577,3 +682,4 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint MP_REGISTER_ROOT_POINTER(void *rp2_uart_rx_buffer[2]); MP_REGISTER_ROOT_POINTER(void *rp2_uart_tx_buffer[2]); +MP_REGISTER_ROOT_POINTER(void *rp2_uart_irq_obj[2]); diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 9d28421cdea9b..f2005c706575d 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -243,6 +243,7 @@ int main(int argc, char **argv) { #endif machine_pwm_deinit_all(); machine_pin_deinit(); + machine_uart_deinit_all(); #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/rp2/modmachine.h b/ports/rp2/modmachine.h index e17ad67b0301d..1ed812afff8f5 100644 --- a/ports/rp2/modmachine.h +++ b/ports/rp2/modmachine.h @@ -8,6 +8,7 @@ void machine_pin_deinit(void); void machine_i2s_init0(void); void machine_i2s_deinit_all(void); void machine_pwm_deinit_all(void); +void machine_uart_deinit_all(void); struct _machine_spi_obj_t *spi_from_mp_obj(mp_obj_t o); diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 3e45dfc8ebd25..23be3240cc13a 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -172,6 +172,7 @@ #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/rp2/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/rp2/machine_wdt.c" #define MICROPY_PY_MACHINE_FREQ_NUM_ARGS_MAX (2) @@ -180,6 +181,7 @@ #define MICROPY_VFS_LFS2 (1) #define MICROPY_VFS_FAT (1) #define MICROPY_SSL_MBEDTLS (1) +#define MICROPY_PY_LWIP_PPP (MICROPY_PY_NETWORK_PPP_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) // Hardware timer alarm index. Available range 0-3. diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index ef060a34b17d5..0d885471538dc 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -32,11 +32,15 @@ #include "py/mphal.h" #include "shared/runtime/mpirq.h" #include "modrp2.h" +#include "machine_pin.h" +#include "genhdr/pins.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/pio.h" +extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; + typedef struct _rp2_pio_obj_t { mp_obj_base_t base; PIO pio; @@ -232,7 +236,7 @@ static void asm_pio_override_shiftctrl(mp_obj_t arg, uint32_t bits, uint32_t lsb } } -static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_base, asm_pio_config_t *config) { +static void asm_pio_get_pins(PIO pio, const char *type, mp_obj_t prog_pins, mp_obj_t arg_base, asm_pio_config_t *config) { if (prog_pins != mp_const_none) { // The PIO program specified pins for initialisation on out/set/sideset. if (mp_obj_is_integer(prog_pins)) { @@ -258,11 +262,18 @@ static void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_ if (arg_base != mp_const_none) { // The instantiation of the PIO program specified a base pin. config->base = mp_hal_get_pin_obj(arg_base); + + #if PICO_PIO_USE_GPIO_BASE + // Check the base is within range of the configured gpio_base. + uint gpio_base = pio_get_gpio_base(pio); + if ((gpio_base == 0 && config->base >= 32) || (gpio_base == 16 && config->base < 16)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%s_base not within gpio_base range"), type); + } + #endif } } static void asm_pio_init_gpio(PIO pio, uint32_t sm, asm_pio_config_t *config) { - uint32_t pinmask = ((1 << config->count) - 1) << (config->base - pio_get_gpio_base(pio)); pio_sm_set_pins_with_mask(pio, sm, config->pinvals << (config->base - pio_get_gpio_base(pio)), pinmask); pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << (config->base - pio_get_gpio_base(pio)), pinmask); @@ -381,28 +392,29 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m } MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_state_machine_obj, 2, rp2_pio_state_machine); -#if PICO_PIO_VERSION > 0 -// PIO.gpio_base(0|16) -static mp_obj_t rp2_pio_gpio_base(mp_obj_t self_in, mp_obj_t gpio_base_in) { - rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); +#if PICO_PIO_USE_GPIO_BASE +// PIO.gpio_base([base]) +static mp_obj_t rp2_pio_gpio_base(size_t n_args, const mp_obj_t *args) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(args[0]); - uint8_t gpio_base = mp_obj_get_int(gpio_base_in); + if (n_args > 1) { + // Set gpio_base value. + uint gpio_base = mp_hal_get_pin_obj(args[1]); - // TODO check for RP2350B? - // Must be 0 for GPIOs 0 to 31 inclusive, - // or 16 for GPIOs 16 to 48 inclusive. - if (gpio_base != 0 && gpio_base != 16) { - mp_raise_ValueError("invalid GPIO base"); - } + // Must be 0 for GPIOs 0 to 31 inclusive, or 16 for GPIOs 16 to 48 inclusive. + if (!(gpio_base == 0 || gpio_base == 16)) { + mp_raise_ValueError("invalid GPIO base"); + } - // Read back with pio->gpiobase - if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { - mp_raise_ValueError("failed to set pio gpio base"); + if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { + mp_raise_OSError(MP_EINVAL); + } } - return mp_const_none; + // Return current gpio_base value. + return pio_get_gpio_base(self->pio) == 0 ? pin_GPIO0 : pin_GPIO16; } -static MP_DEFINE_CONST_FUN_OBJ_2(rp2_pio_gpio_base_obj, rp2_pio_gpio_base); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_pio_gpio_base_obj, 1, 2, rp2_pio_gpio_base); #endif // PIO.irq(handler=None, trigger=IRQ_SM0|IRQ_SM1|IRQ_SM2|IRQ_SM3, hard=False) @@ -458,13 +470,13 @@ static mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k static MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_irq_obj, 1, rp2_pio_irq); static const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { + #if PICO_PIO_USE_GPIO_BASE + { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_add_program), MP_ROM_PTR(&rp2_pio_add_program_obj) }, { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_pio_irq_obj) }, - #if PICO_PIO_VERSION > 0 - { MP_ROM_QSTR(MP_QSTR_gpio_base), MP_ROM_PTR(&rp2_pio_gpio_base_obj) }, - #endif { MP_ROM_QSTR(MP_QSTR_IN_LOW), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_IN_HIGH), MP_ROM_INT(1) }, @@ -661,20 +673,14 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure out pins, if needed. asm_pio_config_t out_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); - if (out_config.base < pio_get_gpio_base(self->pio)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); - } + asm_pio_get_pins(self->pio, "out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); if (out_config.base >= 0) { sm_config_set_out_pins(&config, out_config.base, out_config.count); } // Configure set pin, if needed. asm_pio_config_t set_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); - if (set_config.base < pio_get_gpio_base(self->pio)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("set_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); - } + asm_pio_get_pins(self->pio, "set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); if (set_config.base >= 0) { sm_config_set_set_pins(&config, set_config.base, set_config.count); } @@ -686,10 +692,7 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel // Configure sideset pin, if needed. asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; - asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); - if (sideset_config.base < pio_get_gpio_base(self->pio)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("sideset_base should be >= gpiobase (%d)"), pio_get_gpio_base(self->pio)); - } + asm_pio_get_pins(self->pio, "sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); if (sideset_config.base >= 0) { uint32_t count = sideset_config.count; if (config.execctrl & (1 << PIO_SM0_EXECCTRL_SIDE_EN_LSB)) { diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index b560e0e8e6924..b0dc4c5768d13 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -32,6 +32,7 @@ #include "py/ringbuf.h" #include "samd_soc.h" #include "pin_af.h" +#include "shared/runtime/softtimer.h" #define DEFAULT_UART_BAUDRATE (115200) #define DEFAULT_BUFFER_SIZE (256) @@ -40,7 +41,30 @@ #define FLOW_CONTROL_RTS (1) #define FLOW_CONTROL_CTS (2) +#if MICROPY_PY_MACHINE_UART_IRQ +#define UART_IRQ_RXIDLE (4096) +#define RXIDLE_TIMER_MIN (1) +#define MP_UART_ALLOWED_FLAGS (SERCOM_USART_INTFLAG_RXC | SERCOM_USART_INTFLAG_TXC | UART_IRQ_RXIDLE) + +#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(SERCOM_USART_INTFLAG_RXC) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(SERCOM_USART_INTFLAG_TXC) }, \ + +enum { + RXIDLE_INACTIVE, + RXIDLE_STANDBY, + RXIDLE_ARMED, + RXIDLE_ALERT, +}; +#else #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS +#endif + +typedef struct _soft_timer_entry_extended_t { + soft_timer_entry_t base; + void *context; +} soft_timer_entry_extended_t; typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -67,6 +91,14 @@ typedef struct _machine_uart_obj_t { #if MICROPY_HW_UART_TXBUF ringbuf_t write_buffer; #endif + #if MICROPY_PY_MACHINE_UART_IRQ + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object + soft_timer_entry_extended_t rxidle_timer; + uint8_t rxidle_state; + uint16_t rxidle_ms; + #endif } machine_uart_obj_t; static const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, 2, 3 @@ -93,26 +125,74 @@ void common_uart_irq_handler(int uart_id) { // Handle IRQ if (self != NULL) { Sercom *uart = sercom_instance[self->id]; + #if MICROPY_PY_MACHINE_UART_IRQ + uint16_t mp_irq_flags = 0; + #endif if (uart->USART.INTFLAG.bit.RXC != 0) { // Now handler the incoming data uart_drain_rx_fifo(self, uart); + #if MICROPY_PY_MACHINE_UART_IRQ + if (ringbuf_avail(&self->read_buffer) > 0) { + if (self->mp_irq_trigger & UART_IRQ_RXIDLE) { + if (self->rxidle_state != RXIDLE_INACTIVE) { + if (self->rxidle_state == RXIDLE_STANDBY) { + self->rxidle_timer.base.mode = SOFT_TIMER_MODE_PERIODIC; + soft_timer_insert(&self->rxidle_timer.base, self->rxidle_ms); + } + self->rxidle_state = RXIDLE_ALERT; + } + } else { + mp_irq_flags = SERCOM_USART_INTFLAG_RXC; + } + } + #endif } else if (uart->USART.INTFLAG.bit.DRE != 0) { #if MICROPY_HW_UART_TXBUF // handle the outgoing data if (ringbuf_avail(&self->write_buffer) > 0) { uart->USART.DATA.bit.DATA = ringbuf_get(&self->write_buffer); } else { - // Stop the interrupt if there is no more data + #if MICROPY_PY_MACHINE_UART_IRQ + // Set the TXIDLE flag + mp_irq_flags |= SERCOM_USART_INTFLAG_TXC; + #endif + // Stop the DRE interrupt if there is no more data uart->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE; } #endif - } else { - // Disable the other interrupts, if set by error - uart->USART.INTENCLR.reg = (uint8_t) ~(SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_RXC); } + // Disable the other interrupts, if set by error + uart->USART.INTENCLR.reg = (uint8_t) ~(SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_RXC); + + #if MICROPY_PY_MACHINE_UART_IRQ + // Check the flags to see if the uart user handler should be called + // The handler for RXIDLE is called in the timer callback + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); + } + #endif } } +#if MICROPY_PY_MACHINE_UART_IRQ +static void uart_soft_timer_callback(soft_timer_entry_t *self) { + machine_uart_obj_t *uart = ((soft_timer_entry_extended_t *)self)->context; + if (uart->rxidle_state == RXIDLE_ALERT) { + // At the first call, just switch the state + uart->rxidle_state = RXIDLE_ARMED; + } else if (uart->rxidle_state == RXIDLE_ARMED) { + // At the second call, run the irq callback and stop the timer + // by setting the mode to SOFT_TIMER_MODE_ONE_SHOT. + // Calling soft_timer_remove() would fail here. + self->mode = SOFT_TIMER_MODE_ONE_SHOT; + uart->rxidle_state = RXIDLE_STANDBY; + uart->mp_irq_flags = UART_IRQ_RXIDLE; + mp_irq_handler(uart->mp_irq_obj); + } +} +#endif + // Configure the Sercom device static void machine_sercom_configure(machine_uart_obj_t *self) { Sercom *uart = sercom_instance[self->id]; @@ -202,6 +282,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #if MICROPY_HW_UART_RTSCTS ", rts=%q, cts=%q" #endif + #if MICROPY_PY_MACHINE_UART_IRQ + ", irq=%d" + #endif ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1 @@ -212,6 +295,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ , self->rts != 0xff ? pin_find_by_id(self->rts)->name : MP_QSTR_None , self->cts != 0xff ? pin_find_by_id(self->cts)->name : MP_QSTR_None #endif + #if MICROPY_PY_MACHINE_UART_IRQ + , self->mp_irq_trigger + #endif ); } @@ -228,9 +314,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - #if MICROPY_HW_UART_TXBUF { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - #endif #if MICROPY_HW_UART_RTSCTS { MP_QSTR_rts, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_cts, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -386,6 +470,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->rts = 0xff; self->cts = 0xff; #endif + #if MICROPY_PY_MACHINE_UART_IRQ + self->mp_irq_obj = NULL; + self->rxidle_state = RXIDLE_INACTIVE; + #endif self->new = true; MP_STATE_PORT(sercom_table[uart_id]) = self; @@ -445,6 +533,85 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { mp_hal_set_pin_mux(self->tx, self->tx_pad_config.alt_fct); } +#if MICROPY_PY_MACHINE_UART_IRQ + +// Configure the timer used for IRQ_RXIDLE +static void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger) { + self->rxidle_state = RXIDLE_INACTIVE; + + if (trigger & UART_IRQ_RXIDLE) { + // The RXIDLE event is always a soft IRQ. + self->mp_irq_obj->ishard = false; + mp_int_t ms = 13000 / self->baudrate + 1; + if (ms < RXIDLE_TIMER_MIN) { + ms = RXIDLE_TIMER_MIN; + } + self->rxidle_ms = ms; + self->rxidle_timer.context = self; + soft_timer_static_init( + &self->rxidle_timer.base, + SOFT_TIMER_MODE_PERIODIC, + ms, + uart_soft_timer_callback + ); + self->rxidle_state = RXIDLE_STANDBY; + } +} + +static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + uart_irq_configure_timer(self, new_trigger); + self->mp_irq_trigger = new_trigger; + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + uart_irq_configure_timer(self, trigger); + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + } + + return self->mp_irq_obj; +} + +#endif + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); Sercom *uart = sercom_instance[self->id]; @@ -481,9 +648,17 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ const uint8_t *src = buf_in; Sercom *uart = sercom_instance[self->id]; - #if MICROPY_HW_UART_TXBUF uint64_t t = mp_hal_ticks_ms_64() + self->timeout; + #if MICROPY_HW_UART_TXBUF + #if MICROPY_PY_MACHINE_UART_IRQ + // Prefill the FIFO to get rid of the initial IRQ_TXIDLE event + while (i < size && ringbuf_free(&(self->write_buffer)) > 0) { + ringbuf_put(&(self->write_buffer), *src++); + i++; + } + uart->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; // kick off the IRQ + #endif while (i < size) { // Wait for the first/next character to be sent. while (ringbuf_free(&(self->write_buffer)) == 0) { @@ -506,6 +681,15 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ while (i < size) { while (!(uart->USART.INTFLAG.bit.DRE)) { + if (mp_hal_ticks_ms_64() > t) { // timed out + if (i <= 0) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } else { + return i; + } + } + MICROPY_EVENT_POLL_HOOK } uart->USART.DATA.bit.DATA = *src++; i++; diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index fdad3fee4a71b..29965f50f63a8 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -51,6 +51,7 @@ unsigned long trng_random_u32(int delay); #ifndef MICROPY_HW_UART_RTSCTS #define MICROPY_HW_UART_RTSCTS (SAMD21_EXTRA_FEATURES) #endif +#define MICROPY_PY_MACHINE_UART_IRQ (SAMD21_EXTRA_FEATURES) // selected extensions of the extra features set #define MICROPY_PY_OS_URANDOM (1) @@ -63,12 +64,20 @@ unsigned long trng_random_u32(int delay); #define MICROPY_PY_BUILTINS_ROUND_INT (SAMD21_EXTRA_FEATURES) #define MICROPY_CAN_OVERRIDE_BUILTINS (SAMD21_EXTRA_FEATURES) #define MICROPY_PY_SYS_STDIO_BUFFER (SAMD21_EXTRA_FEATURES) +#ifndef MICROPY_PY_FRAMEBUF #define MICROPY_PY_FRAMEBUF (SAMD21_EXTRA_FEATURES) +#endif +#ifndef MICROPY_PY_ASYNCIO #define MICROPY_PY_ASYNCIO (SAMD21_EXTRA_FEATURES) +#endif +#ifndef MICROPY_PY_SELECT #define MICROPY_PY_SELECT (SAMD21_EXTRA_FEATURES) +#endif #define MICROPY_PY_ERRNO (SAMD21_EXTRA_FEATURES) #define MICROPY_PY_DEFLATE (SAMD21_EXTRA_FEATURES) +#ifndef MICROPY_PY_ONEWIRE #define MICROPY_PY_ONEWIRE (SAMD21_EXTRA_FEATURES) +#endif #ifndef MICROPY_PY_MACHINE_PIN_BOARD_CPU #define MICROPY_PY_MACHINE_PIN_BOARD_CPU (1) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index d567f28eb423a..9a7b8528f3573 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -15,6 +15,7 @@ #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); +#define MICROPY_PY_MACHINE_UART_IRQ (1) // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) diff --git a/ports/stm32/lwip_inc/arch/cc.h b/ports/stm32/lwip_inc/arch/cc.h index fc5230ef723ea..a818d6d6a0b61 100644 --- a/ports/stm32/lwip_inc/arch/cc.h +++ b/ports/stm32/lwip_inc/arch/cc.h @@ -2,7 +2,9 @@ #define MICROPY_INCLUDED_STM32_LWIP_ARCH_CC_H #include -#define LWIP_PLATFORM_DIAG(x) +#include + +#define LWIP_PLATFORM_DIAG(x) do { printf x; } while (0) #define LWIP_PLATFORM_ASSERT(x) { assert(1); } #define LWIP_NO_CTYPE_H 1 diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index f641cf515add2..b779b558c9ec7 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -1,7 +1,7 @@ #ifndef MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H -#include +#include "py/mpconfig.h" // This protection is not needed, instead we execute all lwIP code at PendSV priority #define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) @@ -36,6 +36,12 @@ #define LWIP_MDNS_RESPONDER 1 #define LWIP_IGMP 1 +#if MICROPY_PY_LWIP_PPP +#define PPP_SUPPORT 1 +#define PAP_SUPPORT 1 +#define CHAP_SUPPORT 1 +#endif + #define LWIP_NUM_NETIF_CLIENT_DATA LWIP_MDNS_RESPONDER #define MEMP_NUM_UDP_PCB (4 + LWIP_MDNS_RESPONDER) #define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + LWIP_MDNS_RESPONDER) @@ -77,4 +83,7 @@ extern uint32_t rng_get(void); typedef uint32_t sys_prot_t; +// Needed for PPP. +#define sys_jiffies sys_now + #endif // MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 0f139ae83272f..6be6bcdac1a12 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -41,6 +41,7 @@ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_FLAG_IDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_FLAG_RXNE) }, \ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -153,6 +154,12 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, { MP_QSTR_read_buf_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, // legacy }; + if (self->is_enabled && n_args == 1 && kw_args->used == 0) { + // Only change the baudrate if that's all that is given. + uart_set_baudrate(self, mp_obj_get_int(pos_args[0])); + return; + } + // parse args struct { mp_arg_val_t baudrate, bits, parity, stop, flow, timeout, timeout_char, rxbuf, read_buf_len; @@ -526,6 +533,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ if (*errcode == 0 || *errcode == MP_ETIMEDOUT) { // return number of bytes written, even if there was a timeout + *errcode = 0; return num_tx << self->char_width; } else { return MP_STREAM_ERROR; diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 25fc9e11f9d68..c6ba83d1bd293 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -106,6 +106,7 @@ #define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/stm32/modtime.c" +#define MICROPY_PY_LWIP_PPP (MICROPY_PY_NETWORK_PPP_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 886cf1ab340ba..855f44f0f2345 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -1069,26 +1069,20 @@ size_t uart_tx_data(machine_uart_obj_t *self, const void *src_in, size_t num_cha } uint32_t timeout; - if (self->uartx->CR3 & USART_CR3_CTSE) { - // CTS can hold off transmission for an arbitrarily long time. Apply - // the overall timeout rather than the character timeout. - timeout = self->timeout; - } else { - #if defined(STM32G4) - // With using UART FIFO, the timeout should be long enough that FIFO becomes empty. - // Since previous data transfer may be ongoing, the timeout must be multiplied - // timeout_char by FIFO size + 1. - // STM32G4 has 8 words FIFO. - timeout = (8 + 1) * self->timeout_char; - #else - // The timeout specified here is for waiting for the TX data register to - // become empty (ie between chars), as well as for the final char to be - // completely transferred. The default value for timeout_char is long - // enough for 1 char, but we need to double it to wait for the last char - // to be transferred to the data register, and then to be transmitted. - timeout = 2 * self->timeout_char; - #endif - } + #if defined(STM32G4) + // With using UART FIFO, the timeout should be long enough that FIFO becomes empty. + // Since previous data transfer may be ongoing, the timeout must be multiplied + // timeout_char by FIFO size + 1. + // STM32G4 has 8 words FIFO. + timeout = (8 + 1) * self->timeout_char; + #else + // The timeout specified here is for waiting for the TX data register to + // become empty (ie between chars), as well as for the final char to be + // completely transferred. The default value for timeout_char is long + // enough for 1 char, but we need to double it to wait for the last char + // to be transferred to the data register, and then to be transmitted. + timeout = 2 * self->timeout_char; + #endif const uint8_t *src = (const uint8_t *)src_in; size_t num_tx = 0; diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index 956cbb044f6c0..de4b70cdea738 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -52,7 +52,7 @@ typedef enum { #define CHAR_WIDTH_9BIT (1) // OR-ed IRQ flags which are allowed to be used by the user -#define MP_UART_ALLOWED_FLAGS UART_FLAG_IDLE +#define MP_UART_ALLOWED_FLAGS (UART_FLAG_IDLE | UART_FLAG_RXNE) // OR-ed IRQ flags which should not be touched by the user #define MP_UART_RESERVED_FLAGS UART_FLAG_RXNE diff --git a/ports/unix/coveragecpp.cpp b/ports/unix/coveragecpp.cpp index 93c1b387fe285..23c3955ae9d0b 100644 --- a/ports/unix/coveragecpp.cpp +++ b/ports/unix/coveragecpp.cpp @@ -1,5 +1,20 @@ extern "C" { -#include "py/obj.h" +// Include the complete public API to verify everything compiles as C++. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include } #if defined(MICROPY_UNIX_COVERAGE) diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 2ae5fd22273c1..c4a135aa53e43 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -59,13 +59,6 @@ #include "modmachine.h" #include "modzephyr.h" -#ifdef TEST -#include "shared/upytesthelper/upytesthelper.h" -#include "lib/tinytest/tinytest.c" -#include "shared/upytesthelper/upytesthelper.c" -#include TEST -#endif - static char heap[MICROPY_HEAP_SIZE]; void init_zephyr(void) { @@ -128,13 +121,6 @@ int real_main(void) { init_zephyr(); mp_hal_init(); - #ifdef TEST - static const char *argv[] = {"test"}; - upytest_set_heap(heap, heap + sizeof(heap)); - int r = tinytest_main(1, argv, groups); - printf("status: %d\n", r); - #endif - soft_reset: #if MICROPY_ENABLE_GC gc_init(heap, heap + sizeof(heap)); diff --git a/ports/zephyr/make-bin-testsuite b/ports/zephyr/make-bin-testsuite deleted file mode 100755 index 9a8c329772143..0000000000000 --- a/ports/zephyr/make-bin-testsuite +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# -# This is a wrapper for make to build a binary with builtin testsuite. -# It should be run just like make (i.e. extra vars can be passed on the -# command line, etc.), e.g.: -# -# ./make-bin-testsuite BOARD=qemu_cortex_m3 -# ./make-bin-testsuite BOARD=qemu_cortex_m3 run -# - -(cd ../../tests; ./run-tests.py --write-exp) -(cd ../../tests; ./run-tests.py --list-tests --target=minimal \ - -e async -e intbig -e int_big -e builtin_help -e memstats -e bytes_compare3 -e class_reverse_op \ - -e /set -e frozenset -e complex -e const -e native -e viper \ - -e 'float_divmod\.' -e float_parse_doubleprec -e float/true_value -e float/types \ - | ../tools/tinytest-codegen.py --stdin) > bin-testsuite.c - -make \ - CFLAGS_EXTRA='-DMP_CONFIGFILE="" -DTEST=\"bin-testsuite.c\" -DNO_FORKING' \ - "$@" diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 6046690197c25..d3983fcc25102 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -8,7 +8,7 @@ CONFIG_UART_CONSOLE_DEBUG_SERVER_HOOKS=y CONFIG_CONSOLE_SUBSYS=y CONFIG_CONSOLE_GETCHAR=y -CONFIG_CONSOLE_GETCHAR_BUFSIZE=128 +CONFIG_CONSOLE_GETCHAR_BUFSIZE=258 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=128 CONFIG_NEWLIB_LIBC=y diff --git a/ports/zephyr/prj_minimal.conf b/ports/zephyr/prj_minimal.conf index 806e26af77841..2c89a02fae263 100644 --- a/ports/zephyr/prj_minimal.conf +++ b/ports/zephyr/prj_minimal.conf @@ -6,7 +6,7 @@ CONFIG_POLL=y CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_CONSOLE_SUBSYS=y CONFIG_CONSOLE_GETCHAR=y -CONFIG_CONSOLE_GETCHAR_BUFSIZE=32 +CONFIG_CONSOLE_GETCHAR_BUFSIZE=258 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=32 # TODO: Disable once https://github.com/zephyrproject-rtos/zephyr/pull/13731 is merged #CONFIG_CONSOLE=n diff --git a/py/mkrules.mk b/py/mkrules.mk index 5050935873aff..0dc1cdfe15ac4 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -251,8 +251,8 @@ endif submodules: $(ECHO) "Updating submodules: $(GIT_SUBMODULES)" ifneq ($(GIT_SUBMODULES),) - $(Q)git submodule sync $(addprefix $(TOP)/,$(GIT_SUBMODULES)) - $(Q)git submodule update --init $(addprefix $(TOP)/,$(GIT_SUBMODULES)) + $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) + $(Q)cd $(TOP) && git submodule update --init $(GIT_SUBMODULES) endif .PHONY: submodules diff --git a/shared/runtime/semihosting_arm.c b/shared/runtime/semihosting_arm.c index 3ca29d5d752c4..f4d168f79bc81 100644 --- a/shared/runtime/semihosting_arm.c +++ b/shared/runtime/semihosting_arm.c @@ -34,16 +34,14 @@ #define SYS_OPEN 0x01 #define SYS_WRITEC 0x03 #define SYS_WRITE 0x05 +#define SYS_READ 0x06 #define SYS_READC 0x07 +#define SYS_EXIT 0x18 // Constants: #define OPEN_MODE_READ (0) // mode "r" #define OPEN_MODE_WRITE (4) // mode "w" -#ifndef __thumb__ -#error Semihosting is only implemented for ARM microcontrollers. -#endif - static int mp_semihosting_stdout; static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { @@ -61,7 +59,13 @@ static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { register uint32_t num_reg __asm__ ("r0") = num; register const void *args_reg __asm__ ("r1") = arg; __asm__ __volatile__ ( + #if defined(__ARM_ARCH_ISA_ARM) + "svc 0x00123456\n" // invoke semihosting call + #elif defined(__ARM_ARCH_ISA_THUMB) "bkpt 0xAB\n" // invoke semihosting call + #else + #error Unknown architecture + #endif : "+r" (num_reg) // call number and result : "r" (args_reg) // arguments : "memory"); // make sure args aren't optimized away @@ -85,10 +89,31 @@ void mp_semihosting_init() { mp_semihosting_stdout = mp_semihosting_open_console(OPEN_MODE_WRITE); } +void mp_semihosting_exit(int status) { + if (status == 0) { + status = 0x20026; + } + mp_semihosting_call(SYS_EXIT, (void *)(uintptr_t)status); +} + int mp_semihosting_rx_char() { return mp_semihosting_call(SYS_READC, NULL); } +// Returns 0 on success. +int mp_semihosting_rx_chars(char *str, size_t len) { + struct { + uint32_t fd; + const char *str; + uint32_t len; + } args = { + .fd = mp_semihosting_stdout, + .str = str, + .len = len, + }; + return mp_semihosting_call(SYS_READ, &args); +} + static void mp_semihosting_tx_char(char c) { mp_semihosting_call(SYS_WRITEC, &c); } diff --git a/shared/runtime/semihosting_arm.h b/shared/runtime/semihosting_arm.h index 7e90f25ac916c..08fb66578ac14 100644 --- a/shared/runtime/semihosting_arm.h +++ b/shared/runtime/semihosting_arm.h @@ -38,13 +38,17 @@ Then make sure the debugger is attached and enables semihosting. In OpenOCD thi done with ARM semihosting enable followed by reset. The terminal will need further configuration to work with MicroPython (bash: stty raw -echo). +If mp_semihosting_rx_char() doesn't work then try mp_semihosting_rx_chars(str, 1). + */ #include #include void mp_semihosting_init(); +void mp_semihosting_exit(int status); int mp_semihosting_rx_char(); +int mp_semihosting_rx_chars(char *str, size_t len); uint32_t mp_semihosting_tx_strn(const char *str, size_t len); uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len); diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 937cc070157c6..5c8f2a6095f30 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -40,6 +40,13 @@ #include "device/dcd.h" #endif +// Initialise TinyUSB device. +static inline void mp_usbd_init_tud(void) { + tusb_init(); + tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; + tud_cdc_configure_fifo(&cfg); +} + // Run the TinyUSB device task void mp_usbd_task(void); @@ -125,9 +132,8 @@ inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd static inline void mp_usbd_init(void) { // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() - tusb_init(); - tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; - tud_cdc_configure_fifo(&cfg); + // which is called in the below helper function. + mp_usbd_init_tud(); } #endif diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index a17aaf1f43224..608aa74d244f6 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -428,10 +428,10 @@ void mp_usbd_init(void) { } if (need_usb) { - tusb_init(); // Safe to call redundantly - tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; - tud_cdc_configure_fifo(&cfg); - tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected + // The following will call tusb_init(), which is safe to call redundantly. + mp_usbd_init_tud(); + // Reconnect if mp_usbd_deinit() has disconnected. + tud_connect(); } } diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index ab47321afdc04..0cc5ef03985ef 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -77,9 +77,13 @@ // CDC Configuration #if CFG_TUD_CDC +#ifndef CFG_TUD_CDC_RX_BUFSIZE #define CFG_TUD_CDC_RX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) +#endif +#ifndef CFG_TUD_CDC_TX_BUFSIZE #define CFG_TUD_CDC_TX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) #endif +#endif // MSC Configuration #if CFG_TUD_MSC diff --git a/tests/extmod/machine_uart_irq_txidle.py b/tests/extmod/machine_uart_irq_txidle.py new file mode 100644 index 0000000000000..3e69bb43141aa --- /dev/null +++ b/tests/extmod/machine_uart_irq_txidle.py @@ -0,0 +1,76 @@ +# Test machine.UART.IRQ_TXIDLE firing after a transmission. +# Does not require any external connections. + +try: + from machine import UART + + UART.IRQ_TXIDLE +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "rp2" in sys.platform: + uart_id = 0 + tx_pin = "GPIO0" + rx_pin = "GPIO1" + min_window = 1 +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + # For SAMD delay_ms has to be used for the trailing window, and the + # mininmal time is 11 ms to allow for scheduling. + min_window = 11 +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" + min_window = 11 +elif "mimxrt" in sys.platform: + uart_id = 1 + tx_pin = None + min_window = 0 +elif "nrf" in sys.platform: + uart_id = 0 + tx_pin = None + min_window = 0 +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_TXIDLE:", u.irq().flags() == u.IRQ_TXIDLE) + + +text = "Hello World" * 20 + +# Test that the IRQ is called after the write has completed. +for bits_per_s in (2400, 9600, 115200): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_TXIDLE) + + # The IRQ_TXIDLE shall trigger after the message has been sent. Thus + # the test marks a time window close to the expected of the sending + # and the time at which the IRQ should have been fired. + # It is just a rough estimation of 10 characters before and + # 20 characters after the data's end, unless there is a need to + # wait a minimal time, like for SAMD devices. + + bits_per_char = 10 # 1(startbit) + 8(bits) + 1(stopbit) + 0(parity) + start_time_us = (len(text) - 10) * bits_per_char * 1_000_000 // bits_per_s + window_ms = max(min_window, 20 * bits_per_char * 1_000 // bits_per_s + 1) + + print("write", bits_per_s) + uart.write(text) + time.sleep_us(start_time_us) + print("ready") + time.sleep_ms(window_ms) + print("done") diff --git a/tests/extmod/machine_uart_irq_txidle.py.exp b/tests/extmod/machine_uart_irq_txidle.py.exp new file mode 100644 index 0000000000000..2da51d10faada --- /dev/null +++ b/tests/extmod/machine_uart_irq_txidle.py.exp @@ -0,0 +1,12 @@ +write 2400 +ready +IRQ_TXIDLE: True +done +write 9600 +ready +IRQ_TXIDLE: True +done +write 115200 +ready +IRQ_TXIDLE: True +done diff --git a/tests/extmod_hardware/machine_uart_irq_break.py b/tests/extmod_hardware/machine_uart_irq_break.py new file mode 100644 index 0000000000000..82879c1d6e4a8 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_break.py @@ -0,0 +1,62 @@ +# Test machine.UART.IRQ_BREAK firing after a break is received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_BREAK +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "esp32" in sys.platform: + if "ESP32S2" in sys.implementation._machine or "ESP32C3" in sys.implementation._machine: + print("SKIP") + raise SystemExit + # ESP32 needs separate UART instances for the test + recv_uart_id = 1 + recv_tx_pin = 14 + recv_rx_pin = 5 + send_uart_id = 2 + send_tx_pin = 4 + send_rx_pin = 12 +elif "rp2" in sys.platform: + recv_uart_id = 0 + send_uart_id = 0 + recv_tx_pin = "GPIO0" + recv_rx_pin = "GPIO1" +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_BREAK:", bool(u.irq().flags() & u.IRQ_BREAK), "data:", u.read(1)) + + +# Test that the IRQ is called for each break received. +for bits_per_s in (2400, 9600, 57600): + recv_uart = UART(recv_uart_id, bits_per_s, tx=recv_tx_pin, rx=recv_rx_pin) + if recv_uart_id != send_uart_id: + send_uart = UART(send_uart_id, bits_per_s, tx=send_tx_pin, rx=send_rx_pin) + else: + send_uart = recv_uart + + recv_uart.irq(irq, recv_uart.IRQ_BREAK) + + print("write", bits_per_s) + for i in range(3): + send_uart.write(str(i)) + send_uart.flush() + time.sleep_ms(10) + send_uart.sendbreak() + time.sleep_ms(10) + if "esp32" in sys.platform: + # On esp32 a read is needed to read in the break byte. + recv_uart.read() + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_break.py.exp b/tests/extmod_hardware/machine_uart_irq_break.py.exp new file mode 100644 index 0000000000000..ca8afe8f2b6bf --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_break.py.exp @@ -0,0 +1,15 @@ +write 2400 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done +write 9600 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done +write 57600 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py new file mode 100644 index 0000000000000..bf34900bd0826 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -0,0 +1,78 @@ +# Test machine.UART.IRQ_RX firing for each character received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_RX +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +byte_by_byte = False +# Configure pins based on the target. +if "esp32" in sys.platform: + uart_id = 1 + tx_pin = 4 + rx_pin = 5 +elif "pyboard" in sys.platform: + uart_id = 4 + tx_pin = None # PA0 + rx_pin = None # PA1 +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + byte_by_byte = True +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" +elif "nrf" in sys.platform: + uart_id = 0 + tx_pin = None + rx_pin = None +elif "renesas-ra" in sys.platform: + uart_id = 9 + tx_pin = None # P602 @ RA6M2 + rx_pin = None # P601 @ RA6M2 +elif "CC3200" in sys.implementation._machine: + # CC3200 doesn't work because it's too slow and has an allocation error in the handler. + print("SKIP") + raise SystemExit +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_RX:", bool(u.irq().flags() & u.IRQ_RX), "data:", u.read(1)) + + +text = "1234" + +# Test that the IRQ is called for each byte received. +# Use slow baudrates so that the IRQ has time to run. +for bits_per_s in (2400, 9600): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_RX) + + print("write", bits_per_s) + if byte_by_byte: + # slow devices need data to be sent slow + for c in text: + uart.write(c) + uart.flush() + else: + uart.write(text) + uart.flush() + time.sleep_ms(100) + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py.exp b/tests/extmod_hardware/machine_uart_irq_rx.py.exp new file mode 100644 index 0000000000000..945b1e508fa7c --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rx.py.exp @@ -0,0 +1,12 @@ +write 2400 +IRQ_RX: True data: b'1' +IRQ_RX: True data: b'2' +IRQ_RX: True data: b'3' +IRQ_RX: True data: b'4' +done +write 9600 +IRQ_RX: True data: b'1' +IRQ_RX: True data: b'2' +IRQ_RX: True data: b'3' +IRQ_RX: True data: b'4' +done diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py new file mode 100644 index 0000000000000..182ab24ebe0c4 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -0,0 +1,70 @@ +# Test machine.UART.IRQ_RXIDLE firing after a set of characters are received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_RXIDLE +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "esp32" in sys.platform: + uart_id = 1 + tx_pin = 4 + rx_pin = 5 +elif "mimxrt" in sys.platform: + uart_id = 1 + tx_pin = None +elif "pyboard" in sys.platform: + uart_id = 4 + tx_pin = None # PA0 + rx_pin = None # PA1 +elif "renesas-ra" in sys.platform: + uart_id = 9 + tx_pin = None # P602 @ RA6M2 + rx_pin = None # P601 @ RA6M2 +elif "rp2" in sys.platform: + uart_id = 0 + tx_pin = "GPIO0" + rx_pin = "GPIO1" +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + byte_by_byte = True +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_RXIDLE:", bool(u.irq().flags() & u.IRQ_RXIDLE), "data:", u.read()) + + +text = "12345678" + +# Test that the IRQ is called for each set of byte received. +for bits_per_s in (2400, 9600, 115200): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_RXIDLE) + + print("write", bits_per_s) + uart.write(text) + uart.flush() + print("ready") + time.sleep_ms(100) + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp b/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp new file mode 100644 index 0000000000000..ce1890a06a4b2 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp @@ -0,0 +1,12 @@ +write 2400 +ready +IRQ_RXIDLE: True data: b'12345678' +done +write 9600 +ready +IRQ_RXIDLE: True data: b'12345678' +done +write 115200 +ready +IRQ_RXIDLE: True data: b'12345678' +done diff --git a/tests/ports/rp2/rp2_uart.py b/tests/ports/rp2/rp2_uart.py index 44410fc92611c..da88599344795 100644 --- a/tests/ports/rp2/rp2_uart.py +++ b/tests/ports/rp2/rp2_uart.py @@ -5,6 +5,6 @@ print(UART(0, tx=0, rx=1)) -if "RP2350" in "sys.implementation._machine": +if "RP2350" in sys.implementation._machine: # Test that UART can be constructed using other tx/rx pins. UART(0, tx=2, rx=3) diff --git a/tests/run-tests.py b/tests/run-tests.py index 83344714c0375..60bfc2599fb47 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -563,6 +563,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # These tests don't test slice explicitly but rather use it to perform the test misc_slice_tests = ( "builtin_range", + "bytearray1", "class_super", "containment", "errno1", @@ -573,6 +574,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): "memoryview_gc", "object1", "python34", + "string_format_modulo", "struct_endian", ) @@ -674,7 +676,11 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): "extmod/time_time_ns.py" ) # RA fsp rtc function doesn't support nano sec info elif args.target == "qemu-arm": - skip_tests.add("misc/print_exception.py") # requires sys stdfiles + skip_tests.add("inlineasm/asmfpaddsub.py") # requires Cortex-M4 + skip_tests.add("inlineasm/asmfpcmp.py") + skip_tests.add("inlineasm/asmfpldrstr.py") + skip_tests.add("inlineasm/asmfpmuldiv.py") + skip_tests.add("inlineasm/asmfpsqrt.py") elif args.target == "qemu-riscv": skip_tests.add("misc/print_exception.py") # requires sys stdfiles elif args.target == "webassembly": @@ -1041,7 +1047,6 @@ def main(): LOCAL_TARGETS = ( "unix", - "qemu-arm", "qemu-riscv", "webassembly", ) @@ -1052,6 +1057,7 @@ def main(): "esp32", "minimal", "nrf", + "qemu-arm", "renesas-ra", "rp2", ) @@ -1139,10 +1145,6 @@ def main(): "ports/unix", ) elif args.target == "qemu-arm": - if not args.write_exp: - raise ValueError("--target=qemu-arm must be used with --write-exp") - # Generate expected output files for qemu run. - # This list should match the test_dirs tuple in tinytest-codegen.py. test_dirs += ( "float", "inlineasm", diff --git a/tools/ci.sh b/tools/ci.sh index 1f86c3ed0a7da..9774895faf28b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -258,10 +258,8 @@ function ci_qemu_arm_build { make ${MAKEOPTS} -C ports/qemu-arm submodules make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 make ${MAKEOPTS} -C ports/qemu-arm clean - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test submodules - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test clean - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test BOARD=sabrelite test + make ${MAKEOPTS} -C ports/qemu-arm test + make ${MAKEOPTS} -C ports/qemu-arm BOARD=sabrelite test } ######################################################################################## @@ -751,6 +749,11 @@ function ci_zephyr_setup { -w /micropython/ports/zephyr \ zephyrprojectrtos/ci:${ZEPHYR_DOCKER_VERSION} docker ps -a + + # qemu-system-arm is needed to run the test suite. + sudo apt-get update + sudo apt-get install qemu-system-arm + qemu-system-arm --version } function ci_zephyr_install { @@ -765,3 +768,10 @@ function ci_zephyr_build { docker exec zephyr-ci west build -p auto -b mimxrt1050_evk docker exec zephyr-ci west build -p auto -b nucleo_wb55rg # for bluetooth } + +function ci_zephyr_run_tests { + docker exec zephyr-ci west build -p auto -b qemu_cortex_m3 -- -DCONF_FILE=prj_minimal.conf + # Issues with zephyr tests: + # - inf_nan_arith fails pow(-1, nan) test + (cd tests && ./run-tests.py --target minimal --device execpty:"qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -monitor null -serial pty -kernel ../ports/zephyr/build/zephyr/zephyr.elf" -d basics float --exclude inf_nan_arith) +} diff --git a/tools/pyboard.py b/tools/pyboard.py index c422b64ac5516..55cbe8384d580 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -235,9 +235,9 @@ def __init__(self, cmd): preexec_fn=os.setsid, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, ) - pty_line = self.subp.stderr.readline().decode("utf-8") + pty_line = self.subp.stdout.readline().decode("utf-8") m = re.search(r"/dev/pts/[0-9]+", pty_line) if not m: print("Error: unable to find PTY device in startup line:", pty_line) From 16da9bd8cae7ecff435bf72135cc9f81114c82c5 Mon Sep 17 00:00:00 2001 From: Kirk Benell Date: Sun, 8 Sep 2024 06:58:31 -0600 Subject: [PATCH 87/87] enable split heap - include sram with psram in heap --- ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h index 124bb83c45c08..85192612cf501 100755 --- a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h @@ -21,4 +21,6 @@ // PSRAM max frequency is based on VDD (see datasheet) - SparkFun Pro Micro RP2350 uses 3.3V => 109MHz #define MICROPY_HW_PSRAM_MAX_SCK_HZ (109*1000*1000) +// Enable the heap split - heap is split between sram and psram memory +#define MICROPY_GC_SPLIT_HEAP (1) // NeoPixel data GPIO25, power not toggleable